cmd/getcrx.py

158 lines
4.6 KiB
Python
Raw Normal View History

2019-06-12 05:41:31 +00:00
import argparse
import io
import json
import os
import requests
import sys
import time
import traceback
import zipfile
from voussoirkit import interactive
2021-09-23 03:28:18 +00:00
from voussoirkit import operatornotify
from voussoirkit import pipeable
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'getcrx')
2019-06-12 05:41:31 +00:00
FILENAME_BADCHARS = '\\/:*?<>|"'
2021-09-23 03:28:18 +00:00
WEBSTORE_URL = 'https://chrome.google.com/webstore/detail/{extension_id}'
CRX_URL = 'https://clients2.google.com/service/update2/crx?response=redirect&prodversion=83.0.4103.116&acceptformat=crx2,crx3&x=id%3D{extension_id}%26uc'
2019-06-12 05:41:31 +00:00
2020-11-11 04:14:33 +00:00
session = requests.Session()
2019-06-12 05:41:31 +00:00
def sanitize_filename(name):
for c in FILENAME_BADCHARS:
name = name.replace(c, '-')
return name
def get_webstore_name_version(extension_id):
url = WEBSTORE_URL.format(extension_id=extension_id)
2020-11-11 04:14:33 +00:00
response = session.get(url)
2021-09-23 03:28:18 +00:00
response.raise_for_status()
2019-06-12 05:41:31 +00:00
try:
name = response.text
name = name.split('meta property="og:title" content="')[1]
name = name.split('"')[0]
except IndexError:
name = None
try:
version = response.text
version = version.split('meta itemprop="version" content="')[1]
version = version.split('"')[0]
except IndexError:
version = None
return (name, version)
def get_crx_name_version(crx_bytes):
crx_handle = io.BytesIO(crx_bytes)
crx_archive = zipfile.ZipFile(crx_handle)
manifest = json.loads(crx_archive.read('manifest.json'))
name = manifest.get('name', None)
version = manifest.get('version', None)
return (name, version)
2021-09-23 03:28:18 +00:00
def get_crx(extension_id):
2019-06-12 05:41:31 +00:00
url = CRX_URL.format(extension_id=extension_id)
2020-11-11 04:14:33 +00:00
response = session.get(url)
2019-06-12 05:41:31 +00:00
response.raise_for_status()
2021-09-23 03:28:18 +00:00
return response.content
2019-06-12 05:41:31 +00:00
2021-09-23 03:28:18 +00:00
def download_crx(extension_id, auto_overwrite=None):
log.info('Checking %s.', extension_id)
2019-06-12 05:41:31 +00:00
(name, version) = get_webstore_name_version(extension_id)
2021-09-23 03:28:18 +00:00
crx_bytes = get_crx(extension_id)
2019-06-12 05:41:31 +00:00
if name is None or version is None:
2021-09-23 03:28:18 +00:00
(crx_name, crx_ver) = get_crx_name_version(crx_bytes)
2019-06-12 05:41:31 +00:00
name = name or crx_name
version = version or crx_ver
2019-06-12 05:41:31 +00:00
name = name or extension_id
2021-09-23 03:28:18 +00:00
version = version or time.strftime('%Y-%m-%d')
2019-06-12 05:41:31 +00:00
crx_filename = '{name} ({id}) [{version}]'
crx_filename = crx_filename.format(
name=name,
id=extension_id,
version=version,
)
if not crx_filename.endswith('.crx'):
crx_filename += '.crx'
crx_filename = sanitize_filename(crx_filename)
if os.path.isfile(crx_filename):
2020-02-16 17:50:46 +00:00
if auto_overwrite is True:
permission = True
2019-06-12 05:41:31 +00:00
if auto_overwrite is None:
2020-02-16 17:50:46 +00:00
message = f'"{crx_filename}" already exists. Overwrite?'
permission = interactive.getpermission(message)
2019-06-12 05:41:31 +00:00
else:
permission = False
else:
permission = True
if permission:
crx_handle = open(crx_filename, 'wb')
2021-09-23 03:28:18 +00:00
crx_handle.write(crx_bytes)
log.info(f'Downloaded "{crx_filename}".')
2019-06-12 05:41:31 +00:00
def getcrx_argparse(args):
extension_ids = []
if len(args.extension_ids) == 1:
extension_ids.extend(pipeable.input(args.extension_ids[0]))
2019-06-12 05:41:31 +00:00
elif args.extension_ids:
extension_ids.extend(args.extension_ids)
if args.file:
with open(args.file, 'r') as handle:
lines = handle.readlines()
extension_ids.extend(lines)
2021-09-23 03:28:18 +00:00
extension_ids = [x for x in extension_ids if not x.startswith('#')]
extension_ids = [x.rsplit('/', 1)[-1].strip() for x in extension_ids]
2019-06-12 05:41:31 +00:00
if args.overwrite and not args.dont_overwrite:
auto_overwrite = True
elif args.dont_overwrite and not args.overwrite:
auto_overwrite = False
else:
auto_overwrite = None
for extension_id in extension_ids:
try:
2021-09-23 03:28:18 +00:00
download_crx(extension_id, auto_overwrite=auto_overwrite)
2019-06-12 05:41:31 +00:00
except Exception:
if args.fail_early:
raise
else:
2021-09-23 03:28:18 +00:00
log.error(traceback.format_exc())
pipeable.stderr('Resuming...')
return 0
2019-06-12 05:41:31 +00:00
2021-09-23 03:28:18 +00:00
@operatornotify.main_decorator(subject='getcrx')
@vlogging.main_decorator
2019-06-12 05:41:31 +00:00
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('extension_ids', nargs='*', default=None)
parser.add_argument('--file', default=None)
parser.add_argument('--fail_early', '--fail-early', action='store_true')
parser.add_argument('--overwrite', action='store_true')
parser.add_argument('--dont_overwrite', '--dont-overwrite', action='store_true')
2019-06-12 05:41:31 +00:00
parser.set_defaults(func=getcrx_argparse)
args = parser.parse_args(argv)
return args.func(args)
2019-06-12 05:41:31 +00:00
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))