Compare commits

..

No commits in common. "main" and "1.1" have entirely different histories.
main ... 1.1

10 changed files with 274 additions and 831 deletions

View file

@ -1,16 +1,12 @@
# Revanced Builder # Revanced Builder
This repo will allow one to build [ReVanced](https://github.com/revanced/) apps This repo will allow one to build [ReVanced](https://github.com/revanced/) apps automatically, send notifications (and possibly share the builds with friends). It uses [Gotify](https://gotify.net), [ntfy.sh](https://ntfy.sh) or [telegram.sh](https://github.com/fabianonline/telegram.sh) to send messages. Make sure that `Java >=17` is installed and selected as default.
automatically, send notifications (and possibly share the builds with friends).
It uses [Gotify](https://gotify.net), [ntfy.sh](https://ntfy.sh) or
[telegram.sh](https://github.com/fabianonline/telegram.sh) to send messages.
Make sure that `Java >=17` is installed and selected as default.
## Installation ## Installation
Recommended way is to use [`uv`](https://github.com/astral-sh/uv) to install the program. Recommended way is to use [`pipx`](https://github.com/pypa/pipx) to install the program.
``` ```
uv tool install git+https://github.com/SinTan1729/ReVancedBuilder pipx install git+https://github.com/SinTan1729/ReVancedBuilder
``` ```
And then you can update/reinstall the program using `uv tool update ReVancedBuilder`. And then you can update/reinstall the program using `pipx reinstall ReVancedBuilder`.
## How to use ## How to use
Just run `ReVancedBuilder <working-directory> (force/experimental/checkonly/buildonly)`. Just run `ReVancedBuilder <working-directory> (force/experimental/checkonly/buildonly)`.
@ -54,28 +50,12 @@ It might be a good idea to set it up to run periodically. There are a few ways o
``` ```
## Notes ## Notes
- If you installed it using `uv`, you can figure out the full location of the - If you installed it using `pipx`, you can figure out the full location of the program by running `which ReVancedBuilder`.
program by running `which ReVancedBuilder`. - This app needs some config files to run. Download all the config files inside `example_configs` directory, namely `build_config`, `chosen_patches` (optional), and `notification_config` (optional, needed only if you want to send notifications) and move them to your working directory. Then, you should modify these files to your liking.
- This app needs some config files to run. Download all the config files inside - The script will download the **automatically selected compatible version** (unless version is specified in `build_config`) (using compatibility of patches as listed [here](https://revanced.app/patches)) of Youtube on APKPure, **NOT** latest official version on Google Play.
`example_configs` directory, namely `build_config`, `chosen_patches` - **Under no circumstances** will any APKs be uploaded to this repository as that might attract legal problems.
(optional), and `notification_config` (optional, needed only if you want to - If you enable telegram notifications, make sure to fill up the config options inside the `build_config` file. For more information about the config, take at look at the repos of `telegram.sh` and `telegram-upload` provided above.
send notifications) and move them to your working directory. Then, you should - It can also run a post script (if exists), specified in the `build_config` file. The `timestamp` is passed as `$1`.
modify these files to your liking. - In the current configuration, the script only builds YouTube ReVanced and YouTube Music ReVanced (both nonroot), but it's easy to add support for any other ReVanced app using the `build_config` file. The config files are self-explanatory.
- The script will download the **automatically selected compatible version**, - All the packages are pulled from [APKPure](https://apkpure.com) and GitHub (the [`revanced/*`](https://github.com/revanced) repos).
using compatibility of patches as listed [here](https://revanced.app/patches)
of Youtube on APKPure, **NOT** latest official version on Google Play (unless
version is specified in `build_config`).
- **Under no circumstances** will any APKs be uploaded to this repository as
that might attract legal problems.
- If you enable telegram notifications, make sure to fill up the config options
inside the `build_config` file. For more information about the config, take at
look at the repos of `telegram.sh` and `telegram-upload` provided above.
- It can also run a post script (if exists), specified in the `build_config`
file. The `timestamp` is passed as `$1`.
- In the current configuration, the script only builds YouTube ReVanced and
YouTube Music ReVanced (both nonroot), but it's easy to add support for any
other ReVanced app using the `build_config` file. The config files are
self-explanatory.
- All the packages are pulled from [APKPure](https://apkpure.com) and GitHub
(the [`revanced/*`](https://github.com/revanced) repos).

View file

@ -50,9 +50,5 @@ root = true
output_name = YouTube_Music_ReVanced_root output_name = YouTube_Music_ReVanced_root
keystore = revanced-ytm-root.keystore keystore = revanced-ytm-root.keystore
[gmscore]
# If you use a Xiaomi or Huawei device, change the variant to alt
variant=regular
[post_script] [post_script]
# file = ./post_script.sh # file = ./post_script.sh

View file

@ -23,5 +23,3 @@
enabled = false enabled = false
url = url url = url
topic = topic topic = topic
token = token

View file

@ -1,14 +1,18 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project] [project]
name = "ReVancedBuilder" name = "ReVancedBuilder"
authors = [{ name = "Sayantan Santra", email = "sayantan.santra689@gmail.com" }] authors = [{ name = "Sayantan Santra", email = "sayantan.santra689@gmail.com" }]
description = "A tool to automatically build latest releases of ReVanced apps" description = "A tool to automatically build latest releases of ReVanced apps"
license = "GPL-3.0-only"
readme = "README.md" readme = "README.md"
requires-python = ">=3.6" requires-python = ">=3.6"
keywords = ["revanced", "patch"] keywords = ["revanced", "patch"]
license = { file = "LICENSE" }
classifiers = ["Programming Language :: Python :: 3"] classifiers = ["Programming Language :: Python :: 3"]
dependencies = ["cloudscraper", "requests", "packaging", "bs4"] dependencies = ["requests", "packaging", "bs4"]
version = "1.4.1" version = "1.1"
[project.scripts] [project.scripts]
ReVancedBuilder = "ReVancedBuilder:ReVancedBuilder" ReVancedBuilder = "ReVancedBuilder:ReVancedBuilder"

136
src/ReVancedBuilder/APKPure_dl.py Executable file → Normal file
View file

@ -1,66 +1,65 @@
#!/usr/bin/env python3\ #!/usr/bin/env python3\
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com> # SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
import os import os
import sys
import json
import cloudscraper as scraper
from bs4 import BeautifulSoup as bs
from packaging.version import Version from packaging.version import Version
import requests as req
from bs4 import BeautifulSoup as bs
from ReVancedBuilder.Cleanup import err_exit from ReVancedBuilder.Cleanup import err_exit
# Determine the best version available to download # Determine the best version available to download
def apkpure_best_match(version, soup, appstate, apk): def apkpure_best_match(version, soup):
try: try:
vers_list_str = [ vers_list = [Version(x['data-dt-version'])
x["data-dt-version"] for x in soup.css.select('a[data-dt-apkid^="b/APK/"]') for x in soup.css.select(f"a[data-dt-apkid^=\"b/APK/\"]")]
] except:
except Exception as ex: err_exit(
err_exit(f" There was some error getting list of versions of {apk}: {ex}", appstate) f" There was some error getting list of versions of {apk}...", appstate)
vers_list = map(lambda x: Version(x), vers_list_str) if version != '0':
if version != "0":
vers_list = filter(lambda x: x <= Version(version), vers_list) vers_list = filter(lambda x: x <= Version(version), vers_list)
max_ver = max(vers_list) return str(max(vers_list))
return next(filter(lambda x: Version(x) == max_ver, vers_list_str))
# Download an apk from apkpure.net # Download an apk from apkpure.net
def apkpure_dl(apk, appname, version, hard_version, session, present_vers, flag, appstate): def apkpure_dl(apk, appname, version, hard_version, session, present_vers, flag):
res = session.get(f"https://apkpure.com/{appname}/{apk}/versions") res = session.get(f"https://apkpure.net/{appname}/{apk}/versions")
res.raise_for_status() res.raise_for_status()
soup = bs(res.text, "html.parser") soup = bs(res.text, 'html.parser')
try: try:
if present_vers[apk] == version and flag != "force" and os.path.isfile(apk + ".apk"): if present_vers[apk] == version and flag != 'force' and os.path.isfile(apk+'.apk'):
print(f"Recommended version {version} of {apk} is already present.") print(
f"Recommended version {version} of {apk} is already present.")
return return
except KeyError: except KeyError:
pass pass
if not hard_version: if not hard_version:
apkpure_version = apkpure_best_match(version, soup, appstate, apk) apkpure_version = apkpure_best_match(version, soup)
if version not in [apkpure_version, "0"]: if version not in [apkpure_version, '0']:
print( print(
f"Required version {version} not found in APKPure, choosing version {apkpure_version} instead." f"Required version {version} not found in APKPure, choosing version {apkpure_version} instead.")
)
version = apkpure_version version = apkpure_version
try: try:
if present_vers[apk] == version and flag != "force" and os.path.isfile(apk + ".apk"): if present_vers[apk] == version and flag != 'force' and os.path.isfile(apk+'.apk'):
print(f"Recommended version {version} of {apk} is already present.") print(
f"Recommended version {version} of {apk} is already present.")
return return
except KeyError: except KeyError:
pass pass
if flag == "checkonly" and present_vers[apk] != version: if flag == 'checkonly' and present_vers[apk] != version:
print(f"{apk} has an update ({present_vers[apk]} -> {version})") print(f"{apk} has an update ({present_vers[apk]} -> {version})")
return return
@ -68,15 +67,16 @@ def apkpure_dl(apk, appname, version, hard_version, session, present_vers, flag,
# Get the version code # Get the version code
try: try:
ver_code = soup.css.select(f'a[data-dt-version="{version}"][data-dt-apkid^="b/APK/"]')[0][ ver_code = soup.css.select(
"data-dt-versioncode" f"a[data-dt-version=\"{version}\"][data-dt-apkid^=\"b/APK/\"]")[0]['data-dt-versioncode']
] except:
except Exception as ex: err_exit(
err_exit(f" There was some error while downloading {apk}: {ex}", appstate) f" There was some error while downloading {apk}...", appname)
res = session.get(f"https://d.apkpure.com/b/APK/{apk}?versionCode={ver_code}", stream=True) res = session.get(
f"https://d.apkpure.net/b/APK/{apk}?versionCode={ver_code}", stream=True)
res.raise_for_status() res.raise_for_status()
with open(apk + ".apk", "wb") as f: with open(apk+'.apk', 'wb') as f:
for chunk in res.iter_content(chunk_size=8192): for chunk in res.iter_content(chunk_size=8192):
f.write(chunk) f.write(chunk)
print(" Done!") print(" Done!")
@ -84,72 +84,68 @@ def apkpure_dl(apk, appname, version, hard_version, session, present_vers, flag,
# Download apk files, if needed # Download apk files, if needed
def get_apks(appstate): def get_apks(appstate):
present_vers = appstate["present_vers"] present_vers = appstate['present_vers']
build_config = appstate["build_config"] build_config = appstate['build_config']
flag = appstate["flag"] flag = appstate['flag']
print("Downloading required apk files from APKPure...") print('Downloading required apk files from APKPure...')
# Create a cloudscraper session
session = scraper.create_scraper()
# Get latest patches using the ReVanced API # Get latest patches using the ReVanced API
try: try:
# Get the first result # Get the first result
patches = session.get("https://api.revanced.app/v4/patches/list").json() patches_json = next(filter(
except session.exceptions.RequestException as e: lambda x: x['repository'] == 'revanced/revanced-patches', appstate['tools']))
patches = req.get(patches_json['browser_download_url']).json()
except req.exceptions.RequestException as e:
err_exit(f"Error fetching patches, {e}", appstate) err_exit(f"Error fetching patches, {e}", appstate)
session = req.Session()
session.headers.update(
{'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'})
for app in build_config: for app in build_config:
# Check if we need to build an app # Check if we need to build an app
if not build_config[app].getboolean("build"): if not build_config[app].getboolean('build'):
continue continue
try: try:
apk = build_config[app]["apk"] apk = build_config[app]['apk']
pretty_name = build_config[app]["pretty_name"] pretty_name = build_config[app]['pretty_name']
apkpure_appname = build_config[app]["apkpure_appname"] apkpure_appname = build_config[app]['apkpure_appname']
except Exception as ex: except:
err_exit(f"Invalid config for {app} in build_config!: {ex}", appstate) err_exit(f"Invalid config for {app} in build_config!", appstate)
print(f"Checking {pretty_name}...") print(f"Checking {pretty_name}...")
try: try:
required_ver = build_config[app]["version"] required_ver = build_config[app]['version']
required_ver = Version(required_ver)
hard_version = True hard_version = True
print(f"Using version {required_ver} of {apk} from build_config.") print(f"Using version {required_ver} of {apk} from build_config.")
except Exception as ex: except:
print(f"Dealing with exception: {ex}")
hard_version = False hard_version = False
compatible_vers = [] compatible_vers = []
for patch in patches: for patch in patches:
if patch['compatiblePackages'] is not None:
for pkg in patch['compatiblePackages']:
if pkg['name'] == apk:
try: try:
compatible_vers.append(patch["compatiblePackages"][apk][-1]) compatible_vers.append(pkg['versions'][-1])
except (KeyError, TypeError): except TypeError:
pass pass
if not compatible_vers: if not compatible_vers:
required_ver = Version("0") required_ver = Version('0')
else: else:
required_ver = min(map(lambda x: Version(x), compatible_vers)) required_ver = min(map(lambda x: Version(x), compatible_vers))
required_ver = next(filter(lambda x: Version(x) == required_ver, compatible_vers))
print(f"Chosen required version of {apk} is {required_ver}.") print(f"Chosen required version of {apk} is {required_ver}.")
if apk in appstate["present_vers"] and appstate["present_vers"][apk] == required_ver: if appstate['present_vers'][apk] == str(required_ver):
print("It's already present on disk, so skipping download.") print("It's already present on disk, so skipping download.")
else: else:
apkpure_dl( apkpure_dl(apk, apkpure_appname, str(required_ver),
apk, hard_version, session, present_vers, flag)
apkpure_appname,
required_ver,
hard_version,
session,
present_vers,
flag,
appstate,
)
present_vers.update({apk: required_ver}) present_vers.update({apk: str(required_ver)})
appstate["present_vers"] = present_vers appstate['present_vers'] = present_vers
return appstate return appstate

30
src/ReVancedBuilder/Cleanup.py Executable file → Normal file
View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com> # SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
import os import os
@ -13,28 +13,28 @@ from ReVancedBuilder.Notifications import send_notif
def move_apps(appstate): def move_apps(appstate):
build_config = appstate["build_config"] build_config = appstate['build_config']
print = appstate["logger"].info print = appstate['logger'].info
try: try:
os.mkdir("archive") os.mkdir('archive')
except FileExistsError: except FileExistsError:
pass pass
for app in build_config: for app in build_config:
if not build_config[app].getboolean("build"): if not build_config[app].getboolean('build'):
continue continue
name = build_config[app]["output_name"] name = build_config[app]['output_name']
final_name = f"{name}_{appstate['timestamp']}.apk" final_name = f"{name}_{appstate['timestamp']}.apk"
try: try:
os.rename(name + ".apk", "archive/" + final_name) os.rename(name+'.apk', 'archive/'+final_name)
except FileNotFoundError: except FileNotFoundError:
pass pass
# sys.exit('There was an error moving the final apk files!') # sys.exit('There was an error moving the final apk files!')
# Do some cleanup, keep only the last 3 build's worth of files and a week worth of logs # Do some cleanup, keep only the last 3 build's worth of files and a week worth of logs
with os.scandir("archive") as dir: with os.scandir('archive') as dir:
files = [] files = []
for f in dir: for f in dir:
if name in f.name: if name in f.name:
@ -43,10 +43,10 @@ def move_apps(appstate):
files.reverse() files.reverse()
for f in files[3:]: for f in files[3:]:
os.remove(f) os.remove(f)
print("Deleted old build " + f.name) print('Deleted old build '+f.name)
# Delete logs older than 7 days # Delete logs older than 7 days
with os.scandir("logs") as dir: with os.scandir('logs') as dir:
now = time.time() now = time.time()
for f in dir: for f in dir:
if f.stat().st_ctime < now - 7 * 86400: if f.stat().st_ctime < now - 7 * 86400:
@ -54,18 +54,18 @@ def move_apps(appstate):
def err_exit(msg, appstate, code=1): def err_exit(msg, appstate, code=1):
print = appstate["logger"].info print = appstate['logger'].info
try: try:
appstate["notification_config"] appstate['notification_config']
if appstate["flag"] != "checkonly": if appstate['flag'] != 'checkonly':
send_notif(appstate, error=True) send_notif(appstate, error=True)
except KeyError: except:
pass pass
if msg: if msg:
print(f"ERROR: {msg}") print(f"ERROR: {msg}")
# Delete the lockfile # Delete the lockfile
os.remove("lockfile") os.remove('lockfile')
sys.exit(code) sys.exit(code)

64
src/ReVancedBuilder/JAVABuilder.py Executable file → Normal file
View file

@ -1,11 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com> # SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
import os
import sys
import configparser as cp import configparser as cp
import json import json
import os
import subprocess import subprocess
from ReVancedBuilder.Cleanup import err_exit from ReVancedBuilder.Cleanup import err_exit
@ -14,58 +15,59 @@ from ReVancedBuilder.Cleanup import err_exit
def build_apps(appstate): def build_apps(appstate):
build_config = appstate["build_config"] build_config = appstate['build_config']
flag = appstate["flag"] flag = appstate['flag']
print = appstate["logger"].info print = appstate['logger'].info
chosen_patches = cp.ConfigParser() chosen_patches = cp.ConfigParser()
chosen_patches.read("chosen_patches") chosen_patches.read('chosen_patches')
try: try:
included_patches = json.loads(chosen_patches["patches"]["included"]) included_patches = json.loads(chosen_patches['patches']['included'])
except (KeyError, ValueError): except:
included_patches = [] included_patches = []
try: try:
excluded_patches = json.loads(chosen_patches["patches"]["excluded"]) excluded_patches = json.loads(chosen_patches['patches']['excluded'])
except (KeyError, ValueError): except Exception as e:
excluded_patches = [] excluded_patches = []
for app in build_config: for app in build_config:
# Check if we need to build an app # Check if we need to build an app
if not build_config[app].getboolean("build"): if not build_config[app].getboolean('build'):
continue continue
# Build the command to be run # Build the command to be run
cmd = "java -jar revanced-cli.jar patch -p revanced-patches.rvp" cmd = 'java -jar revanced-cli.jar patch -b revanced-patches.jar -m revanced-integrations.apk'
try: try:
root = build_config[app].getboolean("root") root = build_config[app].getboolean('root')
except cp.Error: except:
root = False root = False
if root: if root:
cmd += ' --mount -e "GmsCore support"' cmd += ' --mount -e microg-support'
for item in included_patches: for item in included_patches:
cmd += f" -i {item}" cmd += f" -i {item}"
for item in excluded_patches: for item in excluded_patches:
cmd += f" -e {item}" cmd += f" -e {item}"
if flag == "experimental": if flag == 'experimental':
cmd += " --experimental" cmd += ' --experimental'
try: try:
keystore = build_config[app]["keystore"] keystore = build_config[app]['keystore']
if not root: if not root:
cmd += f" --keystore {keystore} --keystore-entry-alias=alias --keystore-entry-password=ReVanced --keystore-password=ReVanced" cmd += f" --keystore {keystore} --alias=alias --keystore-entry-password=ReVanced --keystore-password=ReVanced"
except KeyError: except:
pass pass
try: try:
apk = build_config[app]["apk"] apk = build_config[app]['apk']
pretty_name = build_config[app]["pretty_name"] pretty_name = build_config[app]['pretty_name']
output_name = build_config[app]["output_name"] apkpure_appname = build_config[app]['apkpure_appname']
except KeyError: output_name = build_config[app]['output_name']
except:
err_exit(f"Invalid config for {app} in build_config!", appstate) err_exit(f"Invalid config for {app} in build_config!", appstate)
cmd += f" -o {output_name}.apk {apk}.apk" cmd += f" -o {output_name}.apk {apk}.apk"
@ -76,17 +78,17 @@ def build_apps(appstate):
print(f"Building {pretty_name} (nonroot) using '{cmd}'") print(f"Building {pretty_name} (nonroot) using '{cmd}'")
try: try:
with subprocess.Popen( with subprocess.Popen(cmd, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout as output:
cmd, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
).stdout as output:
for line in output: for line in output:
line_utf = line.decode("utf-8").strip("\n") line_utf = line.decode('utf-8').strip('\n')
if line_utf: if line_utf:
print(line_utf) print(line_utf)
except Exception as e: except Exception as e:
err_exit(f"There was an error while building {pretty_name}!\n{e}", appstate) err_exit(
f"There was an error while building {pretty_name}!\n{e}", appstate)
try: try:
os.rename(output_name + ".apk", output_name + ".apk") os.rename(output_name+'.apk', output_name+'.apk')
except FileNotFoundError: except FileNotFoundError:
err_exit(f"There was an error while building {pretty_name}!", appstate) err_exit(
f"There was an error while building {pretty_name}!", appstate)

112
src/ReVancedBuilder/Notifications.py Executable file → Normal file
View file

@ -1,103 +1,97 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com> # SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
import json import json
import re import re
import subprocess
import requests as req import requests as req
import subprocess
def send_notif(appstate, error=False): def send_notif(appstate, error=False):
print = appstate["logger"].info print = appstate['logger'].info
timestamp = appstate["timestamp"] timestamp = appstate['timestamp']
if error: if error:
msg = f"There was an error during build! Please check the logs.\nTimestamp: {timestamp}" msg = f"There was an error during build! Please check the logs.\nTimestamp: {timestamp}"
else: else:
build_config = appstate["build_config"] notification_config = appstate['notification_config']
present_vers = appstate["present_vers"] build_config = appstate['build_config']
present_vers = appstate['present_vers']
flag = appstate['flag']
msg = json.dumps(present_vers, indent=0) msg = json.dumps(present_vers, indent=0)
msg = re.sub('("|\{|\}|,)', "", msg).strip("\n") msg = re.sub('("|\{|\}|,)', '', msg).strip('\n')
msg = msg.replace("revanced-", "ReVanced ") msg = msg.replace('revanced-', 'ReVanced ')
msg = msg.replace("cli", "CLI") msg = msg.replace('cli', 'CLI')
msg = msg.replace("integrations", "Integrations") msg = msg.replace('integrations', 'Integrations')
msg = msg.replace("patches", "Patches") msg = msg.replace('patches', 'Patches')
msg = msg.replace('VancedMicroG', 'Vanced microG')
for app in build_config: for app in build_config:
if not build_config[app].getboolean("build"): if not build_config[app].getboolean('build'):
continue continue
msg = msg.replace(build_config[app]["apk"], build_config[app]["pretty_name"]) msg = msg.replace(
build_config[app]['apk'], build_config[app]['pretty_name'])
msg += "\nTimestamp: " + timestamp msg += '\nTimestamp: ' + timestamp
if appstate["gmscore_updated"]: if appstate['microg_updated']:
msg += "\nGmsCore was updated." msg += '\nVanced microG was updated.'
config = appstate["notification_config"] config = appstate['notification_config']
for entry in config: for entry in config:
if not config[entry].getboolean("enabled"): if not config[entry].getboolean('enabled'):
continue continue
encoded_title = "⚙⚙⚙ ReVanced Build ⚙⚙⚙".encode("utf-8") encoded_title = '⚙⚙⚙ ReVanced Build ⚙⚙⚙'.encode('utf-8')
if entry == "ntfy": if entry == 'ntfy':
print("Sending notification through ntfy.sh...") print('Sending notification through ntfy.sh...')
try: try:
url = config[entry]["url"] url = config[entry]['url']
topic = config[entry]["topic"] topic = config[entry]['topic']
except KeyError: except:
print("URL or TOPIC not provided!") print('URL or TOPIC not provided!')
continue
headers = {
"Icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Revanced-logo-round.svg/240px-Revanced-logo-round.svg.png",
"Title": encoded_title,
}
try:
token = config[entry]["token"]
headers["Authorization"] = "Bearer " + token
except KeyError:
continue continue
headers = {'Icon': 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Revanced-logo-round.svg/240px-Revanced-logo-round.svg.png',
'Title': encoded_title}
try: try:
req.post(f"{url}/{topic}", msg, headers=headers) req.post(f"{url}/{topic}", msg, headers=headers)
except Exception as ex: except Exception as e:
print(f"Failed with exception: {ex}") print('Failed!' + str(e))
elif entry == "gotify": elif entry == 'gotify':
print("Sending notification through Gotify...") print('Sending notification through Gotify...')
try: try:
url = config[entry]["url"] url = config[entry]['url']
token = config[entry]["token"] token = config[entry]['token']
except KeyError: except:
print("URL or TOKEN not provided!") print('URL or TOKEN not provided!')
continue continue
data = {"Title": encoded_title, "message": msg, "priority": "5"} data = {'Title': encoded_title, 'message': msg, 'priority': '5'}
try: try:
req.post(f"{url}/message?token={token}", data) req.post(f"{url}/message?token={token}", data)
except Exception as e: except Exception as e:
print("Failed!" + str(e)) print('Failed!' + str(e))
elif entry == "telegram": elif entry == 'telegram':
print("Sending notification through Telegram...") print('Sending notification through Telegram...')
try: try:
chat = config[entry]["chat"] chat = config[entry]['chat']
token = config[entry]["token"] token = config[entry]['token']
except KeyError: except:
print("CHAT or TOKEN not provided!") print('CHAT or TOKEN not provided!')
continue continue
cmd = f'./telegram.sh -t {token} -c {chat} -T {encoded_title} -M "{msg}"' cmd = f"./telegram.sh -t {token} -c {chat} -T {encoded_title} -M \"{msg}\""
try: try:
with subprocess.Popen( with subprocess.Popen(cmd, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout as output:
cmd, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
).stdout as output:
for line in output: for line in output:
line_utf = line.decode("utf-8").strip("\n") line_utf = line.decode('utf-8').strip('\n')
if line_utf: if line_utf:
print(line_utf) print(line_utf)
except Exception as ex: except Exception as e:
print(f"Failed to send notification with exception: {ex}") err_exit(f"Failed!\n{e}", appstate)
else: else:
print("Don't know how to send notifications to " + entry) print('Don\'t know how to send notifications to ' + entry)

View file

@ -1,107 +1,83 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com> # SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
import sys
import os
import configparser as cp import configparser as cp
import json import json
import logging import logging
import os
import subprocess import subprocess
import sys
from datetime import datetime
import requests as req import requests as req
from packaging.version import Version from packaging.version import Version
from datetime import datetime
from ReVancedBuilder.APKPure_dl import get_apks from ReVancedBuilder.APKPure_dl import apkpure_best_match, apkpure_dl, get_apks
from ReVancedBuilder.Cleanup import err_exit, move_apps, send_notif
from ReVancedBuilder.JAVABuilder import build_apps from ReVancedBuilder.JAVABuilder import build_apps
from ReVancedBuilder.Notifications import send_notif
from ReVancedBuilder.Cleanup import move_apps, err_exit
# Update the ReVanced tools, if needed # Update the ReVanced tools, if needed
def update_tools(appstate): def update_tools(appstate):
for item in ["revanced-cli", "revanced-patches"]: for item in ['revanced-cli', 'revanced-integrations', 'revanced-patches']:
print(f"Checking updates for {item}...") print(f"Checking updates for {item}...")
tools = appstate["tools"] tools = appstate['tools']
tool = next( tool = next(filter(lambda x: x['repository'] == 'revanced/'+item and x['content_type'] not in ['application/pgp-keys', 'application/json'], tools))
filter( latest_ver = Version(tool['version'])
lambda x: x["repository"] == "revanced/" + item
and x["content_type"] not in ["application/pgp-keys", "application/json"],
tools,
)
)
latest_ver = Version(tool["version"])
try: try:
present_ver = Version(appstate["present_vers"][item]) present_ver = Version(appstate['present_vers'][item])
except KeyError: except KeyError:
present_ver = Version("0") present_ver = Version('0')
output_file = item + os.path.splitext(tool["name"])[1] output_file = item+os.path.splitext(tool['name'])[1]
if flag == "force" or not os.path.isfile(output_file) or present_ver < latest_ver: if flag == 'force' or not os.path.isfile(output_file) or present_ver < latest_ver:
appstate["up-to-date"] = False appstate['up-to-date'] = False
print(f"{item} has an update ({str(present_ver)} -> {str(latest_ver)})") print(f"{item} has an update ({str(present_ver)} -> {str(latest_ver)})")
if flag != "checkonly": if flag != 'checkonly':
print(f"Downloading {output_file}...") print(f"Downloading {output_file}...")
res = req.get(tool["browser_download_url"], stream=True) res = req.get(tool['browser_download_url'], stream=True)
res.raise_for_status() res.raise_for_status()
with open(output_file, "wb") as f: with open(output_file, 'wb') as f:
for chunk in res.iter_content(chunk_size=8192): for chunk in res.iter_content(chunk_size=8192):
f.write(chunk) f.write(chunk)
appstate["present_vers"].update({item: str(latest_ver)}) appstate['present_vers'].update({item: str(latest_ver)})
print("Done!") print("Done!")
return appstate return appstate
# Update microG, if needed
# Update GmsCore, if needed def update_microg(appstate):
def update_gmscore(appstate): print('Checking updates for Vanced microG...')
print("Checking updates for GmsCore...")
# Pull the latest information using the ReVanced API
try: try:
data = req.get("https://api.revanced.app/v2/gmscore/releases/latest").json()["release"] data = req.get('https://api.github.com/repos/inotia00/VancedMicroG/releases/latest').json()['tag_name']
latest_ver = Version(data)
except req.exceptions.RequestException as e: except req.exceptions.RequestException as e:
err_exit(f"Error fetching GmsCore information, {e}", appstate) err_exit(f"Error fetching info about Vanced microG, {e}", appstate)
latest_ver = Version(data["metadata"]["tag_name"])
try: try:
present_ver = Version(appstate["present_vers"]["GmsCore"]) present_ver = Version(appstate['present_vers']['VancedMicroG'])
except KeyError: except KeyError:
present_ver = Version("0") present_ver = Version('0')
try: if flag == 'force' or not os.path.isfile('microg.apk') or present_ver < latest_ver:
variant = appstate["build_config"]["gmscore"]["variant"] appstate['up-to-date'] = False
except KeyError: print(f"Vanced microG has an update ({str(present_ver)} -> {str(latest_ver)})")
variant = "regular" if flag != 'checkonly':
print(f"Downloading vanced-microg.apk...")
if variant == "alt": res = req.get('https://github.com/inotia00/VancedMicroG/releases/latest/download/microg.apk', stream=True)
gmscore_link = next(filter(lambda x: "-hw-" in x["name"], data["assets"]))[
"browser_download_url"
]
else:
gmscore_link = next(filter(lambda x: "-hw-" not in x["name"], data["assets"]))[
"browser_download_url"
]
if flag == "force" or not os.path.isfile("GmsCore.apk") or present_ver < latest_ver:
appstate["up-to-date"] = False
print(f"GmsCore has an update ({str(present_ver)} -> {str(latest_ver)})")
if flag != "checkonly":
print("Downloading GmsCore...")
res = req.get(gmscore_link, stream=True)
res.raise_for_status() res.raise_for_status()
with open("GmsCore.apk", "wb") as f: with open('microg.apk', 'wb') as f:
for chunk in res.iter_content(chunk_size=8192): for chunk in res.iter_content(chunk_size=8192):
f.write(chunk) f.write(chunk)
appstate["present_vers"].update({"GmsCore": str(latest_ver)}) appstate['present_vers'].update({'VancedMicroG': str(latest_ver)})
print("Done!") print("Done!")
appstate["gmscore_updated"] = True appstate['microg_updated'] = True
return appstate return appstate
# ------------------------------ # ------------------------------
# The main function starts here # The main function starts here
# ------------------------------ # ------------------------------
@ -111,116 +87,111 @@ appstate = {}
# Get a timestamp # Get a timestamp
time = datetime.now() time = datetime.now()
appstate["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ") appstate['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ')
# Read arguments # Read arguments
try: try:
os.chdir(sys.argv[1]) os.chdir(sys.argv[1])
except IndexError: except IndexError:
sys.exit("Please provide a working directory as argument!") sys.exit('Please provide a working directory as argument!')
except FileNotFoundError: except FileNotFoundError:
sys.exit("Invalid working directory provided!") sys.exit('Invalid working directory provided!')
# Try to make sure only one instance is running in a given working directory # Try to make sure only one instance is running in a given working directory
try: try:
if os.path.exists("lockfile"): if os.path.exists('lockfile'):
raise FileExistsError raise FileExistsError
with open("tmplockfile", "x") as f: with open('tmplockfile', 'x') as f:
f.flush() f.flush()
os.fsync(f.fileno()) os.fsync(f.fileno())
os.replace("tmplockfile", "lockfile") os.replace('tmplockfile', 'lockfile')
except FileExistsError: except FileExistsError:
sys.exit("Another instance is already running in the same working directory!") sys.exit('Another instance is already running in the same working directory!')
# Set up logging # Set up logging
try: try:
os.mkdir("logs") os.mkdir('logs')
except FileExistsError: except FileExistsError:
pass pass
logging.basicConfig(level=logging.INFO, format="%(message)s") logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger() logger = logging.getLogger()
logger.addHandler(logging.FileHandler(f"logs/{appstate['timestamp']}.log", "w")) logger.addHandler(logging.FileHandler(f"logs/{appstate['timestamp']}.log", 'w'))
print = logger.info print = logger.info
appstate["logger"] = logger appstate['logger'] = logger
# Get the flag # Get the flag
try: try:
flag = sys.argv[2] flag = sys.argv[2]
except IndexError: except:
flag = None flag = None
if flag not in ["buildonly", "checkonly", "force", "experimental", None]: if flag not in ['buildonly', 'checkonly', 'force', 'experimental', None]:
err_exit(f"Unknown flag: {flag}", appstate) err_exit(f"Unknown flag: {flag}", appstate)
appstate["flag"] = flag appstate['flag'] = flag
appstate["gmscore_updated"] = False appstate['microg_updated'] = False
print(f"Started building ReVanced apps at {time.strftime('%d %B, %Y %H:%M:%S')}") print(f"Started building ReVanced apps at {time.strftime('%d %B, %Y %H:%M:%S')}")
print("----------------------------------------------------------------------") print('----------------------------------------------------------------------')
# Read configs # Read configs
try: try:
appstate["build_config"] = cp.ConfigParser() appstate['build_config']=cp.ConfigParser()
appstate["build_config"].read_file(open("build_config", "r")) appstate['build_config'].read_file(open('build_config', 'r'))
except FileNotFoundError: except FileNotFoundError:
err_exit( err_exit('No build config provided, exiting. Please look at the GitHub page for more information:\n https://github.com/SinTan1729/ReVancedBuilder', appstate)
"No build config provided, exiting. Please look at the GitHub page for more information:\n https://github.com/SinTan1729/ReVancedBuilder",
appstate,
)
appstate["notification_config"] = cp.ConfigParser() appstate['notification_config'] = cp.ConfigParser()
appstate["notification_config"].read("notification_config") appstate['notification_config'].read('notification_config')
# Pull the latest information using the ReVanced API # Pull the latest information using the ReVanced API
try: try:
tools = req.get("https://api.revanced.app/tools").json()["tools"] tools = req.get('https://api.revanced.app/tools').json()['tools']
appstate["tools"] = tools appstate['tools'] = tools
except req.exceptions.RequestException as e: except req.exceptions.RequestException as e:
err_exit(f"Error fetching patch list, {e}", appstate) err_exit(f"Error fetching patch list, {e}", appstate)
try: try:
with open("versions.json", "r") as f: with open('versions.json', 'r') as f:
appstate["present_vers"] = json.load(f) appstate['present_vers'] = json.load(f)
except FileNotFoundError: except:
# We'll treat empty as 0 later # We'll treat empty as 0 later
appstate["present_vers"] = json.loads("{}") appstate['present_vers'] = json.loads('{}')
appstate["up-to-date"] = True appstate['up-to-date'] = True
# send_notif(appstate, error=False) # <,,,,,,,,<,,,,,,,,,,,,,
if flag != "buildonly": if flag != 'buildonly':
appstate = update_tools(appstate) appstate = update_tools(appstate)
appstate = update_gmscore(appstate) appstate = update_microg(appstate)
if (not appstate["up-to-date"] and flag != "checkonly") or flag == "force": if (not appstate['up-to-date'] and flag != 'checkonly') or flag == 'force':
appstate = get_apks(appstate) appstate = get_apks(appstate)
if (flag != "checkonly" and not appstate["up-to-date"]) or flag in ["force", "buildonly"]: if (flag != 'checkonly' and not appstate['up-to-date']) or flag in ['force', 'buildonly']:
build_apps(appstate) build_apps(appstate)
move_apps(appstate) move_apps(appstate)
# Update version numbers in the versions.json file # Update version numbers in the versions.json file
if appstate["up-to-date"] and flag != "buildonly": if appstate['up-to-date'] and flag != 'buildonly':
print("There's nothing to do.") print('There\'s nothing to do.')
elif flag != "checkonly": elif flag != 'checkonly':
send_notif(appstate)
try: try:
os.rename("versions.json", "versions-old.json") os.rename('versions.json', 'versions-old.json')
except FileNotFoundError: except FileNotFoundError:
pass pass
if flag != "buildonly": if flag != 'buildonly':
with open("versions.json", "w") as f: with open('versions.json', 'w') as f:
json.dump(appstate["present_vers"], f, indent=4) json.dump(appstate['present_vers'], f, indent=4)
try: try:
cmd = f"{appstate['build_config']['post_script']['file']} {appstate['timestamp']}" cmd = f"{appstate['build_config']['post_script']['file']} {appstate['timestamp']}"
print(f"Running the post command '{cmd}'") print(f"Running the post command '{cmd}'")
subprocess.run(cmd, shell=True) subprocess.run(cmd, shell=True)
except Exception as ex: except:
print(f"Got exception while running the build: '{ex}'") pass
err_exit("", appstate, 0)
send_notif(appstate)
# Delete the lockfile # Delete the lockfile
os.remove("lockfile") os.remove('lockfile')
sys.exit(0) sys.exit(0)

498
uv.lock
View file

@ -1,498 +0,0 @@
version = 1
revision = 1
requires-python = ">=3.6"
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
"python_full_version == '3.7.*'",
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
dependencies = [
{ name = "soupsieve", version = "2.3.2.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 },
]
[[package]]
name = "beautifulsoup4"
version = "4.13.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
"python_full_version == '3.7.*'",
]
dependencies = [
{ name = "soupsieve", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "soupsieve", version = "2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
{ name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "typing-extensions", version = "4.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 },
]
[[package]]
name = "bs4"
version = "0.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4", version = "4.12.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
{ name = "beautifulsoup4", version = "4.13.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.7'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189 },
]
[[package]]
name = "certifi"
version = "2025.1.31"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
]
[[package]]
name = "charset-normalizer"
version = "2.0.12"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
sdist = { url = "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", size = 79105 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df", size = 39623 },
]
[[package]]
name = "charset-normalizer"
version = "3.4.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
"python_full_version == '3.7.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 },
{ url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 },
{ url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 },
{ url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 },
{ url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 },
{ url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 },
{ url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 },
{ url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 },
{ url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 },
{ url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 },
{ url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 },
{ url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 },
{ url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 },
{ url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 },
{ url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 },
{ url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 },
{ url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 },
{ url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 },
{ url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 },
{ url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 },
{ url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 },
{ url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 },
{ url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 },
{ url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 },
{ url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 },
{ url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 },
{ url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
{ url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
{ url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
{ url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
{ url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
{ url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
{ url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
{ url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
{ url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
{ url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
{ url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
{ url = "https://files.pythonhosted.org/packages/bb/a6/3a22521736a3a75263ce930a2257eb60fd2a6b8fd9293ea1271a074a202f/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", size = 135247 },
{ url = "https://files.pythonhosted.org/packages/c2/1e/fa099993d17c00addbb76b3bfcd3ab1692fefc4e193ba738e111c09cfbe2/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", size = 144536 },
{ url = "https://files.pythonhosted.org/packages/c2/95/8725b2b2fcb418abe048b5558af42e444161433a76b47759539fe5bb7992/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", size = 137661 },
{ url = "https://files.pythonhosted.org/packages/10/2f/6e2c727649d5e97ee17230f4c064480c69eb563f52ba87df448647d77a99/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", size = 138592 },
{ url = "https://files.pythonhosted.org/packages/e9/1b/0cad3a4f1906dc36a9cc89093d36b134638463e8e43479dbb96fd47d99ef/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", size = 141250 },
{ url = "https://files.pythonhosted.org/packages/8f/aa/73a89701dd661d9b5d24d0594c42ed0f7a3aed4448cc4f8f7315d0701399/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", size = 135234 },
{ url = "https://files.pythonhosted.org/packages/a8/00/b8b43690ecac81e5038dc6332681757123ee9c4c15eb4a6ecf0232393f59/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", size = 142457 },
{ url = "https://files.pythonhosted.org/packages/ec/a3/2db14427b37cefbf9a9ec3d3c861e1b1721e4d4507536df102c2854966dc/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", size = 145967 },
{ url = "https://files.pythonhosted.org/packages/0a/97/a183d752295997d95d20f2aa34d66ff6abda5af5482d0e7995dbbc9a753a/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", size = 145129 },
{ url = "https://files.pythonhosted.org/packages/96/c6/f62d90c0e66525a374c35a1389f4290e5d7a731cd2cfacd9740272f927f9/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", size = 138515 },
{ url = "https://files.pythonhosted.org/packages/0b/fe/4ade940a1b4bac031357b4a74fc8406a2ecd9dfc007dea850c5b7ee348de/charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", size = 93486 },
{ url = "https://files.pythonhosted.org/packages/89/17/74bb34d02d62a21cc626c7d4db475b6f2207980f6beb002051db8342b139/charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", size = 100195 },
{ url = "https://files.pythonhosted.org/packages/10/bd/6517ea94f2672e801011d50b5d06be2a0deaf566aea27bcdcd47e5195357/charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", size = 195653 },
{ url = "https://files.pythonhosted.org/packages/e5/0d/815a2ba3f283b4eeaa5ece57acade365c5b4135f65a807a083c818716582/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", size = 140701 },
{ url = "https://files.pythonhosted.org/packages/aa/17/c94be7ee0d142687e047fe1de72060f6d6837f40eedc26e87e6e124a3fc6/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", size = 150495 },
{ url = "https://files.pythonhosted.org/packages/f7/33/557ac796c47165fc141e4fb71d7b0310f67e05cb420756f3a82e0a0068e0/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", size = 142946 },
{ url = "https://files.pythonhosted.org/packages/1e/0d/38ef4ae41e9248d63fc4998d933cae22473b1b2ac4122cf908d0f5eb32aa/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", size = 144737 },
{ url = "https://files.pythonhosted.org/packages/43/01/754cdb29dd0560f58290aaaa284d43eea343ad0512e6ad3b8b5c11f08592/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", size = 147471 },
{ url = "https://files.pythonhosted.org/packages/ba/cd/861883ba5160c7a9bd242c30b2c71074cda2aefcc0addc91118e0d4e0765/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", size = 140801 },
{ url = "https://files.pythonhosted.org/packages/6f/7f/0c0dad447819e90b93f8ed238cc8f11b91353c23c19e70fa80483a155bed/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", size = 149312 },
{ url = "https://files.pythonhosted.org/packages/8e/09/9f8abcc6fff60fb727268b63c376c8c79cc37b833c2dfe1f535dfb59523b/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", size = 152347 },
{ url = "https://files.pythonhosted.org/packages/be/e5/3f363dad2e24378f88ccf63ecc39e817c29f32e308ef21a7a6d9c1201165/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", size = 149888 },
{ url = "https://files.pythonhosted.org/packages/e4/10/a78c0e91f487b4ad0ef7480ac765e15b774f83de2597f1b6ef0eaf7a2f99/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", size = 145169 },
{ url = "https://files.pythonhosted.org/packages/d3/81/396e7d7f5d7420da8273c91175d2e9a3f569288e3611d521685e4b9ac9cc/charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", size = 95094 },
{ url = "https://files.pythonhosted.org/packages/40/bb/20affbbd9ea29c71ea123769dc568a6d42052ff5089c5fe23e21e21084a6/charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", size = 102139 },
{ url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 },
{ url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 },
{ url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 },
{ url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 },
{ url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 },
{ url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 },
{ url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 },
{ url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 },
{ url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 },
{ url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 },
{ url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 },
{ url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 },
{ url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 },
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
]
[[package]]
name = "cloudscraper"
version = "1.2.71"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyparsing", version = "3.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.6.8'" },
{ name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.6.8' and python_full_version < '3.9'" },
{ name = "pyparsing", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "requests", version = "2.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
{ name = "requests", version = "2.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "requests", version = "2.32.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
{ name = "requests-toolbelt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/25/6d0481860583f44953bd791de0b7c4f6d7ead7223f8a17e776247b34a5b4/cloudscraper-1.2.71.tar.gz", hash = "sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3", size = 93261 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/97/fc88803a451029688dffd7eb446dc1b529657577aec13aceff1cc9628c5d/cloudscraper-1.2.71-py2.py3-none-any.whl", hash = "sha256:76f50ca529ed2279e220837befdec892626f9511708e200d48d5bb76ded679b0", size = 99652 },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
]
[[package]]
name = "packaging"
version = "21.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
dependencies = [
{ name = "pyparsing", version = "3.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.6.8'" },
{ name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.6.8' and python_full_version < '3.7'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", size = 84848 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522", size = 40750 },
]
[[package]]
name = "packaging"
version = "24.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.7.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488 },
]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
[[package]]
name = "pyparsing"
version = "3.0.7"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.6.8'",
]
sdist = { url = "https://files.pythonhosted.org/packages/d6/60/9bed18f43275b34198eb9720d4c1238c68b3755620d20df0afd89424d32b/pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", size = 884709 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/80/c1/23fd82ad3121656b585351aba6c19761926bb0db2ebed9e4ff09a43a3fcc/pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484", size = 98049 },
]
[[package]]
name = "pyparsing"
version = "3.1.4"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.8.*'",
"python_full_version == '3.7.*'",
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
]
sdist = { url = "https://files.pythonhosted.org/packages/83/08/13f3bce01b2061f2bbd582c9df82723de943784cf719a35ac886c652043a/pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032", size = 900231 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/0c/0e3c05b1c87bb6a1c76d281b0f35e78d2d80ac91b5f8f524cebf77f51049/pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", size = 104100 },
]
[[package]]
name = "pyparsing"
version = "3.2.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/1a/3544f4f299a47911c2ab3710f534e52fea62a633c96806995da5d25be4b2/pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a", size = 1067694 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/a7/c8a2d361bf89c0d9577c934ebb7421b25dc84bf3a8e3ac0a40aed9acc547/pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1", size = 107716 },
]
[[package]]
name = "requests"
version = "2.27.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
dependencies = [
{ name = "certifi", marker = "python_full_version < '3.7'" },
{ name = "charset-normalizer", version = "2.0.12", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
{ name = "idna", marker = "python_full_version < '3.7'" },
{ name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/f3/26ff3767f099b73e0efa138a9998da67890793bfa475d8278f84a30fec77/requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", size = 106758 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d", size = 63133 },
]
[[package]]
name = "requests"
version = "2.31.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.7.*'",
]
dependencies = [
{ name = "certifi", marker = "python_full_version == '3.7.*'" },
{ name = "charset-normalizer", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "idna", marker = "python_full_version == '3.7.*'" },
{ name = "urllib3", version = "2.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 },
]
[[package]]
name = "requests"
version = "2.32.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
]
dependencies = [
{ name = "certifi", marker = "python_full_version >= '3.8'" },
{ name = "charset-normalizer", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
{ name = "idna", marker = "python_full_version >= '3.8'" },
{ name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" },
{ name = "urllib3", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests", version = "2.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
{ name = "requests", version = "2.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "requests", version = "2.32.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 },
]
[[package]]
name = "revancedbuilder"
version = "1.4.1"
source = { virtual = "." }
dependencies = [
{ name = "bs4" },
{ name = "cloudscraper" },
{ name = "packaging", version = "21.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
{ name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "packaging", version = "24.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
{ name = "requests", version = "2.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" },
{ name = "requests", version = "2.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" },
{ name = "requests", version = "2.32.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
]
[package.metadata]
requires-dist = [
{ name = "bs4" },
{ name = "cloudscraper" },
{ name = "packaging" },
{ name = "requests" },
]
[[package]]
name = "soupsieve"
version = "2.3.2.post1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/03/bac179d539362319b4779a00764e95f7542f4920084163db6b0fd4742d38/soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d", size = 102814 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/16/e3/4ad79882b92617e3a4a0df1960d6bce08edfb637737ac5c3f3ba29022e25/soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", size = 37377 },
]
[[package]]
name = "soupsieve"
version = "2.4.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.7.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/47/9e/780779233a615777fbdf75a4dee2af7a345f4bf74b42d4a5f836800b9d91/soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea", size = 101278 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/37/673d6490efc51ec46d198c75903d99de59baffdd47aea3d071b80a9e4e89/soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8", size = 36379 },
]
[[package]]
name = "soupsieve"
version = "2.6"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
]
[[package]]
name = "typing-extensions"
version = "4.7.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.7.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232 },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version == '3.8.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
]
[[package]]
name = "urllib3"
version = "1.26.20"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.6.8' and python_full_version < '3.7'",
"python_full_version < '3.6.8'",
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225 },
]
[[package]]
name = "urllib3"
version = "2.0.7"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.7.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/af/47/b215df9f71b4fdba1025fc05a77db2ad243fa0926755a52c5e71659f4e3c/urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", size = 282546 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/b2/b157855192a68541a91ba7b2bbcb91f1b4faa51f8bae38d8005c034be524/urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e", size = 124213 },
]
[[package]]
name = "urllib3"
version = "2.2.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.8.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
]
[[package]]
name = "urllib3"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
]
sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
]