diff options
Diffstat (limited to '')
-rw-r--r-- | sjdbmk/__init__.py | 0 | ||||
-rw-r--r-- | sjdbmk/__main__.py | 90 | ||||
-rw-r--r-- | sjdbmk/const.py | 30 | ||||
-rw-r--r-- | sjdbmk/daily.py | 45 | ||||
-rw-r--r-- | sjdbmk/grant.py | 12 | ||||
-rw-r--r-- | sjdbmk/inspire_dl.py | 17 | ||||
-rw-r--r-- | sjdbmk/legacy_wikipedia.py | 63 | ||||
-rw-r--r-- | sjdbmk/menuparser.py | 20 | ||||
-rw-r--r-- | sjdbmk/pack.py | 23 | ||||
-rw-r--r-- | sjdbmk/sendmail.py | 55 | ||||
-rw-r--r-- | sjdbmk/sendmail2.py | 22 | ||||
-rw-r--r-- | sjdbmk/serve.py | 6 | ||||
-rw-r--r-- | sjdbmk/weekly.py | 127 |
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") |