download_utils.py 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import os
  2. import re
  3. from glob import glob, escape
  4. from contextlib import contextmanager
  5. from subprocess import check_call
  6. from urllib.parse import urlparse, parse_qs
  7. from gplaycli.gplaycli import GPlaycli
  8. class BadPackageNameException(Exception):
  9. pass
  10. class dotdict(dict):
  11. """dot.notation access to dictionary attributes"""
  12. __getattr__ = dict.get
  13. __setattr__ = dict.__setitem__
  14. __delattr__ = dict.__delitem__
  15. def validate_package_name(package_name):
  16. # https://developer.android.com/studio/build/application-id
  17. """
  18. - It must have at least two segments (one or more dots).
  19. - Each segment must start with a letter.
  20. - All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
  21. """
  22. reg = r"^(?:[a-zA-Z]+(?:\d*[a-zA-Z_]*)*)(?:\.[a-zA-Z]+(?:\d*[a-zA-Z_]*)*)+$"
  23. return re.match(reg, package_name) is not None
  24. class APK:
  25. def __init__(self, apk, conf={}, conf_file=None):
  26. self.conf = conf
  27. self.conf_file = conf_file
  28. self.package_name = ''
  29. if 'play.google.com/store/apps/details' in apk:
  30. url = urlparse(apk)
  31. if url.netloc == 'play.google.com':
  32. self.package_name = parse_qs(url.query)['id'][0]
  33. else:
  34. self.package_name = apk
  35. def download(self):
  36. if not validate_package_name(self.package_name):
  37. raise BadPackageNameException(f'error downloading {self.package_name}')
  38. cli = GPlaycli(args=dotdict(self.conf), config_file=self.conf_file)
  39. cli.download_folder = 'out'
  40. d = cli.download([self.package_name]) # returns the number of downloaded packages
  41. if len(d) == 0:
  42. raise BadPackageNameException(f'error downloading {self.package_name}')
  43. fname = os.path.join('out', self.package_name + '.apk')
  44. if os.path.isfile(fname):
  45. self.file_name = fname
  46. def check_dimension(self):
  47. if os.path.getsize(self.file_name) > 50 * 1024 * 1023:
  48. check_call(['split', '-b', '49M', self.file_name, self.file_name])
  49. os.remove(self.file_name)
  50. return glob(escape(self.file_name) + '*')
  51. @contextmanager
  52. def send(self):
  53. files = self.check_dimension() # split if size >= 50MB
  54. yield files
  55. for f in files: # removing old files
  56. os.remove(f)