Browse Source

more validaitons and optimizations

thezero 11 months ago
parent
commit
8fbe1a2bf4
4 changed files with 47 additions and 21 deletions
  1. 1 1
      README.md
  2. 1 0
      docker-compose.yml
  3. 23 3
      src/download_utils.py
  4. 22 17
      src/main.py

+ 1 - 1
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

+ 1 - 0
docker-compose.yml

@@ -8,3 +8,4 @@ services:
       - ./conf:/bot/conf
     environment:
       - CONF_FOLDER=/bot/conf/
+    user: 1000:1000

+ 23 - 3
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) + '*')
 

+ 22 - 17
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()