summarylogtreecommitdiffstats
path: root/update.py
blob: 5d4e3e1eee8dc4f25ee8ab8333d35ad2648762b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python
from html.parser import HTMLParser
from urllib.error import HTTPError
from urllib.parse import urlencode
import argparse
import getpass
import hashlib
import http.cookiejar
import json
import re
import subprocess as subp
import urllib.request as req

parser = argparse.ArgumentParser(
    description="Update PKGBUILD and .SRCINFO to the latest Foundry VTT version"
)
parser.add_argument(
    "--no-commit", action="store_true", help="Do not commit the changes"
)
args = parser.parse_args()


class CsrfMiddlewareParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)
        if (
            tag == "input"
            and "name" in attrs
            and attrs["name"] == "csrfmiddlewaretoken"
        ):
            self.csrfmiddlewaretoken = attrs["value"]


class ReleasesParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.in_release = False
        self.curr_release = None
        self.releases = []

    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)
        if tag == "li" and "class" in attrs and "release" in attrs["class"].split():
            self.in_release = True
        elif (
            self.in_release
            and tag == "a"
            and "href" in attrs
            and attrs["href"].startswith("/releases/")
        ):
            self.curr_release = tuple(
                int(n) for n in attrs["href"].split("/")[-1].split(".")
            )
        elif (
            self.in_release
            and tag == "span"
            and "class" in attrs
            and "stable" in attrs["class"].split()
        ):
            self.releases.append(self.curr_release)

    def handle_endtag(self, tag):
        if tag == "li":
            self.in_release = False


cookiejar = http.cookiejar.LWPCookieJar("foundry_login_cookies.txt")
try:
    cookiejar.load()
except OSError:
    pass
opener = req.build_opener(req.HTTPCookieProcessor(cookiejar))

if "sessionid" not in (c.name for c in cookiejar):
    with opener.open("https://foundryvtt.com/") as res:
        csrfmiddleware_parser = CsrfMiddlewareParser()
        csrfmiddleware_parser.feed(res.read().decode())
        csrfmiddlewaretoken = csrfmiddleware_parser.csrfmiddlewaretoken

    with open("/dev/tty", "r+b", buffering=0) as f:
        f.write("Foundry username: ".encode())
        username = f.readline().decode()
    password = getpass.getpass(prompt="Foundry password: ")

    auth = urlencode(
        {
            "csrfmiddlewaretoken": csrfmiddlewaretoken,
            "login_redirect": "/",
            "login_username": username,
            "login_password": password,
            "login": "",
        }
    )
    auth_req = req.Request(
        "https://foundryvtt.com/auth/login/",
        headers={"Referer": "https://foundryvtt.com/"},
        data=auth.encode(),
    )
    with opener.open(auth_req) as res:
        csrftoken_cookie = res.getheader("Set-Cookie").split(";")[0]
    cookiejar.save()

with opener.open("https://foundryvtt.com/releases/") as res:
    release_parser = ReleasesParser()
    release_parser.feed(res.read().decode())
    latest_release = max(release_parser.releases)

with open("PKGBUILD", "r") as f:
    pkgbuild = f.read()

curr_version = tuple(
    int(n) for n in re.search(r"pkgver\s*=\s*([\d\.]+)", pkgbuild).group(1).split(".")
)

if latest_release > curr_version:
    release_str = ".".join(str(n) for n in latest_release)
    download_req = req.Request(
        f"https://foundryvtt.com/releases/download?build={latest_release[-1]}&platform=linux&response_type=json"
    )
    with opener.open(download_req) as res:
        download_url = json.loads(res.read())["url"]
    with opener.open(download_url) as res:
        filename = f"FoundryVTT-{release_str}.zip"
        with open(filename, "wb") as f:
            hasher = hashlib.sha256()
            expected_bytes = int(res.headers['Content-Length'])
            received_bytes = 0
            chunk = res.read(1024)
            while len(chunk) > 0:
                f.write(chunk)
                received_bytes += len(chunk)
                percent_complete = received_bytes / expected_bytes
                print(f"Downloading {filename}: {percent_complete:.1%}", end="\r")
                hasher.update(chunk)
                chunk = res.read(1024)
            print()

    package_hash = hasher.hexdigest()
    new_pkgbuild = re.sub(
        r"(sha256sums\s*=\s*\(\s*['\"]?)[\da-zA-Z]{64}(['\"]?\s*\))",
        rf"\g<1>{package_hash}\2",
        pkgbuild,
    )
    new_pkgbuild = re.sub(
        r"(pkgver\s*=\s*)[\d\.]+",
        rf"\g<1>{release_str}",
        new_pkgbuild,
    )
    with open("PKGBUILD", "w") as f:
        f.write(new_pkgbuild)
    with open(".SRCINFO", "w") as f:
        subp.run(["makepkg", "--printsrcinfo"], stdout=f)
    if not args.no_commit:
        subp.run(
            ["git", "commit", "-m", f"Update to {release_str}", "PKGBUILD", ".SRCINFO"]
        )
else:
    print("Already up to date")