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
|
||||
.direnv
|
||||
.vscode/*
|
||||
|
||||
*.pdf
|
||||
addresses.csv
|
||||
|
|
115
format.py
115
format.py
|
@ -13,10 +13,19 @@ import xml.etree.ElementTree as ET
|
|||
import requests
|
||||
import imb
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
|
||||
# A lot of stuff needs to be in the same directory, just chdir
|
||||
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:
|
||||
if len(s) != 2:
|
||||
raise ValueError("must be 2 characters long")
|
||||
|
@ -27,74 +36,70 @@ def iso_code(s: str) -> str:
|
|||
|
||||
|
||||
def get_discord_avatar(
|
||||
url: urllib.parse.ParseResult, secrets: dict
|
||||
) -> (typing.Optional[str], str):
|
||||
url: urllib.parse.ParseResult, secrets: dict[str, str]
|
||||
) -> typing.Optional[str]:
|
||||
try:
|
||||
uid = url.path
|
||||
token = secrets["discord_token"]
|
||||
user_info = requests.get(
|
||||
f"https://discord.com/api/users/{url.path}",
|
||||
f"https://discord.com/api/users/{uid}",
|
||||
headers={"Authorization": f"Bot {token}"},
|
||||
).json()
|
||||
avatar_hash = user_info["avatar"]
|
||||
return (
|
||||
f"https://cdn.discordapp.com/avatars/{url.path}/{avatar_hash}.png?size=4096",
|
||||
"png",
|
||||
)
|
||||
return f"https://cdn.discordapp.com/avatars/{uid}/{avatar_hash}.png?size=4096"
|
||||
except KeyError:
|
||||
return None, ""
|
||||
return None
|
||||
|
||||
|
||||
def get_fedi_avatar(
|
||||
url: urllib.parse.ParseResult, secrets: dict
|
||||
) -> (typing.Optional[str], str):
|
||||
url: urllib.parse.ParseResult, secrets: dict[str, str]
|
||||
) -> typing.Optional[str]:
|
||||
try:
|
||||
mastodon_api = secrets["mastodon_api"]
|
||||
user_info = requests.get(
|
||||
f"{mastodon_api}/api/v1/accounts/lookup", params={"acct": url.path}
|
||||
).json()
|
||||
avatar_url = user_info["avatar_static"]
|
||||
extension = avatar_url.split(".")[-1]
|
||||
return avatar_url, extension
|
||||
return avatar_url
|
||||
except KeyError:
|
||||
return None, ""
|
||||
return None
|
||||
|
||||
|
||||
def get_orig_avatar(url: str, basename: str, secrets: dict) -> typing.Optional[bytes]:
|
||||
if not os.path.exists("cache"):
|
||||
os.mkdir("cache")
|
||||
|
||||
def get_orig_avatar(
|
||||
url: str, basename: str, secrets: dict[str, str]
|
||||
) -> typing.Optional[bytes]:
|
||||
url_parts = urllib.parse.urlparse(url)
|
||||
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":
|
||||
real_url, extension = get_discord_avatar(url_parts, secrets)
|
||||
real_url = get_discord_avatar(url_parts, secrets)
|
||||
else:
|
||||
real_url = url
|
||||
extension = url_parts.path.rsplit(".", 1)[1]
|
||||
|
||||
if real_url is None:
|
||||
return None
|
||||
|
||||
img_name = f"cache/{basename}.{extension}"
|
||||
img_file = cache_dir() / basename
|
||||
|
||||
if os.path.exists(img_name):
|
||||
with open(img_name, "rb") as infile:
|
||||
if img_file.exists():
|
||||
with img_file.open("rb") as infile:
|
||||
return infile.read()
|
||||
result = requests.get(real_url)
|
||||
if result.ok:
|
||||
with open(img_name, "wb") as outfile:
|
||||
outfile.write(result.content)
|
||||
return result.content
|
||||
return None
|
||||
if not result.ok:
|
||||
return None
|
||||
with img_file.open("wb") as outfile:
|
||||
outfile.write(result.content)
|
||||
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()
|
||||
if os.path.exists(f"cache/{basename}.svg"):
|
||||
return f"cache/{basename}.svg"
|
||||
file_path = cache_dir() / f"{basename}.svg"
|
||||
if file_path.exists():
|
||||
return str(file_path)
|
||||
avatar_raster = get_orig_avatar(url, basename, secrets)
|
||||
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">
|
||||
<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")}" />
|
||||
</svg>"""
|
||||
|
||||
with open(f"cache/{basename}.svg", "w") as svgfile:
|
||||
with open(file_path, "w") as svgfile:
|
||||
svgfile.write(svg_text)
|
||||
return f"cache/{basename}.svg"
|
||||
return str(file_path)
|
||||
|
||||
|
||||
def get_country_name(
|
||||
root: ET.ElementTree, destination: str, alt=None
|
||||
root: ET.ElementTree, destination: str, alt: str | None = None
|
||||
) -> typing.Optional[str]:
|
||||
elements = root.findall(
|
||||
f"./localeDisplayNames/territories/territory[@type='{destination.upper()}']"
|
||||
|
@ -198,16 +203,24 @@ current_serial = imb.get_first_serial()
|
|||
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:
|
||||
if row["Address"] == "":
|
||||
continue
|
||||
|
||||
country = (
|
||||
[]
|
||||
if row["Country"].lower() == args.origin
|
||||
else [get_country_name(cldr_root, row["Country"]).upper()]
|
||||
)
|
||||
if row["Country"].lower() == args.origin:
|
||||
country = []
|
||||
else:
|
||||
name = get_country_name(cldr_root, row["Country"])
|
||||
assert name is not None
|
||||
country = [name.upper()]
|
||||
|
||||
address = row["Address"].split("\n") + country
|
||||
|
||||
|
@ -216,14 +229,13 @@ for row in rows:
|
|||
else:
|
||||
avatar = None
|
||||
|
||||
cards += [
|
||||
{
|
||||
"address": "\n".join(address),
|
||||
"avatar": avatar,
|
||||
"row": row,
|
||||
"imb": "",
|
||||
}
|
||||
]
|
||||
card: Card = {
|
||||
"address": "\n".join(address),
|
||||
"avatar": avatar,
|
||||
"row": row,
|
||||
"imb": "",
|
||||
}
|
||||
cards.append(card)
|
||||
|
||||
# Typst can't access files outside the project root, except through a symlink
|
||||
# Create one in cache to use here
|
||||
|
@ -258,6 +270,9 @@ with open("options.json", "w") as options:
|
|||
if args.dont_compile:
|
||||
exit()
|
||||
|
||||
font_paths = os.getenv("TYPST_FONT_PATHS")
|
||||
assert font_paths is not None
|
||||
|
||||
os.execlp(
|
||||
"typst",
|
||||
"typst",
|
||||
|
@ -265,7 +280,7 @@ os.execlp(
|
|||
"--font-path",
|
||||
args.content_path,
|
||||
"--font-path",
|
||||
os.getenv("TYPST_FONT_PATHS"),
|
||||
font_paths,
|
||||
args.template,
|
||||
"output.pdf",
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue