Use pathlib.Path, remove img extensions, fix under strict typing
This commit is contained in:
parent
72fbcb42d6
commit
3ddd3dec8e
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
result
|
result
|
||||||
.direnv
|
.direnv
|
||||||
|
.vscode/*
|
||||||
|
|
||||||
*.pdf
|
*.pdf
|
||||||
addresses.csv
|
addresses.csv
|
||||||
|
|
115
format.py
115
format.py
|
@ -13,10 +13,19 @@ import xml.etree.ElementTree as ET
|
||||||
import requests
|
import requests
|
||||||
import imb
|
import imb
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
# A lot of stuff needs to be in the same directory, just chdir
|
# A lot of stuff needs to be in the same directory, just chdir
|
||||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def cache_dir() -> Path:
|
||||||
|
cache = Path("cache")
|
||||||
|
cache.mkdir(exist_ok=True)
|
||||||
|
return cache
|
||||||
|
|
||||||
|
|
||||||
def iso_code(s: str) -> str:
|
def iso_code(s: str) -> str:
|
||||||
if len(s) != 2:
|
if len(s) != 2:
|
||||||
raise ValueError("must be 2 characters long")
|
raise ValueError("must be 2 characters long")
|
||||||
|
@ -27,74 +36,70 @@ def iso_code(s: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_discord_avatar(
|
def get_discord_avatar(
|
||||||
url: urllib.parse.ParseResult, secrets: dict
|
url: urllib.parse.ParseResult, secrets: dict[str, str]
|
||||||
) -> (typing.Optional[str], str):
|
) -> typing.Optional[str]:
|
||||||
try:
|
try:
|
||||||
|
uid = url.path
|
||||||
token = secrets["discord_token"]
|
token = secrets["discord_token"]
|
||||||
user_info = requests.get(
|
user_info = requests.get(
|
||||||
f"https://discord.com/api/users/{url.path}",
|
f"https://discord.com/api/users/{uid}",
|
||||||
headers={"Authorization": f"Bot {token}"},
|
headers={"Authorization": f"Bot {token}"},
|
||||||
).json()
|
).json()
|
||||||
avatar_hash = user_info["avatar"]
|
avatar_hash = user_info["avatar"]
|
||||||
return (
|
return f"https://cdn.discordapp.com/avatars/{uid}/{avatar_hash}.png?size=4096"
|
||||||
f"https://cdn.discordapp.com/avatars/{url.path}/{avatar_hash}.png?size=4096",
|
|
||||||
"png",
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None, ""
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_fedi_avatar(
|
def get_fedi_avatar(
|
||||||
url: urllib.parse.ParseResult, secrets: dict
|
url: urllib.parse.ParseResult, secrets: dict[str, str]
|
||||||
) -> (typing.Optional[str], str):
|
) -> typing.Optional[str]:
|
||||||
try:
|
try:
|
||||||
mastodon_api = secrets["mastodon_api"]
|
mastodon_api = secrets["mastodon_api"]
|
||||||
user_info = requests.get(
|
user_info = requests.get(
|
||||||
f"{mastodon_api}/api/v1/accounts/lookup", params={"acct": url.path}
|
f"{mastodon_api}/api/v1/accounts/lookup", params={"acct": url.path}
|
||||||
).json()
|
).json()
|
||||||
avatar_url = user_info["avatar_static"]
|
avatar_url = user_info["avatar_static"]
|
||||||
extension = avatar_url.split(".")[-1]
|
return avatar_url
|
||||||
return avatar_url, extension
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None, ""
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_orig_avatar(url: str, basename: str, secrets: dict) -> typing.Optional[bytes]:
|
def get_orig_avatar(
|
||||||
if not os.path.exists("cache"):
|
url: str, basename: str, secrets: dict[str, str]
|
||||||
os.mkdir("cache")
|
) -> typing.Optional[bytes]:
|
||||||
|
|
||||||
url_parts = urllib.parse.urlparse(url)
|
url_parts = urllib.parse.urlparse(url)
|
||||||
if url_parts.scheme == "fedi":
|
if url_parts.scheme == "fedi":
|
||||||
real_url, extension = get_fedi_avatar(url_parts, secrets)
|
real_url = get_fedi_avatar(url_parts, secrets)
|
||||||
elif url_parts.scheme == "discord":
|
elif url_parts.scheme == "discord":
|
||||||
real_url, extension = get_discord_avatar(url_parts, secrets)
|
real_url = get_discord_avatar(url_parts, secrets)
|
||||||
else:
|
else:
|
||||||
real_url = url
|
real_url = url
|
||||||
extension = url_parts.path.rsplit(".", 1)[1]
|
|
||||||
|
|
||||||
if real_url is None:
|
if real_url is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
img_name = f"cache/{basename}.{extension}"
|
img_file = cache_dir() / basename
|
||||||
|
|
||||||
if os.path.exists(img_name):
|
if img_file.exists():
|
||||||
with open(img_name, "rb") as infile:
|
with img_file.open("rb") as infile:
|
||||||
return infile.read()
|
return infile.read()
|
||||||
result = requests.get(real_url)
|
result = requests.get(real_url)
|
||||||
if result.ok:
|
if not result.ok:
|
||||||
with open(img_name, "wb") as outfile:
|
return None
|
||||||
outfile.write(result.content)
|
with img_file.open("wb") as outfile:
|
||||||
return result.content
|
outfile.write(result.content)
|
||||||
return None
|
return result.content
|
||||||
|
|
||||||
|
|
||||||
def get_avatar(url: str, secrets: dict) -> str:
|
def get_avatar(url: str, secrets: dict[str, str]) -> str | None:
|
||||||
basename = hashlib.sha256(url.encode("utf-8")).hexdigest()
|
basename = hashlib.sha256(url.encode("utf-8")).hexdigest()
|
||||||
if os.path.exists(f"cache/{basename}.svg"):
|
file_path = cache_dir() / f"{basename}.svg"
|
||||||
return f"cache/{basename}.svg"
|
if file_path.exists():
|
||||||
|
return str(file_path)
|
||||||
avatar_raster = get_orig_avatar(url, basename, secrets)
|
avatar_raster = get_orig_avatar(url, basename, secrets)
|
||||||
if avatar_raster is None:
|
if avatar_raster is None:
|
||||||
return ""
|
return None
|
||||||
|
|
||||||
svg_text = f"""<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
svg_text = f"""<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<clipPath id="circle">
|
<clipPath id="circle">
|
||||||
|
@ -104,13 +109,13 @@ def get_avatar(url: str, secrets: dict) -> str:
|
||||||
xlink:href="data:;base64,{base64.b64encode(avatar_raster).decode("utf-8")}" />
|
xlink:href="data:;base64,{base64.b64encode(avatar_raster).decode("utf-8")}" />
|
||||||
</svg>"""
|
</svg>"""
|
||||||
|
|
||||||
with open(f"cache/{basename}.svg", "w") as svgfile:
|
with open(file_path, "w") as svgfile:
|
||||||
svgfile.write(svg_text)
|
svgfile.write(svg_text)
|
||||||
return f"cache/{basename}.svg"
|
return str(file_path)
|
||||||
|
|
||||||
|
|
||||||
def get_country_name(
|
def get_country_name(
|
||||||
root: ET.ElementTree, destination: str, alt=None
|
root: ET.ElementTree, destination: str, alt: str | None = None
|
||||||
) -> typing.Optional[str]:
|
) -> typing.Optional[str]:
|
||||||
elements = root.findall(
|
elements = root.findall(
|
||||||
f"./localeDisplayNames/territories/territory[@type='{destination.upper()}']"
|
f"./localeDisplayNames/territories/territory[@type='{destination.upper()}']"
|
||||||
|
@ -198,16 +203,24 @@ current_serial = imb.get_first_serial()
|
||||||
mid = secrets.get("mailer_id")
|
mid = secrets.get("mailer_id")
|
||||||
|
|
||||||
|
|
||||||
cards = []
|
class Card(TypedDict):
|
||||||
|
address: str
|
||||||
|
avatar: str | None
|
||||||
|
row: dict[str, str]
|
||||||
|
imb: str
|
||||||
|
|
||||||
|
|
||||||
|
cards: list[Card] = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row["Address"] == "":
|
if row["Address"] == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
country = (
|
if row["Country"].lower() == args.origin:
|
||||||
[]
|
country = []
|
||||||
if row["Country"].lower() == args.origin
|
else:
|
||||||
else [get_country_name(cldr_root, row["Country"]).upper()]
|
name = get_country_name(cldr_root, row["Country"])
|
||||||
)
|
assert name is not None
|
||||||
|
country = [name.upper()]
|
||||||
|
|
||||||
address = row["Address"].split("\n") + country
|
address = row["Address"].split("\n") + country
|
||||||
|
|
||||||
|
@ -216,14 +229,13 @@ for row in rows:
|
||||||
else:
|
else:
|
||||||
avatar = None
|
avatar = None
|
||||||
|
|
||||||
cards += [
|
card: Card = {
|
||||||
{
|
"address": "\n".join(address),
|
||||||
"address": "\n".join(address),
|
"avatar": avatar,
|
||||||
"avatar": avatar,
|
"row": row,
|
||||||
"row": row,
|
"imb": "",
|
||||||
"imb": "",
|
}
|
||||||
}
|
cards.append(card)
|
||||||
]
|
|
||||||
|
|
||||||
# Typst can't access files outside the project root, except through a symlink
|
# Typst can't access files outside the project root, except through a symlink
|
||||||
# Create one in cache to use here
|
# Create one in cache to use here
|
||||||
|
@ -258,6 +270,9 @@ with open("options.json", "w") as options:
|
||||||
if args.dont_compile:
|
if args.dont_compile:
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
font_paths = os.getenv("TYPST_FONT_PATHS")
|
||||||
|
assert font_paths is not None
|
||||||
|
|
||||||
os.execlp(
|
os.execlp(
|
||||||
"typst",
|
"typst",
|
||||||
"typst",
|
"typst",
|
||||||
|
@ -265,7 +280,7 @@ os.execlp(
|
||||||
"--font-path",
|
"--font-path",
|
||||||
args.content_path,
|
args.content_path,
|
||||||
"--font-path",
|
"--font-path",
|
||||||
os.getenv("TYPST_FONT_PATHS"),
|
font_paths,
|
||||||
args.template,
|
args.template,
|
||||||
"output.pdf",
|
"output.pdf",
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue