aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sjdbmk/__init__.py0
-rw-r--r--sjdbmk/__main__.py90
-rw-r--r--sjdbmk/const.py30
-rw-r--r--sjdbmk/daily.py45
-rw-r--r--sjdbmk/grant.py12
-rw-r--r--sjdbmk/inspire_dl.py17
-rw-r--r--sjdbmk/legacy_wikipedia.py63
-rw-r--r--sjdbmk/menuparser.py20
-rw-r--r--sjdbmk/pack.py23
-rw-r--r--sjdbmk/sendmail.py55
-rw-r--r--sjdbmk/sendmail2.py22
-rw-r--r--sjdbmk/serve.py6
-rw-r--r--sjdbmk/weekly.py127
13 files changed, 266 insertions, 244 deletions
diff --git a/sjdbmk/__init__.py b/sjdbmk/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sjdbmk/__init__.py
diff --git a/sjdbmk/__main__.py b/sjdbmk/__main__.py
new file mode 100644
index 0000000..64e9d0f
--- /dev/null
+++ b/sjdbmk/__main__.py
@@ -0,0 +1,90 @@
+import argparse
+import datetime
+import logging
+import os
+import zoneinfo
+from configparser import ConfigParser
+
+from .const import *
+
+
+logger = logging.getLogger(__name__)
+
+
+def main() -> None:
+ logging.basicConfig(level=logging.INFO)
+
+ parser = argparse.ArgumentParser(
+ prog="sjdbmk",
+ description="Songjiang Daily Bulletin Build System",
+ )
+ parser.add_argument(
+ "--config",
+ default="config.ini", # TODO
+ help="path to the configuration file",
+ )
+ parser.add_argument(
+ "--rebuild",
+ action="store_true",
+ default=False,
+ help="rebuild from scratch",
+ )
+ parser.add_argument(
+ "--date",
+ default="",
+ help="build for date",
+ )
+ args = parser.parse_args()
+
+ config = ConfigParser()
+ config.read(args.config)
+
+ tzinfo = zoneinfo.ZoneInfo(config["general"]["timezone"])
+ if args.date:
+ datetime_target_naive = datetime.datetime.strptime(args.date, "%Y-%m-%d")
+ datetime_target = datetime_target_naive.replace(tzinfo=tzinfo)
+ else:
+ datetime_current_aware = datetime.datetime.now(tz=tzinfo)
+ datetime_target = datetime_current_aware + datetime.timedelta(days=((-datetime_current_aware.weekday()) % 7))
+
+ logger.info("Building for %s" % datetime_target.strftime("%Y-%m-%d %Z"))
+
+ build_path = config["general"]["build_path"]
+ os.chdir(build_path)
+
+ the_week_ahead_url = config["the_week_ahead"]["file_url"]
+ the_week_ahead_community_time_page_number = int(config["the_week_ahead"]["community_time_page_number"])
+ the_week_ahead_aod_page_number = int(config["the_week_ahead"]["aod_page_number"])
+
+ weekly_menu_query_string = config["weekly_menu"]["query_string"]
+ weekly_menu_sender = config["weekly_menu"]["sender"]
+ weekly_menu_subject_regex = config["weekly_menu"]["subject_regex"]
+ weekly_menu_subject_regex_four_groups_raw = config["weekly_menu"]["subject_regex_four_groups"].split(" ")
+ weekly_menu_subject_regex_four_groups = tuple([int(z) for z in weekly_menu_subject_regex_four_groups_raw])
+ assert len(weekly_menu_subject_regex_four_groups) == 4
+ del weekly_menu_subject_regex_four_groups_raw
+
+ graph_client_id = config["credentials"]["client_id"]
+ graph_authority = config["credentials"]["authority"]
+ graph_username = config["credentials"]["username"]
+ graph_password = config["credentials"]["password"]
+ graph_scopes = config["credentials"]["scope"].split(" ")
+
+ calendar_address = config["calendar"]["address"]
+
+ generate(
+ datetime_target=datetime_target,
+ the_week_ahead_url=the_week_ahead_url,
+ the_week_ahead_community_time_page_number=the_week_ahead_community_time_page_number,
+ the_week_ahead_aod_page_number=the_week_ahead_aod_page_number,
+ weekly_menu_query_string=weekly_menu_query_string,
+ weekly_menu_sender=weekly_menu_sender,
+ weekly_menu_subject_regex=weekly_menu_subject_regex,
+ weekly_menu_subject_regex_four_groups=weekly_menu_subject_regex_four_groups,
+ graph_client_id=graph_client_id,
+ graph_authority=graph_authority,
+ graph_username=graph_username,
+ graph_password=graph_password,
+ graph_scopes=graph_scopes,
+ calendar_address=calendar_address,
+ )
diff --git a/sjdbmk/const.py b/sjdbmk/const.py
new file mode 100644
index 0000000..0963ed7
--- /dev/null
+++ b/sjdbmk/const.py
@@ -0,0 +1,30 @@
+DAYNAMES = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ "Monday",
+]
+DAYNAMES_CHINESE = [
+ "周一",
+ "周二",
+ "周三",
+ "周四",
+ "周五",
+ "周六",
+ "周日",
+ "周一",
+]
+DAYNAMES_SHORT = [
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun",
+ "Mon",
+]
diff --git a/sjdbmk/daily.py b/sjdbmk/daily.py
index ce21bce..f141743 100644
--- a/sjdbmk/daily.py
+++ b/sjdbmk/daily.py
@@ -43,8 +43,26 @@ DAYNAMES = [
"Sunday",
"Monday",
]
-DAYNAMES_CHINESE = ["周一", "周二", "周三", "周四", "周五", "周六", "周日", "周一"]
-DAYNAMES_SHORT = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"]
+DAYNAMES_CHINESE = [
+ "周一",
+ "周二",
+ "周三",
+ "周四",
+ "周五",
+ "周六",
+ "周日",
+ "周一",
+]
+DAYNAMES_SHORT = [
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun",
+ "Mon",
+]
def main() -> None:
@@ -58,7 +76,9 @@ def main() -> None:
# TODO: Verify consistency of date elsewhere
)
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
@@ -114,9 +134,7 @@ def generate(
logger.warning('Cycle day not found, using "SA"')
for days_since_beginning in range(0, 5):
- week_start_date = datetime_target - datetime.timedelta(
- days=days_since_beginning
- )
+ week_start_date = datetime_target - datetime.timedelta(days=days_since_beginning)
try:
with open(
"week-%s.json" % week_start_date.strftime("%Y%m%d"),
@@ -129,9 +147,7 @@ def generate(
else:
break
else:
- raise FileNotFoundError(
- "Cannot find a week-{date}.json file without five prior days"
- )
+ raise FileNotFoundError("Cannot find a week-{date}.json file without five prior days")
try:
aod = week_data["aods"][days_since_beginning]
@@ -182,12 +198,11 @@ def generate(
inspiration_image_fn = inspjq["file"]
if inspiration_image_fn:
logger.info("Inspiration has attachment %s" % inspiration_image_fn)
- inspiration_image_mime, inspiration_image_extra_encoding = (
- mimetypes.guess_type(inspiration_image_fn)
- )
+ inspiration_image_mime, inspiration_image_extra_encoding = mimetypes.guess_type(inspiration_image_fn)
assert not inspiration_image_extra_encoding
with open(
- "inspattach-%s" % os.path.basename(inspiration_image_fn), "rb"
+ "inspattach-%s" % os.path.basename(inspiration_image_fn),
+ "rb",
) as ifd:
inspiration_image_data = base64.b64encode(ifd.read()).decode("ascii")
else:
@@ -258,7 +273,9 @@ def generate(
"in_the_news_html_zh": in_the_news_html_zh,
}
with open(
- "day-%s.json" % datetime_target.strftime("%Y%m%d"), "w", encoding="utf-8"
+ "day-%s.json" % datetime_target.strftime("%Y%m%d"),
+ "w",
+ encoding="utf-8",
) as fd:
json.dump(data, fd, ensure_ascii=False, indent="\t")
logger.info(
diff --git a/sjdbmk/grant.py b/sjdbmk/grant.py
index d55d9d6..08d1036 100644
--- a/sjdbmk/grant.py
+++ b/sjdbmk/grant.py
@@ -28,9 +28,7 @@ import msal # type: ignore
# logging.getLogger("msal").setLevel(logging.INFO)
-def acquire_token_interactive(
- app: msal.PublicClientApplication, config: ConfigParser
-) -> str:
+def acquire_token_interactive(app: msal.PublicClientApplication, config: ConfigParser) -> str:
result = app.acquire_token_interactive(
config["credentials"]["scope"].split(" "),
login_hint=config["credentials"]["username"],
@@ -39,14 +37,10 @@ def acquire_token_interactive(
if "access_token" in result:
assert isinstance(result["access_token"], str)
return result["access_token"]
- raise ValueError(
- "Authentication error while trying to interactively acquire a token"
- )
+ raise ValueError("Authentication error while trying to interactively acquire a token")
-def test_login(
- app: msal.PublicClientApplication, config: ConfigParser
-) -> dict[str, Any]:
+def test_login(app: msal.PublicClientApplication, config: ConfigParser) -> dict[str, Any]:
result = app.acquire_token_by_username_password(
config["credentials"]["username"],
config["credentials"]["password"],
diff --git a/sjdbmk/inspire_dl.py b/sjdbmk/inspire_dl.py
index 631ea44..4eddc24 100644
--- a/sjdbmk/inspire_dl.py
+++ b/sjdbmk/inspire_dl.py
@@ -36,7 +36,9 @@ def main() -> None:
parser = argparse.ArgumentParser(description="Download Daily Inspirations")
# parser.add_argument("--changeme", default=None, help="changeme")
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
@@ -57,9 +59,7 @@ def main() -> None:
assert isinstance(response_json, list)
remote_submission_list = set(response_json)
- local_submission_list = set(
- [sn.lstrip("inspire-") for sn in os.listdir() if sn.startswith("inspire-")]
- )
+ local_submission_list = set([sn.lstrip("inspire-") for sn in os.listdir() if sn.startswith("inspire-")])
to_fetch = remote_submission_list - local_submission_list
if to_fetch:
logger.info("Going to fetch: %s" % ", ".join(to_fetch))
@@ -97,10 +97,11 @@ def main() -> None:
stream=True,
timeout=20,
) as r:
- with open("inspattach-%s" % os.path.basename(sub["file"]), "wb") as fd:
- logger.info(
- "Saved to inspattach-%s" % os.path.basename(sub["file"])
- )
+ with open(
+ "inspattach-%s" % os.path.basename(sub["file"]),
+ "wb",
+ ) as fd:
+ logger.info("Saved to inspattach-%s" % os.path.basename(sub["file"]))
shutil.copyfileobj(r.raw, fd)
fd.flush()
diff --git a/sjdbmk/legacy_wikipedia.py b/sjdbmk/legacy_wikipedia.py
index c2f60a1..0451c8a 100644
--- a/sjdbmk/legacy_wikipedia.py
+++ b/sjdbmk/legacy_wikipedia.py
@@ -62,21 +62,7 @@ def get_on_this_day_zh() -> None:
li_element.append(event)
ul_element.append(li_element)
- result = str(p_element).replace(
- "/wiki", "https://zh.wikipedia.org/zh-cn"
- ).replace('<span class="otd-year">', "<b>").replace(
- "</span>:", ":</b>"
- ) + str(
- ul_element
- ).replace(
- "/wiki", "https://zh.wikipedia.org/zh-cn"
- ).replace(
- "</dt><dd>", " – "
- ).replace(
- '<div class="event">\n<dt>', ""
- ).replace(
- "</dd>\n</div>", ""
- )
+ result = str(p_element).replace("/wiki", "https://zh.wikipedia.org/zh-cn").replace('<span class="otd-year">', "<b>").replace("</span>:", ":</b>") + str(ul_element).replace("/wiki", "https://zh.wikipedia.org/zh-cn").replace("</dt><dd>", " – ").replace('<div class="event">\n<dt>', "").replace("</dd>\n</div>", "")
result = re.sub(r"<small>.*?图.*?</small>", "", result)
with open("otd_zh-" + formatted_time_yearless + ".html", "w") as file:
@@ -105,9 +91,7 @@ def get_on_this_day_en() -> None:
month = months[index]
day = 1
- url = (
- "https://en.m.wikipedia.org/wiki/Wikipedia:Selected_anniversaries/" + month
- )
+ url = "https://en.m.wikipedia.org/wiki/Wikipedia:Selected_anniversaries/" + month
response = requests.get(url, timeout=15)
html = response.text
soup = bs4.BeautifulSoup(html, "html.parser")
@@ -135,16 +119,7 @@ def get_on_this_day_en() -> None:
for li in li_contents:
p_element_2.append(li)
- result = (
- str(p_element).replace("/wiki", "https://en.wikipedia.org/wiki")
- + str(ul_element).replace("/wiki", "https://en.wikipedia.org/wiki")
- + "\n"
- + str(p_element_2)
- .replace("</li><li>", "; ")
- .replace("<li>", "<b>Births and Deaths: </b>")
- .replace("</li>", "")
- .replace("/wiki", "https://en.wikipedia.org/wiki")
- )
+ result = str(p_element).replace("/wiki", "https://en.wikipedia.org/wiki") + str(ul_element).replace("/wiki", "https://en.wikipedia.org/wiki") + "\n" + str(p_element_2).replace("</li><li>", "; ").replace("<li>", "<b>Births and Deaths: </b>").replace("</li>", "").replace("/wiki", "https://en.wikipedia.org/wiki")
result = re.sub(r" <i>.*?icture.*?</i>", "", result)
with open("otd_en-" + formatted_time_yearless + ".html", "w") as file:
@@ -200,23 +175,7 @@ def get_in_the_news_en() -> str:
else:
p_element_3.append(li)
- result = (
- str(ul_element).replace("/wiki", "https://en.wikipedia.org/wiki")
- + str(p_element_2)
- .replace("</li><li>", "; ")
- .replace("<li>", "<b>Ongoing: </b>")
- .replace("</li>", "")
- .replace("\n;", ";")
- .replace("/wiki", "https://en.wikipedia.org/wiki")
- .replace("</p>", "<br>")
- + str(p_element_3)
- .replace("</li><li>", "; ")
- .replace("<li>", "<b>Recent deaths: </b>")
- .replace("</li>", "")
- .replace("\n;", ";")
- .replace("/wiki", "https://en.wikipedia.org/wiki")
- .replace("<p>", "")
- )
+ result = str(ul_element).replace("/wiki", "https://en.wikipedia.org/wiki") + str(p_element_2).replace("</li><li>", "; ").replace("<li>", "<b>Ongoing: </b>").replace("</li>", "").replace("\n;", ";").replace("/wiki", "https://en.wikipedia.org/wiki").replace("</p>", "<br>") + str(p_element_3).replace("</li><li>", "; ").replace("<li>", "<b>Recent deaths: </b>").replace("</li>", "").replace("\n;", ";").replace("/wiki", "https://en.wikipedia.org/wiki").replace("<p>", "")
result = re.sub(r" <i>\(.*?\)</i>", "", result)
return result
@@ -255,11 +214,7 @@ def get_in_the_news_zh() -> str:
"",
)
.replace("/wiki", "https://zh.wikipedia.org/zh-cn")
- + str(p_element_3)
- .replace('<span class="hlist inline">', "<b>最近逝世:</b>")
- .replace("</span>", "")
- .replace("-", ";")
- .replace("/wiki", "https://zh.wikipedia.org/zh-cn")
+ + str(p_element_3).replace('<span class="hlist inline">', "<b>最近逝世:</b>").replace("</span>", "").replace("-", ";").replace("/wiki", "https://zh.wikipedia.org/zh-cn")
).replace("</p><p>", "<br>")
result = re.sub(r"<small.*?>.*?</small>", "", result)
@@ -267,11 +222,11 @@ def get_in_the_news_zh() -> str:
def main() -> None:
- parser = argparse.ArgumentParser(
- description="Legacy Wikipedia script for the Daily Bulletin"
- )
+ parser = argparse.ArgumentParser(description="Legacy Wikipedia script for the Daily Bulletin")
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
diff --git a/sjdbmk/menuparser.py b/sjdbmk/menuparser.py
index 7413ff0..998272b 100644
--- a/sjdbmk/menuparser.py
+++ b/sjdbmk/menuparser.py
@@ -28,18 +28,10 @@ def menu_item_fix(s: str) -> Optional[str]:
return None
if s == "Condiments Selection\n葱,香菜,榨菜丝,老干妈,生抽,醋":
return None
- return (
- s.strip()
- .replace("Biscuit /", "Biscuit/")
- .replace("Juice /", "Juice/")
- .replace(" \n", "\n")
- .replace("\n ", "\n")
- )
-
-
-def parse_meal_table(
- rows: list[Any], initrow: int, t: list[str]
-) -> dict[str, dict[str, list[str]]]:
+ return s.strip().replace("Biscuit /", "Biscuit/").replace("Juice /", "Juice/").replace(" \n", "\n").replace("\n ", "\n")
+
+
+def parse_meal_table(rows: list[Any], initrow: int, t: list[str]) -> dict[str, dict[str, list[str]]]:
assert rows[initrow + 1][1].value is None
igroups = []
@@ -76,7 +68,9 @@ def parse_meal_table(
return ret
-def parse_menus(filename: str) -> dict[str, dict[str, dict[str, list[str]]]]:
+def parse_menus(
+ filename: str,
+) -> dict[str, dict[str, dict[str, list[str]]]]:
wb = openpyxl.load_workbook(filename=filename)
ws = wb["菜单"]
rows = list(ws.iter_rows())
diff --git a/sjdbmk/pack.py b/sjdbmk/pack.py
index 902e256..78c6297 100644
--- a/sjdbmk/pack.py
+++ b/sjdbmk/pack.py
@@ -30,17 +30,23 @@ from jinja2 import Template, StrictUndefined
def main(date: str, config: ConfigParser) -> None:
with open(
- os.path.join(config["templates"]["directory"], config["templates"]["main"]),
+ os.path.join(
+ config["templates"]["directory"],
+ config["templates"]["main"],
+ ),
"r",
encoding="utf-8",
) as template_file:
template = Template(
- template_file.read(), undefined=StrictUndefined, autoescape=True
+ template_file.read(),
+ undefined=StrictUndefined,
+ autoescape=True,
)
with open(
os.path.join(
- config["general"]["build_path"], "day-" + date.replace("-", "") + ".json"
+ config["general"]["build_path"],
+ "day-" + date.replace("-", "") + ".json",
),
"r",
encoding="utf-8",
@@ -54,7 +60,8 @@ def main(date: str, config: ConfigParser) -> None:
template.stream(**data).dump(
os.path.join(
- config["general"]["build_path"], "sjdb-%s.html" % date.replace("-", "")
+ config["general"]["build_path"],
+ "sjdb-%s.html" % date.replace("-", ""),
)
)
@@ -73,7 +80,9 @@ if __name__ == "__main__":
# TODO: Verify consistency of date elsewhere
)
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
config = ConfigParser()
@@ -81,9 +90,7 @@ if __name__ == "__main__":
if args.date:
date = args.date
else:
- now = datetime.datetime.now(
- zoneinfo.ZoneInfo(config["general"]["timezone"])
- )
+ now = datetime.datetime.now(zoneinfo.ZoneInfo(config["general"]["timezone"]))
date = (now + datetime.timedelta(days=1)).strftime("%Y-%m-%d")
logging.info("Generating for day %s" % date)
# main(date, config)
diff --git a/sjdbmk/sendmail.py b/sjdbmk/sendmail.py
index ddd6f32..363efe9 100644
--- a/sjdbmk/sendmail.py
+++ b/sjdbmk/sendmail.py
@@ -74,9 +74,7 @@ def sendmail(
raise TypeError("Naive datetimes are no longer supported")
utcwhen = when.astimezone(datetime.timezone.utc)
isoval = utcwhen.isoformat(timespec="seconds").replace("+00:00", "Z")
- data["singleValueExtendedProperties"] = [
- {"id": "SystemTime 0x3FEF", "value": isoval}
- ]
+ data["singleValueExtendedProperties"] = [{"id": "SystemTime 0x3FEF", "value": isoval}]
if not reply_to:
response = requests.post(
@@ -116,8 +114,7 @@ def sendmail(
if response2.status_code != 202:
pprint(response2.content.decode("utf-8", "replace"))
raise ValueError(
- "Graph response to messages/%s/send returned something other than 202 Accepted"
- % response["id"],
+ "Graph response to messages/%s/send returned something other than 202 Accepted" % response["id"],
)
return msgid
@@ -138,19 +135,17 @@ def main() -> None:
help="Reply to the previous bulletin when sending (BROKEN)",
)
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
config = ConfigParser()
config.read(args.config)
if args.date:
- date = datetime.datetime.strptime(args.date, "%Y-%m-%d").replace(
- tzinfo=zoneinfo.ZoneInfo(config["general"]["timezone"])
- )
+ date = datetime.datetime.strptime(args.date, "%Y-%m-%d").replace(tzinfo=zoneinfo.ZoneInfo(config["general"]["timezone"]))
else:
- date = datetime.datetime.now(
- zoneinfo.ZoneInfo(config["general"]["timezone"])
- ) + datetime.timedelta(days=1)
+ date = datetime.datetime.now(zoneinfo.ZoneInfo(config["general"]["timezone"])) + datetime.timedelta(days=1)
os.chdir(config["general"]["build_path"])
@@ -167,16 +162,11 @@ def main() -> None:
if not args.reply:
a = sendmail(
token,
- subject=config["sendmail"]["subject_format"]
- % date.strftime(config["sendmail"]["subject_date_format"]),
+ subject=config["sendmail"]["subject_format"] % date.strftime(config["sendmail"]["subject_date_format"]),
body=html,
to=config["sendmail"]["to_1"].split(" "),
cc=config["sendmail"]["cc_1"].split(" "),
- bcc=[
- w.strip()
- for w in open_and_readlines(config["sendmail"]["bcc_1_file"])
- if w.strip()
- ],
+ bcc=[w.strip() for w in open_and_readlines(config["sendmail"]["bcc_1_file"]) if w.strip()],
when=date.replace(
hour=int(config["sendmail"]["hour"]),
minute=int(config["sendmail"]["minute"]),
@@ -191,16 +181,11 @@ def main() -> None:
fd.write(a)
b = sendmail(
token,
- subject=config["sendmail"]["subject_format"]
- % date.strftime(config["sendmail"]["subject_date_format"]),
+ subject=config["sendmail"]["subject_format"] % date.strftime(config["sendmail"]["subject_date_format"]),
body=html,
to=config["sendmail"]["to_2"].split(" "),
cc=config["sendmail"]["cc_2"].split(" "),
- bcc=[
- w.strip()
- for w in open_and_readlines(config["sendmail"]["bcc_2_file"])
- if w.strip()
- ],
+ bcc=[w.strip() for w in open_and_readlines(config["sendmail"]["bcc_2_file"]) if w.strip()],
when=date.replace(
hour=int(config["sendmail"]["hour"]),
minute=int(config["sendmail"]["minute"]),
@@ -218,16 +203,11 @@ def main() -> None:
last_a = fd.read().strip()
a = sendmail(
token,
- subject=config["sendmail"]["subject_format"]
- % date.strftime(config["sendmail"]["subject_date_format"]),
+ subject=config["sendmail"]["subject_format"] % date.strftime(config["sendmail"]["subject_date_format"]),
body=html,
to=config["sendmail"]["to_1"].split(" "),
cc=config["sendmail"]["cc_1"].split(" "),
- bcc=[
- w.strip()
- for w in open_and_readlines(config["sendmail"]["bcc_1_file"])
- if w.strip()
- ],
+ bcc=[w.strip() for w in open_and_readlines(config["sendmail"]["bcc_1_file"]) if w.strip()],
when=date.replace(
hour=int(config["sendmail"]["hour"]),
minute=int(config["sendmail"]["minute"]),
@@ -245,16 +225,11 @@ def main() -> None:
last_b = fd.read().strip()
b = sendmail(
token,
- subject=config["sendmail"]["subject_format"]
- % date.strftime(config["sendmail"]["subject_date_format"]),
+ subject=config["sendmail"]["subject_format"] % date.strftime(config["sendmail"]["subject_date_format"]),
body=html,
to=config["sendmail"]["to_2"].split(" "),
cc=config["sendmail"]["cc_2"].split(" "),
- bcc=[
- w.strip()
- for w in open_and_readlines(config["sendmail"]["bcc_2_file"])
- if w.strip()
- ],
+ bcc=[w.strip() for w in open_and_readlines(config["sendmail"]["bcc_2_file"]) if w.strip()],
when=date.replace(
hour=int(config["sendmail"]["hour"]),
minute=int(config["sendmail"]["minute"]),
diff --git a/sjdbmk/sendmail2.py b/sjdbmk/sendmail2.py
index 6cfda8c..0f725e5 100644
--- a/sjdbmk/sendmail2.py
+++ b/sjdbmk/sendmail2.py
@@ -68,9 +68,7 @@ def sendmail(
raise TypeError("Naive datetimes are no longer supported")
utcwhen = when.astimezone(datetime.timezone.utc)
isoval = utcwhen.isoformat(timespec="seconds").replace("+00:00", "Z")
- data["singleValueExtendedProperties"] = [
- {"id": "SystemTime 0x3FEF", "value": isoval}
- ]
+ data["singleValueExtendedProperties"] = [{"id": "SystemTime 0x3FEF", "value": isoval}]
response = requests.post(
"https://graph.microsoft.com/v1.0/me/messages",
@@ -86,8 +84,7 @@ def sendmail(
if response2.status_code != 202:
print(response2.content)
raise ValueError(
- "Graph response to messages/%s/send returned something other than 202 Accepted"
- % response["id"],
+ "Graph response to messages/%s/send returned something other than 202 Accepted" % response["id"],
response2,
)
# TODO: Handle more errors
@@ -101,19 +98,17 @@ def main() -> None:
help="the date of the bulletin to send, in local time, in YYYY-MM-DD; defaults to tomorrow",
)
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
config = ConfigParser()
config.read(args.config)
if args.date:
- date = datetime.datetime.strptime(args.date, "%Y-%m-%d").replace(
- tzinfo=zoneinfo.ZoneInfo(config["general"]["timezone"])
- )
+ date = datetime.datetime.strptime(args.date, "%Y-%m-%d").replace(tzinfo=zoneinfo.ZoneInfo(config["general"]["timezone"]))
else:
- date = datetime.datetime.now(
- zoneinfo.ZoneInfo(config["general"]["timezone"])
- ) + datetime.timedelta(days=1)
+ date = datetime.datetime.now(zoneinfo.ZoneInfo(config["general"]["timezone"])) + datetime.timedelta(days=1)
os.chdir(config["general"]["build_path"])
@@ -136,8 +131,7 @@ def main() -> None:
),
"content_type": "HTML",
"importance": "Normal",
- "subject": config["sendmail"]["subject_format"]
- % date.strftime(config["sendmail"]["subject_date_format"]),
+ "subject": config["sendmail"]["subject_format"] % date.strftime(config["sendmail"]["subject_date_format"]),
"body": html,
}
diff --git a/sjdbmk/serve.py b/sjdbmk/serve.py
index 492e443..411a5b2 100644
--- a/sjdbmk/serve.py
+++ b/sjdbmk/serve.py
@@ -65,11 +65,7 @@ def index() -> ResponseType:
with open(
os.path.join(
config["general"]["build_path"],
- "day-%s.json"
- % (
- datetime.datetime.now(tz=zoneinfo.ZoneInfo("Asia/Shanghai"))
- + datetime.timedelta(days=1)
- ).strftime("%Y%m%d"),
+ "day-%s.json" % (datetime.datetime.now(tz=zoneinfo.ZoneInfo("Asia/Shanghai")) + datetime.timedelta(days=1)).strftime("%Y%m%d"),
),
"r",
encoding="utf-8",
diff --git a/sjdbmk/weekly.py b/sjdbmk/weekly.py
index 9c0a0c9..80dc0a3 100644
--- a/sjdbmk/weekly.py
+++ b/sjdbmk/weekly.py
@@ -81,36 +81,33 @@ def generate(
logger.info("Output filename: %s" % output_filename)
token: str = acquire_token(
- graph_client_id, graph_authority, graph_username, graph_password, graph_scopes
+ graph_client_id,
+ graph_authority,
+ graph_username,
+ graph_password,
+ graph_scopes,
)
calendar_response = requests.get(
- "https://graph.microsoft.com/v1.0/users/%s/calendar/calendarView"
- % calendar_address,
+ "https://graph.microsoft.com/v1.0/users/%s/calendar/calendarView" % calendar_address,
headers={"Authorization": "Bearer " + token},
params={
"startDateTime": datetime_target.replace(microsecond=0).isoformat(),
- "endDateTime": (datetime_target + datetime.timedelta(days=7))
- .replace(microsecond=0)
- .isoformat(),
+ "endDateTime": (datetime_target + datetime.timedelta(days=7)).replace(microsecond=0).isoformat(),
},
timeout=15,
)
if calendar_response.status_code != 200:
raise ValueError(
- "Calendar response status code is not 200", calendar_response.content
+ "Calendar response status code is not 200",
+ calendar_response.content,
)
calendar_object = calendar_response.json()
# pprint(calendar_object)
- the_week_ahead_filename = "the_week_ahead-%s.pptx" % datetime_target.strftime(
- "%Y%m%d"
- )
+ the_week_ahead_filename = "the_week_ahead-%s.pptx" % datetime_target.strftime("%Y%m%d")
if not os.path.isfile(the_week_ahead_filename):
- logger.info(
- "The Week Ahead doesn't seem to exist at %s, downloading"
- % the_week_ahead_filename
- )
+ logger.info("The Week Ahead doesn't seem to exist at %s, downloading" % the_week_ahead_filename)
download_share_url(token, the_week_ahead_url, the_week_ahead_filename)
logger.info("Downloaded The Week Ahead to %s" % the_week_ahead_filename)
assert os.path.isfile(the_week_ahead_filename)
@@ -141,9 +138,7 @@ def generate(
the_week_ahead_community_time_page_number,
)
except ValueError:
- logger.error(
- "Invalid community time! Opening The Week Ahead for manual intervention."
- )
+ logger.error("Invalid community time! Opening The Week Ahead for manual intervention.")
del the_week_ahead_presentation
subprocess.run([soffice, the_week_ahead_filename], check=True)
the_week_ahead_presentation = pptx.Presentation(the_week_ahead_filename)
@@ -186,7 +181,9 @@ def main() -> None:
help="the start of the week to generate for, in local time, YYYY-MM-DD; defaults to next Monday",
)
parser.add_argument(
- "--config", default="config.ini", help="path to the configuration file"
+ "--config",
+ default="config.ini",
+ help="path to the configuration file",
)
args = parser.parse_args()
@@ -204,9 +201,7 @@ def main() -> None:
datetime_target_aware = datetime_target_naive.replace(tzinfo=tzinfo)
else:
datetime_current_aware = datetime.datetime.now(tz=tzinfo)
- datetime_target_aware = datetime_current_aware + datetime.timedelta(
- days=((-datetime_current_aware.weekday()) % 7)
- )
+ datetime_target_aware = datetime_current_aware + datetime.timedelta(days=((-datetime_current_aware.weekday()) % 7))
del datetime_current_aware
del datetime_target_naive
logger.info("Generating for %s" % datetime_target_aware.strftime("%Y-%m-%d %Z"))
@@ -216,25 +211,17 @@ def main() -> None:
os.chdir(build_path)
the_week_ahead_url = config["the_week_ahead"]["file_url"]
- the_week_ahead_community_time_page_number = int(
- config["the_week_ahead"]["community_time_page_number"]
- )
+ the_week_ahead_community_time_page_number = int(config["the_week_ahead"]["community_time_page_number"])
the_week_ahead_aod_page_number = int(config["the_week_ahead"]["aod_page_number"])
- weekly_menu_breakfast_page_number = int(
- config["weekly_menu"]["breakfast_page_number"]
- )
+ weekly_menu_breakfast_page_number = int(config["weekly_menu"]["breakfast_page_number"])
weekly_menu_lunch_page_number = int(config["weekly_menu"]["lunch_page_number"])
weekly_menu_dinner_page_number = int(config["weekly_menu"]["dinner_page_number"])
weekly_menu_query_string = config["weekly_menu"]["query_string"]
weekly_menu_sender = config["weekly_menu"]["sender"]
weekly_menu_subject_regex = config["weekly_menu"]["subject_regex"]
- weekly_menu_subject_regex_four_groups_raw = config["weekly_menu"][
- "subject_regex_four_groups"
- ].split(" ")
- weekly_menu_subject_regex_four_groups = tuple(
- [int(z) for z in weekly_menu_subject_regex_four_groups_raw]
- )
+ weekly_menu_subject_regex_four_groups_raw = config["weekly_menu"]["subject_regex_four_groups"].split(" ")
+ weekly_menu_subject_regex_four_groups = tuple([int(z) for z in weekly_menu_subject_regex_four_groups_raw])
assert len(weekly_menu_subject_regex_four_groups) == 4
del weekly_menu_subject_regex_four_groups_raw
# weekly_menu_dessert_page_number = config["weekly_menu"]["dessert_page_number"]
@@ -276,18 +263,13 @@ def main() -> None:
def encode_sharing_url(url: str) -> str:
- return "u!" + base64.urlsafe_b64encode(url.encode("utf-8")).decode("ascii").rstrip(
- "="
- )
+ return "u!" + base64.urlsafe_b64encode(url.encode("utf-8")).decode("ascii").rstrip("=")
-def download_share_url(
- token: str, url: str, local_filename: str, chunk_size: int = 65536
-) -> None:
+def download_share_url(token: str, url: str, local_filename: str, chunk_size: int = 65536) -> None:
download_direct_url = requests.get(
- "https://graph.microsoft.com/v1.0/shares/%s/driveItem"
- % encode_sharing_url(url),
+ "https://graph.microsoft.com/v1.0/shares/%s/driveItem" % encode_sharing_url(url),
headers={"Authorization": "Bearer " + token},
timeout=20,
).json()["@microsoft.graph.downloadUrl"]
@@ -317,9 +299,7 @@ def acquire_token(
graph_client_id,
authority=graph_authority,
)
- result = app.acquire_token_by_username_password(
- graph_username, graph_password, scopes=graph_scopes
- )
+ result = app.acquire_token_by_username_password(graph_username, graph_password, scopes=graph_scopes)
if "access_token" in result:
assert isinstance(result["access_token"], str)
@@ -343,15 +323,17 @@ def search_mail(token: str, query_string: str) -> list[dict[str, Any]]:
]
},
timeout=20,
- ).json()["value"][0]["hitsContainers"][0]["hits"]
+ ).json()["value"][
+ 0
+ ]["hitsContainers"][
+ 0
+ ]["hits"]
assert isinstance(hits, list)
assert isinstance(hits[0], dict)
return hits
-def extract_aods(
- prs: pptx.presentation.Presentation, aod_page_number: int
-) -> list[str]:
+def extract_aods(prs: pptx.presentation.Presentation, aod_page_number: int) -> list[str]:
slide = prs.slides[aod_page_number]
aods = ["", "", "", ""]
for shape in slide.shapes:
@@ -372,18 +354,14 @@ def extract_aods(
elif day == "thursday":
aods[3] = aod
if not all(aods):
- raise ValueError(
- "AOD parsing: The Week Ahead doesn't include all AOD days, or the formatting is borked"
- )
+ raise ValueError("AOD parsing: The Week Ahead doesn't include all AOD days, or the formatting is borked")
return aods
raise ValueError("AOD parsing: The Week Ahead's doesn't even include \"Monday\"")
# TODO: this is one of those places where Monday is *expected* to be the first day.
# TODO: revamp this. this is ugly!
-def extract_community_time(
- prs: pptx.presentation.Presentation, community_time_page_number: int
-) -> list[list[str]]:
+def extract_community_time(prs: pptx.presentation.Presentation, community_time_page_number: int) -> list[list[str]]:
slide = prs.slides[community_time_page_number]
for shape in slide.shapes:
@@ -396,13 +374,9 @@ def extract_community_time(
row_count = len(tbl.rows)
col_count = len(tbl.columns)
if col_count not in [4, 5]:
- raise ValueError(
- "Community time parsing: The Week Ahead community time table does not have 4 or 5 columns"
- )
+ raise ValueError("Community time parsing: The Week Ahead community time table does not have 4 or 5 columns")
if col_count == 4:
- logger.warning(
- "Community time warning: only four columns found, assuming that Y12 has graduated"
- )
+ logger.warning("Community time warning: only four columns found, assuming that Y12 has graduated")
res = [["" for c in range(col_count)] for r in range(row_count)]
@@ -417,11 +391,7 @@ def extract_community_time(
t = t.strip()
if "whole school assembly" in t.lower():
t = "Whole School Assembly"
- elif (
- "tutor group check-in" in t.lower()
- or "follow up day" in t.lower()
- or "open session for tutor and tutee" in t.lower()
- ):
+ elif "tutor group check-in" in t.lower() or "follow up day" in t.lower() or "open session for tutor and tutee" in t.lower():
t = "Tutor Time"
res[r][c] = t
if cell.is_merge_origin:
@@ -432,14 +402,9 @@ def extract_community_time(
return [x[1:] for x in res[1:]]
-def filter_mail_results_by_sender(
- original: Iterable[dict[str, Any]], sender: str
-) -> Iterator[dict[str, Any]]:
+def filter_mail_results_by_sender(original: Iterable[dict[str, Any]], sender: str) -> Iterator[dict[str, Any]]:
for hit in original:
- if (
- hit["resource"]["sender"]["emailAddress"]["address"].lower()
- == sender.lower()
- ):
+ if hit["resource"]["sender"]["emailAddress"]["address"].lower() == sender.lower():
yield hit
@@ -453,7 +418,10 @@ def filter_mail_results_by_subject_regex_groups(
logging.debug("Trying %s" % hit["resource"]["subject"])
matched = re.compile(subject_regex).match(hit["resource"]["subject"])
if matched:
- yield (hit, [matched.group(group) for group in subject_regex_groups])
+ yield (
+ hit,
+ [matched.group(group) for group in subject_regex_groups],
+ )
def download_menu(
@@ -467,22 +435,23 @@ def download_menu(
) -> None:
search_results = search_mail(token, weekly_menu_query_string)
- for hit, matched_groups in filter_mail_results_by_subject_regex_groups(
+ for (
+ hit,
+ matched_groups,
+ ) in filter_mail_results_by_subject_regex_groups(
filter_mail_results_by_sender(search_results, weekly_menu_sender),
weekly_menu_subject_regex,
weekly_menu_subject_regex_four_groups,
):
try:
subject_1st_month = datetime.datetime.strptime(
- matched_groups[0], "%b" # issues here are probably locales
+ matched_groups[0],
+ "%b", # issues here are probably locales
).month
subject_1st_day = int(matched_groups[1])
except ValueError as exc:
raise ValueError(hit["resource"]["subject"], matched_groups[0]) from exc
- if (
- subject_1st_month == datetime_target.month
- and subject_1st_day == datetime_target.day
- ):
+ if subject_1st_month == datetime_target.month and subject_1st_day == datetime_target.day:
break
else:
raise ValueError("No SJ-menu email found")