From 99c2b8bea3049bb17fc581bd1c258a8f043ce682 Mon Sep 17 00:00:00 2001 From: David Kruger Date: Fri, 30 May 2025 10:27:57 -0700 Subject: [PATCH] Adding a script to automate getting the newest version --- README.md | 2 ++ update.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100755 update.py diff --git a/README.md b/README.md index da2dbb1..ac146ec 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,5 @@ Given the main JS file URL, the source code can be downloaded and mapped using: ``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ``` + +For convenience you can run `python update.py` diff --git a/update.py b/update.py new file mode 100755 index 0000000..8daa2d9 --- /dev/null +++ b/update.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +import argparse +import gzip +import html.parser +import logging +import re +import subprocess +import urllib.request + +## The URL of a dndbeyond character which won't be deleted +DND_BEYOND_CHARACTER_URL = "https://www.dndbeyond.com/characters/147022047" + +## Regex to find the matching JS file +MAIN_JS_RE = re.compile(r"https://media\.dndbeyond\.com\/character-app.*main.*\.js") + +## Path to sourcemapper bin +SOURCEMAPPER_BIN = "/home/prometheus/go/bin/sourcemapper" + + +def main(): + parser = argparse.ArgumentParser( + description="Utility for scanning and converting videos." + ) + parser.add_argument( + "-u", + "--character-url", + default=DND_BEYOND_CHARACTER_URL, + help="URL of the dndbeyond character to grab the source from.", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable verbose logging" + ) + parser.add_argument( + "-q", "--quiet", action="store_true", help="Only display errors" + ) + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + elif args.quiet: + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(level=logging.INFO) + char_data = download_character() + parser = MainJSExtractor() + parser.feed(char_data.decode("utf-8")) + print(f"Found URL: {parser.js_url}") + subprocess.check_call( + [ + SOURCEMAPPER_BIN, + "-output", + "ddb_main", + "-jsurl", + parser.js_url, + ] + ) + + +def download_character() -> bytes: + try: + req = urllib.request.Request( + DND_BEYOND_CHARACTER_URL, + headers={ + "user-agent": "Mozilla/5.0", + "authority": "www.dndbeyond.com", + "cache-control": "max-age=0", + "upgrade-insecure-requests": "1", + "sec-fetch-user": "?1", + "accept": "text/json", + "sec-fetch-site": "none", + "sec-fetch-mode": "navigate", + "accept-encoding": "gzip, deflate, br", + "accept-language": "en-US,en;q=0.9", + }, + ) + with urllib.request.urlopen(req) as resp: + if resp.info().get("Content-Encoding") == "gzip": + return gzip.decompress(resp.read()) + else: + return resp.read() + except Exception: + logging.exception(f"Failed to load the character") + + +class MainJSExtractor(html.parser.HTMLParser): + def __init__(self): + super().__init__() + self.js_url = None + + def handle_starttag(self, tag, attrs): + if tag == "script": + for attr_name, attr_val in attrs: + if attr_name == "src": + m = MAIN_JS_RE.match(attr_val) + if m is not None: + self.js_url = attr_val + + +if __name__ == "__main__": + main()