cmd/fdroidapk.py

135 lines
3.9 KiB
Python
Raw Normal View History

2021-05-07 02:34:55 +00:00
'''
fdroidapk - F-Droid APK downloader
==================================
> fdroidapk package_names <flags>
package_names:
One or more package names to download, separated by spaces. You can find
the package name in the URL on f-droid.org.
For example, com.nutomic.syncthingandroid from the URL
https://f-droid.org/en/packages/com.nutomic.syncthingandroid/
--destination path:
Alternative path to download the apk files to. Default is cwd.
--folders:
If provided, each apk will be downloaded into a separate folder named after
the package.
--debug:
Add this flag to see more detailed information.
--operatornotify:
Add this flag to get any warning messages sent to your operatornotify.
See voussoirkit.operatornotify.py for details.
'''
2021-02-26 08:40:22 +00:00
import argparse
import bs4
import requests
import sys
import time
from voussoirkit import backoff
from voussoirkit import betterhelp
from voussoirkit import downloady
2021-05-07 02:34:55 +00:00
from voussoirkit import operatornotify
2021-02-26 08:40:22 +00:00
from voussoirkit import pathclass
2021-06-22 05:21:06 +00:00
from voussoirkit import pipeable
2021-02-26 08:40:22 +00:00
from voussoirkit import vlogging
2021-05-07 02:34:55 +00:00
log = vlogging.getLogger(__name__, 'fdroidapk')
2021-02-26 08:40:22 +00:00
session = requests.Session()
def get_apk_url(package_name):
url = f'https://f-droid.org/en/packages/{package_name}'
log.debug('Downloading page %s', url)
response = session.get(url, timeout=30)
response.raise_for_status()
soup = bs4.BeautifulSoup(response.text, 'html.parser')
li = soup.find('li', {'class': 'package-version'})
aa = li.find_all('a')
aa = [a for a in aa if a.get('href', '').endswith('.apk')]
apk_url = aa[0]['href']
return apk_url
def normalize_package_name(package_name):
package_name = package_name.strip()
# If it happens to be a URL.
package_name = package_name.strip('/')
package_name = package_name.rsplit('/', 1)[-1]
return package_name
2021-05-07 02:34:55 +00:00
def _retry_request(f, tries=5):
2021-02-26 08:40:22 +00:00
bo = backoff.Linear(m=3, b=3, max=30)
while tries > 0:
try:
return f()
except requests.exceptions.ConnectionError as exc:
if tries == 1:
raise exc
log.debug(exc)
time.sleep(bo.next())
tries -= 1
2021-06-22 05:21:06 +00:00
@pipeable.ctrlc_return1
2021-02-26 08:40:22 +00:00
def fpk_argparse(args):
destination = pathclass.Path(args.destination)
destination.assert_is_directory()
2021-05-07 02:34:55 +00:00
return_status = 0
2021-02-26 08:40:22 +00:00
for package in args.packages:
package = normalize_package_name(package)
2021-05-07 02:34:55 +00:00
try:
apk_url = _retry_request(lambda: get_apk_url(package))
except Exception:
log.error('%s was unable to get apk url.', package)
return_status = 1
continue
2021-02-26 08:40:22 +00:00
apk_basename = downloady.basename_from_url(apk_url)
if args.folders:
this_dest = destination.with_child(package)
this_dest.makedirs(exist_ok=True)
else:
this_dest = destination
this_dest = this_dest.with_child(apk_basename)
2021-05-07 02:34:55 +00:00
2021-02-26 08:40:22 +00:00
if this_dest.exists:
log.info('%s exists.', this_dest.absolute_path)
continue
2021-05-07 02:34:55 +00:00
log.info('Downloading %s.', this_dest.absolute_path)
try:
_retry_request(lambda: downloady.download_file(
apk_url,
this_dest,
callback_progress=downloady.Progress2,
timeout=30,
))
except Exception:
log.error('%s was unable to download apk.', package)
return_status = 1
continue
return return_status
2021-02-26 08:40:22 +00:00
@operatornotify.main_decorator(subject='fdroidapk.py')
@vlogging.main_decorator
2021-02-26 08:40:22 +00:00
def main(argv):
2021-05-07 02:34:55 +00:00
parser = argparse.ArgumentParser(description=__doc__)
2021-02-26 08:40:22 +00:00
parser.add_argument('packages', nargs='+')
parser.add_argument('--folders', action='store_true')
parser.add_argument('--destination', default='.')
parser.set_defaults(func=fpk_argparse)
return betterhelp.single_main(argv, parser, __doc__)
2021-02-26 08:40:22 +00:00
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))