#!/usr/bin/env python3 # This file is part of AOMO, "Archive Of My Own", a collection of Python and PHP # scripts designed act as an improved local backup system # for works published on https://archiveofourown.org # # Copyright (c) 2025 Cyberpro123, except where otherwise noted. # # This project is available under the GNU General Public License (GPL) v2.0, # available at https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html # or in the 'LICENSE.md' file that should be distributed alongside this one. # # Report issues to https://codeberg.org/Cyberpro123/AOMO # Contact author at cyberpro123@posteo.com import datetime import mailbox import bs4 import common def verifyAo3Email( message: mailbox.Message, ) -> bool: rightSubject = bool(message["subject"][:5] == "[AO3]") rightSender = bool( message["from"] == "Archive of Our Own " ) if rightSubject and rightSender: return True return False def getIdsFromMessage( message: mailbox.Message, ) -> set: out = set(()) for part in message.walk(): if part.get_content_type() == "text/html": body = part.get_payload(decode=True) soup = bs4.BeautifulSoup(body, "lxml") hrefs = [] for link in soup.find_all("a"): hrefs.append(str(link.get("href"))) for i in hrefs: if "archiveofourown.org/works" in i: j = i.split("/") k = "" done = False for char in j[j.index("works") + 1]: try: int(char) except ValueError: done = True else: if not done: k += str(int(char)) if k: out.add(int(k)) return out if __name__ == "__main__": sinceStr = "Specify a time, mail sent before this time will be ignored. Accepts either a date string in YYYY/MM/DD-HH:MM:SS format, representing a literal " sinceStr += str( "time, or an integer, representing a certain number of seconds ago (ie 86400 will accept only mail from within the last day)", ) # autoformatter gave me a 'string too long' warning common.init( description="Scans through an email inbox file to detect AO3 subscription notification emails.", secondOut="WorkID Output", main=("mailbox", "Location of mailbox file to scan"), since=sinceStr, ) del sinceStr config = common.config mailFile = common.os.path.abspath(common.os.path.expanduser(common.args.mailbox[0])) if not common.os.path.exists(mailFile): raise FileNotFoundError(f"File [{mailFile}] doesn't exist.") earliestOkTime = datetime.datetime.now().timestamp() - 86400 if common.args.since: try: earliestOkTime = datetime.datetime.strptime( common.args.since, "%Y/%m/%d-%H:%M:%S" ).timestamp() except ValueError: try: earliestOkTime = datetime.datetime.now().timestamp() - int( common.args.since ) except ValueError: common.logging.getLogger(__name__).error( f"Can't parse argument to --since [{common.args.since}], using default value (1 day ago) instead" ) ids = set(()) for message in mailbox.mbox(mailFile): if verifyAo3Email(message=message): if ( datetime.datetime.strptime( message["date"], "%a, %d %b %Y %H:%M:%S %z" ).timestamp() > earliestOkTime ): for id in getIdsFromMessage(message=message): ids.add(id) for workID in sorted(ids): common.args.batch_out.write(f"{workID}\n")