From 8fbe1a2bf4b8030a1f1ced0dc6cc820f2e0065eb Mon Sep 17 00:00:00 2001 From: thezero Date: Sat, 28 Nov 2020 12:52:18 +0100 Subject: [PATCH] more validaitons and optimizations --- README.md | 2 +- docker-compose.yml | 1 + src/download_utils.py | 26 +++++++++++++++++++++++--- src/main.py | 39 ++++++++++++++++++++++----------------- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4b8a032..cd9cf8c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# telegram-bot-youtube-downloader +# telegram-apk-downloader Set in your environment the following variables: - `BOT_TOKEN` telegram token for your bot diff --git a/docker-compose.yml b/docker-compose.yml index f00fb75..cdb7593 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,3 +8,4 @@ services: - ./conf:/bot/conf environment: - CONF_FOLDER=/bot/conf/ + user: 1000:1000 diff --git a/src/download_utils.py b/src/download_utils.py index 0148a1f..9ba09ab 100644 --- a/src/download_utils.py +++ b/src/download_utils.py @@ -1,6 +1,10 @@ import os +import re + from glob import glob, escape from contextlib import contextmanager +from subprocess import check_call +from urllib.parse import urlparse, parse_qs from gplaycli.gplaycli import GPlaycli @@ -13,16 +17,32 @@ class dotdict(dict): __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ +def validate_package_name(package_name): + # https://developer.android.com/studio/build/application-id + """ + - It must have at least two segments (one or more dots). + - Each segment must start with a letter. + - All characters must be alphanumeric or an underscore [a-zA-Z0-9_]. + """ + reg = r"^(?:[a-zA-Z]+(?:\d*[a-zA-Z_]*)*)(?:\.[a-zA-Z]+(?:\d*[a-zA-Z_]*)*)+$" + return re.match(reg, package_name) is not None + class APK: def __init__(self, apk, conf={}, conf_file=None): self.conf = conf self.conf_file = conf_file - if 'play.google.com/store/apps/details?id=' in apk: - self.package_name = apk.split('play.google.com/store/apps/details?id=')[1] + self.package_name = '' + if 'play.google.com/store/apps/details' in apk: + url = urlparse(apk) + if url.netloc == 'play.google.com': + self.package_name = parse_qs(url.query)['id'][0] else: self.package_name = apk def download(self): + if not validate_package_name(self.package_name): + raise BadPackageNameException(f'error downloading {self.package_name}') + cli = GPlaycli(args=dotdict(self.conf), config_file=self.conf_file) cli.download_folder = 'out' d = cli.download([self.package_name]) # returns the number of downloaded packages @@ -35,7 +55,7 @@ class APK: def check_dimension(self): if os.path.getsize(self.file_name) > 50 * 1024 * 1023: - os.system('split -b 49M "{0}" "{1}"'.format(self.file_name, self.file_name)) + check_call(['split', '-b', '49M', self.file_name, self.file_name]) os.remove(self.file_name) return glob(escape(self.file_name) + '*') diff --git a/src/main.py b/src/main.py index e67cb8f..a9e3974 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,6 @@ import logging import os +from subprocess import CalledProcessError from telegram.ext import Updater, MessageHandler, Filters @@ -8,37 +9,32 @@ from download_utils import APK, BadPackageNameException logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) +def error_callback(update, context): + logger.warning('Update "%s" caused error "%s"', update, context.error) -def get_format(update, context): +def get_package(update, context): logger.info("from {}: {}".format(update.message.chat_id, update.message.text)) # "history" if update.message.text == '/start': update.message.reply_text("To start downloading APK file send me a PlayStore link ok a package name. :)") return - conf = { - 'yes': True, - 'verbose': False, - 'tokencachefile': os.path.join(os.environ['CONF_FOLDER'], 'token.cache'), + apk = APK(update.message.text, conf=CONF, conf_file=CONF_FILE) - } - apk = APK(update.message.text, conf=conf, conf_file=os.path.join(os.environ['CONF_FOLDER'], 'gplaycli.conf')) - - msg = context.bot.send_message(text="Downloading...", + msg = context.bot.send_message(text=f"Downloading {apk.package_name}...", chat_id=update.message.chat_id) try: apk.download() + with apk.send() as files: + for f in files: + context.bot.send_document(chat_id=update.message.chat_id, document=open(f, 'rb')) except BadPackageNameException as e: # instead of editing the text message we delete and resend one so a new notification will trigger - context.bot.delete_message(chat_id=update.message.chat_id, - message_id=msg.message_id) context.bot.send_message(text="Bad package name: {}".format(e), chat_id=update.message.chat_id) - return - - with apk.send() as files: - for f in files: - context.bot.send_document(chat_id=update.message.chat_id, document=open(f, 'rb')) + except CalledProcessError: + context.bot.send_message(text="Failed to send APK file", + chat_id=update.message.chat_id) context.bot.delete_message(chat_id=update.message.chat_id, message_id=msg.message_id) @@ -50,9 +46,18 @@ if os.environ.get('CONF_FOLDER') is None: TOKEN = None with open(os.path.join(os.environ['CONF_FOLDER'], 'bot.token')) as f: TOKEN = f.read().strip() + +CONF = { + 'yes': True, + 'verbose': False, + 'tokencachefile': os.path.join(os.environ['CONF_FOLDER'], 'token.cache'), +} +CONF_FILE = os.path.join(os.environ['CONF_FOLDER'], 'gplaycli.conf') + updater = Updater(token=TOKEN) -updater.dispatcher.add_handler(MessageHandler(Filters.text, get_format)) +updater.dispatcher.add_handler(MessageHandler(Filters.text, get_package)) +updater.dispatcher.add_error_handler(error_callback) updater.start_polling() updater.idle()