|
@@ -0,0 +1,153 @@
|
|
|
|
|
+import os
|
|
|
|
|
+import configparser
|
|
|
|
|
+import shutil
|
|
|
|
|
+
|
|
|
|
|
+from typing import Optional
|
|
|
|
|
+from time import sleep
|
|
|
|
|
+from argparse import ArgumentParser
|
|
|
|
|
+from subprocess import run, Popen
|
|
|
|
|
+from sys import stderr, stdout
|
|
|
|
|
+from yaml import load, SafeLoader
|
|
|
|
|
+
|
|
|
|
|
+from marionette_driver.addons import Addons
|
|
|
|
|
+from marionette_driver.marionette import Marionette
|
|
|
|
|
+from marionette_driver.geckoinstance import DesktopInstance
|
|
|
|
|
+from mozprofile import FirefoxProfile
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def shell_execute(cmd: str, env: Optional[dict] = None):
|
|
|
|
|
+ if env is None:
|
|
|
|
|
+ env = {}
|
|
|
|
|
+
|
|
|
|
|
+ run(cmd, env=env, stdout=stdout, stderr=stderr, shell=True)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def profile_list() -> list:
|
|
|
|
|
+ result = []
|
|
|
|
|
+
|
|
|
|
|
+ ini = configparser.ConfigParser()
|
|
|
|
|
+ ini.read(os.path.expanduser("~/.mozilla/firefox/profiles.ini"))
|
|
|
|
|
+ for _, values in ini.items():
|
|
|
|
|
+ for key, value in values.items():
|
|
|
|
|
+ if key != "path":
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ result.append(value)
|
|
|
|
|
+
|
|
|
|
|
+ return list(set(result))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def profile_create(name_1: str):
|
|
|
|
|
+ shell_execute(f"/usr/bin/firefox --createprofile {name_1} -headless")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def profile_configure(name_1: str, name_2: str):
|
|
|
|
|
+ cwd = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
|
+
|
|
|
|
|
+ # Read config
|
|
|
|
|
+ config = {}
|
|
|
|
|
+ with open(f"{cwd}/profiles/{name_1}.yaml", "rt", encoding="utf-8") as handle:
|
|
|
|
|
+ config = load(handle, Loader=SafeLoader)
|
|
|
|
|
+
|
|
|
|
|
+ # Install signed addons first
|
|
|
|
|
+ # Start firefox
|
|
|
|
|
+ profile = FirefoxProfile(name_1)
|
|
|
|
|
+ firefox = DesktopInstance(
|
|
|
|
|
+ host="127.0.0.1",
|
|
|
|
|
+ port=2828,
|
|
|
|
|
+ bin="/usr/bin/firefox",
|
|
|
|
|
+ app_args=["-remote-allow-system-access"],
|
|
|
|
|
+ profile=profile,
|
|
|
|
|
+ headless=1,
|
|
|
|
|
+ )
|
|
|
|
|
+ firefox.start()
|
|
|
|
|
+
|
|
|
|
|
+ # Init marionette
|
|
|
|
|
+ marionette = Marionette()
|
|
|
|
|
+ marionette.start_session()
|
|
|
|
|
+
|
|
|
|
|
+ # Install addons
|
|
|
|
|
+ addons = Addons(marionette)
|
|
|
|
|
+ for addon in config["addons"]:
|
|
|
|
|
+ addons.install(f"{cwd}/addons/{addon['id']}-{addon['version']}.xpi")
|
|
|
|
|
+
|
|
|
|
|
+ firefox.close()
|
|
|
|
|
+
|
|
|
|
|
+ # Set prefs
|
|
|
|
|
+ with open(f"{cwd}/{name_1}/user.js", "wt", encoding="utf-8") as handle:
|
|
|
|
|
+ for key, value in config["prefs"].items():
|
|
|
|
|
+ if type(value) == str:
|
|
|
|
|
+ value = f'"{value}"'
|
|
|
|
|
+ elif type(value) == bool:
|
|
|
|
|
+ value = 'true' if value else 'false'
|
|
|
|
|
+ else:
|
|
|
|
|
+ value = value
|
|
|
|
|
+
|
|
|
|
|
+ handle.write(f'user_pref("{key}", {value});\n')
|
|
|
|
|
+
|
|
|
|
|
+ # Install profile
|
|
|
|
|
+ from_path = f"{cwd}/{name_1}"
|
|
|
|
|
+ to_path = os.path.expanduser(f"~/.mozilla/firefox/{name_2}")
|
|
|
|
|
+
|
|
|
|
|
+ if os.path.exists(to_path):
|
|
|
|
|
+ shutil.rmtree(to_path)
|
|
|
|
|
+
|
|
|
|
|
+ shutil.copytree(from_path, to_path, dirs_exist_ok=True)
|
|
|
|
|
+ shutil.rmtree(from_path)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def profile_init(name_1: str):
|
|
|
|
|
+ proc = Popen(f"/usr/bin/firefox -p {name_1} -headless", shell=True)
|
|
|
|
|
+ sleep(5) # TODO: investigate why firefox take a time to apply extensions
|
|
|
|
|
+ proc.kill()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def profile_patch(name_2: str):
|
|
|
|
|
+ cwd = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
|
+
|
|
|
|
|
+ # Patch profile
|
|
|
|
|
+ from_path = f"{cwd}/patches/search.json.mozlz4"
|
|
|
|
|
+ to_path = os.path.expanduser(f"~/.mozilla/firefox/{name_2}/search.json.mozlz4")
|
|
|
|
|
+
|
|
|
|
|
+ shutil.copy(from_path, to_path)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ parser = ArgumentParser()
|
|
|
|
|
+ parser.add_argument("--configs", type=str, nargs="*", required=True)
|
|
|
|
|
+ args = parser.parse_args()
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ shutil.rmtree(os.path.expanduser("~/.mozilla"))
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ names_1 = []
|
|
|
|
|
+
|
|
|
|
|
+ for config in args.configs:
|
|
|
|
|
+ names_1.append(config)
|
|
|
|
|
+
|
|
|
|
|
+ for name_1 in names_1:
|
|
|
|
|
+ profile_create(name_1)
|
|
|
|
|
+ sleep(1) # Prevent profile hash collision
|
|
|
|
|
+
|
|
|
|
|
+ names_2 = profile_list()
|
|
|
|
|
+ mapping = {}
|
|
|
|
|
+
|
|
|
|
|
+ for name_1 in names_1:
|
|
|
|
|
+ for name_2 in names_2:
|
|
|
|
|
+ if name_1 in name_2:
|
|
|
|
|
+ mapping[name_1] = name_2
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ for name_1, name_2 in mapping.items():
|
|
|
|
|
+ profile_configure(name_1, name_2)
|
|
|
|
|
+ profile_init(name_1) # TODO: remove this workaround
|
|
|
|
|
+ # profile_patch(name_2) # TODO: patching not working for now, dynamic patching needed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ try:
|
|
|
|
|
+ main()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(e)
|