-
-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8026175
commit 429abb6
Showing
1 changed file
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
from datetime import datetime | ||
from os import name, getenv | ||
from json import loads | ||
from re import compile, IGNORECASE, sub | ||
from pathlib import Path | ||
from configparser import ConfigParser | ||
from argparse import ArgumentParser | ||
from shutil import copytree, ignore_patterns | ||
from urllib.request import urlopen | ||
from subprocess import check_output | ||
from io import BytesIO | ||
from zipfile import ZipFile | ||
|
||
""" | ||
install(-betterfox).py | ||
Usage: | ||
python install.py | ||
When called without arguments, it will: | ||
- Backup your current firefox profile | ||
- Automatically download user.js from the latest Betterfox release compatible with your Firefox version into the profile | ||
- Apply user-overrides in the same directory | ||
However, you can check out install.py/betterfox-install.exe --help to customise most behaviours! | ||
Limitations: | ||
- When using a different repositoy as a source, that repository needs to use the same releases workflow | ||
- Over time, the get_releases might not list older releases due to limited page size. This can be expanded down the road, though | ||
Building into an exe (on Windows): | ||
- pipx install pyinstaller (note: you can try without pipx, but this didn't work for me) | ||
- Run: | ||
- CMD: `pyinstaller --onefile --name install-betterfox install.py && move %cd%\dist\install-betterfox.exe %cd% && del install-betterfox.spec && rmdir /S /Q build && rmdir dist` | ||
- BASH: `pyinstaller --onefile --name install-betterfox install.py && && mv dist/install-betterfox.exe . && rm install-betterfox.spec && rm -rf ./build/ && rmdir dist` | ||
(Sorry, didn't want to add a .gitignore solely for the install script) | ||
- Done! | ||
If there's any problems with the script, feel free to mention @Denperidge on GitHub! | ||
""" | ||
|
||
re_find_version = compile(r"mozilla.org/.*?/firefox/(?P<version>[\d.]*?)/", IGNORECASE) | ||
re_find_overrides = r"(overrides|prefs).*\n(?P<space>\n)" | ||
|
||
FIREFOX_ROOT = Path.home().joinpath(".mozilla/firefox").absolute() if name != "nt" else Path(getenv("APPDATA") + "/Mozilla/Firefox/").resolve() | ||
DEFAULT_FIREFOX_INSTALL = Path("C:/Program Files/Mozilla Firefox/" if name == "nt" else "") | ||
|
||
selected_if_backup = None | ||
selected_config = "" | ||
userjs_path = None | ||
|
||
|
||
def _get_firefox_version(bin="firefox"): | ||
try: | ||
ver_string = check_output([bin, "--version"], encoding="UTF-8") | ||
return ver_string[ver_string.rindex(" ")+1:].strip() | ||
except FileNotFoundError: | ||
return _get_firefox_version(str(DEFAULT_FIREFOX_INSTALL.joinpath("firefox"))) | ||
|
||
def _get_default_profile_folder(): | ||
config_path = FIREFOX_ROOT.joinpath("profiles.ini") | ||
|
||
print(f"Reading {config_path}...") | ||
|
||
config_parser = ConfigParser(strict=False) | ||
config_parser.read(config_path) | ||
|
||
for section in config_parser.sections(): | ||
if "Default" in config_parser[section]: | ||
if config_parser[section]["Default"] == "1": | ||
print("Default detected: " + section) | ||
return FIREFOX_ROOT.joinpath(config_parser[section]["Path"]) | ||
|
||
|
||
def _get_releases(repository_owner, repository_name): | ||
releases = [] | ||
raw_releases = loads(urlopen(f"https://api.github.com/repos/{repository_owner}/{repository_name}/releases").read()) | ||
for raw_release in raw_releases: | ||
name = raw_release["name"] or raw_release["tag_name"] # or fixes 126.0 not being lodaded | ||
body = raw_release["body"] | ||
|
||
|
||
# Find which firefox releases are supported. Manual overrides for ones that don't have it written in their thing! | ||
if name == "user.js v.122.1": | ||
supported = ["107.0", "107.1", "108.0", "108.0.1", "108.0.2", "109.0", "109.0", "110.1", "110.0.1", "111.0", "111.0.1", "112.0", "112.0.1", "112.0.2", "113.0", "113.0.1", "113.0.2", "114.0", "114.0.1", "114.0.2", "115.0", "115.0.1", "115.0.2", "115.0.3", "115.1.0", "115.10.0", "115.11.0", "115.12.0", "115.13.0", "115.14.0", "115.15.0", "115.16.0", "115.16.1", "115.17.0", "115.2.0", "115.2.1", "115.3.0", "115.3.1", "115.4.0", "115.5.0", "115.6.0", "115.7.0", "115.8.0", "115.9.0", "115.9.1", "116.0", "116.0.1", "116.0.2", "116.0.3", "117.0", "117.0.1", "118.0", "118.0.1", "118.0.2", "119.0", "119.0.1", "120.0", "120.0.1", "121.0", "121.0.1", "122.0", "122.0.1"] | ||
elif name == "user.js 116.1": | ||
supported = ["116.0", "116.0.1", "116.0.2", "116.0.3"] | ||
elif name == "Betterfox v.107": | ||
supported = ["107.0"] | ||
elif "firefox release" in body.lower(): | ||
trim_body = body.lower()[body.lower().index("firefox release"):] | ||
supported = re_find_version.findall(trim_body) | ||
if len(supported) == 0: | ||
print(f"Could not parse release in '{name}'. Please post this error message on https://github.com/{repository_owner}/{repository_name}/issues") | ||
continue | ||
else: | ||
print(f"Could not find firefox release header '{name}'. Please post this error message on https://github.com/{repository_owner}/{repository_name}/issues") | ||
continue | ||
|
||
releases.append({ | ||
"name": name, | ||
"url": raw_release["zipball_url"], | ||
"supported": supported, | ||
}) | ||
return releases | ||
|
||
def _get_latest_compatible_release(releases): | ||
for release in releases: | ||
if firefox_version in release["supported"]: | ||
return release | ||
return None | ||
|
||
|
||
def backup_profile(src): | ||
dest = f"{src}-backup-{datetime.today().strftime('%Y-%m-%d-%H-%M-%S')}" | ||
|
||
copytree(src, dest, ignore=ignore_patterns("*lock")) | ||
print("Backed up profile to " + dest) | ||
|
||
|
||
def download_betterfox(url): | ||
data = BytesIO() | ||
data.write(urlopen(url).read()) | ||
return data | ||
|
||
def extract_betterfox(data, profile_folder): | ||
zipfile = ZipFile(data) | ||
userjs_zipinfo = None | ||
for file in zipfile.filelist: | ||
if file.filename.endswith("user.js"): | ||
userjs_zipinfo = file | ||
userjs_zipinfo.filename = Path(userjs_zipinfo.filename).name | ||
|
||
if not userjs_zipinfo: | ||
raise BaseException("Could not find user.js!") | ||
|
||
return zipfile.extract(userjs_zipinfo, profile_folder) | ||
|
||
|
||
def list_releases(releases, only_supported=False, add_index=False): | ||
print() | ||
print(f"Listing {'compatible' if only_supported else 'all'} Betterfox releases:") | ||
if only_supported: | ||
print("Use --list-all to view all available releases") | ||
else: | ||
print(f"Releases marked with '> ' are documented to be compatible with your Firefox version ({firefox_version})") | ||
print() | ||
|
||
i = 0 | ||
for release in releases: | ||
supported = firefox_version in release["supported"] | ||
if not only_supported or (only_supported and supported): | ||
print(f"{f'[{i}]' if add_index else ''}{'> ' if supported else ' '}{release['name'].ljust(20)}\t\t\tSupported: {','.join(release['supported'])}") | ||
i+=1 | ||
|
||
|
||
if __name__ == "__main__": | ||
firefox_version = _get_firefox_version() | ||
selected_release = None | ||
|
||
default_profile_folder = _get_default_profile_folder() | ||
argparser = ArgumentParser( | ||
|
||
) | ||
argparser.add_argument("--overrides", "-o", default=default_profile_folder.joinpath("user-overrides.js"), help="if the provided file exists, add overrides to user.js. Defaults to " + str(default_profile_folder.joinpath("user-overrides.js"))), | ||
|
||
|
||
advanced = argparser.add_argument_group("Advanced") | ||
advanced.add_argument("--betterfox-version", "-bv", default=None, help=f"Which version of Betterfox to install. Defaults to the latest compatible release for your installed Firefox version") | ||
advanced.add_argument("--profile-dir", "-p", "-pd", default=default_profile_folder, help=f"Which profile dir to install user.js in. Defaults to {default_profile_folder}") | ||
advanced.add_argument("--repository-owner", "-ro", default="yokoffing", help="owner of the Betterfox repository. Defaults to yokoffing") | ||
advanced.add_argument("--repository-name", "-rn", default="Betterfox", help="name of the Betterfox repository. Defaults to Betterfox") | ||
|
||
disable = argparser.add_argument_group("Disable functionality") | ||
disable.add_argument("--no-backup", "-nb", action="store_true", default=False, help="disable backup of current profile (not recommended)"), | ||
disable.add_argument("--no-install", "-ni", action="store_true", default=False, help="don't install Betterfox"), | ||
|
||
modes = argparser.add_mutually_exclusive_group() | ||
modes.add_argument("--list", action="store_true", default=False, help=f"List all Betterfox releases compatible with your version of Firefox ({firefox_version})") | ||
modes.add_argument("--list-all", action="store_true", default=False, help=f"List all Betterfox releases") | ||
modes.add_argument("--interactive", "-i", action="store_true", default=False, help=f"Interactively select Betterfox version") | ||
|
||
args = argparser.parse_args() | ||
|
||
releases = _get_releases(args.repository_owner, args.repository_name) | ||
|
||
|
||
if args.list or args.list_all: | ||
list_releases(releases, args.list) | ||
input("Press ENTER to exit...") | ||
exit() | ||
|
||
if not args.no_backup: | ||
backup_profile(args.profile_dir) | ||
|
||
if args.betterfox_version: | ||
# If not None AND not string, default value has been used | ||
if not isinstance(args.betterfox_version, str): | ||
selected_release = args.betterfox_version | ||
print(f"Using latest compatible Betterfox version ({selected_release['name']})...") | ||
# If string has been passed | ||
else: | ||
selected_release = next(rel for rel in releases if rel['name'] == args.betterfox_version) | ||
print(f"Using manually selected Betterfox version ({selected_release['name']})") | ||
|
||
if not args.betterfox_version: | ||
selected_release = _get_latest_compatible_release(releases) | ||
|
||
if args.interactive or not selected_release: | ||
if not selected_release: | ||
print("Could not find a compatible Betterfox version for your Firefox installation.") | ||
|
||
list_releases(releases, False, True) | ||
selection = int(input(f"Select Betterfox version, or press enter without typing a number to cancel [0-{len(releases) - 1}]: ")) | ||
|
||
selected_release = releases[selection] | ||
|
||
|
||
|
||
if not args.no_install: | ||
userjs_path = extract_betterfox( | ||
download_betterfox(selected_release["url"]), | ||
args.profile_dir | ||
) | ||
print(f"Installed user.js to {userjs_path} !") | ||
|
||
|
||
if Path(args.overrides).exists(): | ||
print("Found overrides at " + str(args.overrides)) | ||
|
||
with open(str(args.overrides), "r", encoding="utf-8") as overrides_file: | ||
overrides = overrides_file.read() | ||
with open(userjs_path, "r", encoding="utf-8") as userjs_file: | ||
old_content = userjs_file.read() | ||
new_content = sub(re_find_overrides, "\n" + overrides + "\n", old_content, count=1, flags=IGNORECASE) | ||
with open(userjs_path, "w", encoding="utf-8") as userjs_file: | ||
userjs_file.write(new_content) | ||
else: | ||
print(f"Found no overrides in {args.overrides}") | ||
|
||
input("Press ENTER to exit...") | ||
|