Use pathlib.Path, remove img extensions, fix under strict typing

This commit is contained in:
Skye 2023-12-02 16:29:40 -05:00
parent 72fbcb42d6
commit 3ddd3dec8e
2 changed files with 66 additions and 50 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
result result
.direnv .direnv
.vscode/*
*.pdf *.pdf
addresses.csv addresses.csv

101
format.py
View file

@ -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
with img_file.open("wb") as outfile:
outfile.write(result.content) outfile.write(result.content)
return result.content return result.content
return None
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",
) )