Separated telegram related code from download upload related code

Signed-off-by: lzzy12 <jhashivam2020@gmail.com>
This commit is contained in:
lzzy12 2019-09-24 00:46:31 +05:30
parent d6b6933ee8
commit 1c56b18724
13 changed files with 363 additions and 297 deletions

5
.gitignore vendored
View File

@ -6,4 +6,7 @@ config.ini
downloads/*
download/*
data*
.vscode
.vscode
.idea
*.json
*.pickle

View File

@ -0,0 +1,2 @@
This is a telegram bot writen in python for mirroring files on the internet to our beloved Google Drive.\n
Documentation is coming as soon as we finish implementing all the basic features of the bot

View File

@ -1 +1 @@
aria2c --enable-rpc --rpc-listen-all=false --rpc-listen-port 6800 --max-connection-per-server=10 --rpc-max-request-size=1024M --seed-time=0.01 --min-split-size=10M --follow-torrent=mem --split=10 --daemon=true --allow-overwrite=true
aria2c --enable-rpc --rpc-listen-all=false --rpc-listen-port 6800 --max-connection-per-server=10 --rpc-max-request-size=1024M --seed-time=0.01 --min-split-size=10M --follow-torrent=mem --split=10 --daemon=true --allow-overwrite=true

View File

@ -1,18 +1,17 @@
import logging
import configparser
from telegram.ext import Updater
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
level=logging.INFO)
config = configparser.ConfigParser()
config.read('bot/config.ini')
def getConfig(name: str):
return config['DEFAULT'][name]
return config['DEFAULT'][name]
LOGGER = logging.getLogger(__name__)
try:
@ -21,13 +20,23 @@ try:
exit()
except KeyError:
pass
BOT_TOKEN = getConfig('BOT_TOKEN')
CLIENT_ID = getConfig('G_DRIVE_CLIENT_ID')
CLIENT_SECRET = getConfig('G_DRIVE_CLIENT_SECRET')
parent_id = getConfig('GDRIVE_FOLDER_ID')
DOWNLOAD_DIR = getConfig('DOWNLOAD_DIR')
if DOWNLOAD_DIR[-1] != '/' or DOWNLOAD_DIR[-1] != '\\':
DOWNLOAD_DIR = DOWNLOAD_DIR + '/'
DOWNLOAD_DIR = None
BOT_TOKEN = None
try:
BOT_TOKEN = getConfig('BOT_TOKEN')
CLIENT_ID = getConfig('G_DRIVE_CLIENT_ID')
CLIENT_SECRET = getConfig('G_DRIVE_CLIENT_SECRET')
parent_id = getConfig('GDRIVE_FOLDER_ID')
DOWNLOAD_DIR = getConfig('DOWNLOAD_DIR')
if DOWNLOAD_DIR[-1] != '/' or DOWNLOAD_DIR[-1] != '\\':
DOWNLOAD_DIR = DOWNLOAD_DIR + '/'
DOWNLOAD_STATUS_UPDATE_INTERVAL = int(getConfig('DOWNLOAD_STATUS_UPDATE_INTERVAL'))
download_list = {}
except KeyError as e:
LOGGER.error("One or more env variables missing! Exiting now")
exit(1)
updater = Updater(token=BOT_TOKEN, use_context=True)
dispatcher = updater.dispatcher
dispatcher = updater.dispatcher

View File

@ -2,16 +2,21 @@ from telegram.ext import CommandHandler, run_async
from bot import dispatcher, LOGGER, updater
import bot.mirror
@run_async
def start(update, context):
print(update)
context.bot.send_message(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
print(update)
context.bot.send_message(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
LOGGER.info('__main__.py')
def main():
start_handler = CommandHandler('start', start)
dispatcher.add_handler(start_handler)
LOGGER.info("Bot Started!")
updater.start_polling()
main()
main()

View File

@ -7,4 +7,5 @@ BOT_TOKEN =
G_DRIVE_CLIENT_ID =
G_DRIVE_CLIENT_SECRET =
GDRIVE_FOLDER_ID =
DOWNLOAD_DIR = ./download
DOWNLOAD_DIR = ./download
DOWNLOAD_STATUS_UPDATE_INTERVAL = 5

View File

@ -1,94 +0,0 @@
import aria2p
import os
from time import sleep
from bot import LOGGER,DOWNLOAD_DIR
EDIT_SLEEP_TIME_OUT = 5
aria2 = aria2p.API(
aria2p.Client(
host="http://localhost",
port=6800,
secret=""
)
)
allDls = {}
def check_metadata(gid):
file = aria2.get_download(gid)
if file.followed_by_ids[0] != None:
new_gid = file.followed_by_ids[0]
LOGGER.info("Changing GID "+gid+" to "+new_gid)
return new_gid
else:
return False
def add_download(link,message):
if "magnet" in link:
download = aria2.add_magnet(link,{'dir':DOWNLOAD_DIR})
allDls[message[0]] = [download,message[1]]
LOGGER.info("Adding: "+link)
return download
else:
download = aria2.add_uris([link],{'dir':DOWNLOAD_DIR})
allDls[message[0]] = [download,message[1]]
LOGGER.info("Adding: "+link)
return download
def remove_download(message):
del allDls[message]
aria2.remove_download(allDls[message].gid)
return True
def get_file_name(download):
file = aria2.get_download(download.gid)
return file.name
def get_download_by_message(message):
if allDls[message] == None:
return None
else:
download = allDls[message]
return download[0]
def progress_status(context,update,previous):
download = get_download_by_message(update)
file = aria2.get_download(download.gid)
if not file.is_complete:
if not file.error_message:
msg = "<i>" + file.name +"</i>:- " + file.progress_string()+" of " + file.total_length_string() + \
" at " + file.download_speed_string() + " ,ETA: " + file.eta_string()
if previous != msg:
LOGGER.info("Editing message")
try:
context.bot.edit_message_text(text=msg,message_id=update.message_id,chat_id=update.chat.id,parse_mode='HTMl')
except:
pass
previous = msg
sleep(5)
return progress_status(context,update,previous)
else:
LOGGER.error(file.error_message)
return
else:
try:
new_gid = check_metadata(file.gid)
except:
new_gid = None
pass
if new_gid:
download = aria2.get_download(new_gid)
allDls[update][0] = download
return progress_status(context,update,previous=None)
else:
LOGGER.info(file.name+" Completed.")
msg = "<i>" + file.name +"</i>:- Uploading."
context.bot.edit_message_text(text=msg,message_id=update.message_id,chat_id=update.chat.id,parse_mode='HTMl')

View File

@ -0,0 +1,8 @@
class Message:
pass
class Download:
def __init__(self, gid, download):
self.isDownloading = download.is_complete
self.gid = gid
self.download = download

View File

@ -0,0 +1,85 @@
import aria2p
from time import sleep
from bot import LOGGER, DOWNLOAD_DIR, DOWNLOAD_STATUS_UPDATE_INTERVAL, download_list
from bot.helper.listeners import *
from urllib.parse import urlparse
from aria2p import Options
def is_url(url: str):
try:
urlparse(url)
return True
except ValueError:
return False
def is_magnet(url: str):
if "magnet" in url:
return True
else:
return False
class DownloadHelper:
def __init__(self, listener: MirrorListeners):
self.__aria2 = aria2p.API(
aria2p.Client(
host="http://localhost",
port=6800,
secret="",
)
)
self.__listener = listener
def add_download(self, link: str):
download = None
if is_url(link):
download = self.__aria2.add_uris([link], {'dir': DOWNLOAD_DIR + str(self.__listener.update.update_id),
'max_download_limit': 1})
elif is_magnet(link):
download = self.__aria2.add_magnet(link, {'dir': DOWNLOAD_DIR})
else:
self.__listener.onDownloadError("No download URL or URL malformed")
return
download_list[self.__listener.update.update_id] = download.gid
self.__listener.onDownloadStarted(link)
self.__update_download_status()
def get_downloads_status_str_list(self):
"""
Generates a human readable string of progress of all the downloads
:return: list of strings of progress of all the downloads
"""
str_list = []
LOGGER.info(download_list)
for gid in list(download_list.values()):
download = self.__aria2.get_download(gid)
str_list.append("<b>" + download.name + "</b>:- "
+ download.progress_string() + " of "
+ download.total_length_string()
+ " at " + download.download_speed_string()
+ " ,ETA: " + download.eta_string()
)
return str_list
def __get_download(self):
return self.__aria2.get_download(download_list[self.__listener.update.update_id])
def __update_download_status(self):
# TODO: Try to find a way to move this code to mirror.py and send only a
# list of Download objects to onDownloadProgress()
previous = None
LOGGER.info(self.get_downloads_status_str_list())
while not self.__get_download().is_complete:
if self.__get_download().has_failed:
self.__listener.onDownloadError(self.__get_download().error_message)
break
progress_str_list = self.get_downloads_status_str_list()
if progress_str_list != previous:
self.__listener.onDownloadProgress(progress_str_list)
previous = progress_str_list
sleep(DOWNLOAD_STATUS_UPDATE_INTERVAL)
self.__listener.onDownloadComplete(self.__get_download())

View File

@ -1,174 +1,150 @@
from apiclient.discovery import build
from apiclient.http import MediaFileUpload
from apiclient.errors import ResumableUploadError
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.file import Storage
from oauth2client import file, client, tools
from oauth2client.client import HttpAccessTokenRefreshError
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.http import MediaFileUpload
import pickle
from mimetypes import guess_type
import httplib2
import os
from bot import LOGGER, CLIENT_ID, CLIENT_SECRET, parent_id, DOWNLOAD_DIR
from bot.helper.exceptions import DriveAuthError
from bot import LOGGER, CLIENT_ID, CLIENT_SECRET, parent_id, DOWNLOAD_DIR, download_list
from .listeners import MirrorListeners
from shutil import rmtree
G_DRIVE_TOKEN_FILE = "auth_token.txt"
# Check https://developers.google.com/drive/scopes for all available scopes
OAUTH_SCOPE = "https://www.googleapis.com/auth/drive.file"
# Redirect URI for installed apps, can be left as is
REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"
G_DRIVE_DIR_MIME_TYPE = "application/vnd.google-apps.folder"
class GoogleDriveHelper:
def __init__(self, listener: MirrorListeners):
self.__G_DRIVE_TOKEN_FILE = "token.pickle"
# Check https://developers.google.com/drive/scopes for all available scopes
self.__OAUTH_SCOPE = "https://www.googleapis.com/auth/drive.file"
# Redirect URI for installed apps, can be left as is
self.__REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"
self.__G_DRIVE_DIR_MIME_TYPE = "application/vnd.google-apps.folder"
self.__G_DRIVE_BASE_DOWNLOAD_URL = "https://drive.google.com/uc?id={}&export=download"
self.__listener = listener
if CLIENT_ID is None or CLIENT_SECRET is None or parent_id is None:
LOGGER.error("Please Setup Config Properly.")
if CLIENT_ID is None or CLIENT_SECRET is None or parent_id is None:
LOGGER.error("Please Setup Config Properly.")
self.__service = self.authorize()
def upload_file(self, file_path, file_name, mime_type, parent_id):
# File body description
media_body = MediaFileUpload(file_path,
mimetype=mime_type,
resumable=True)
body = {
'title': file_name,
'description': 'backup',
'mimeType': mime_type,
}
if parent_id is not None:
body["parents"] = [{"id": parent_id}]
# Permissions body description: anyone who has link can upload
# Other permissions can be found at https://developers.google.com/drive/v2/reference/permissions
permissions = {
'role': 'reader',
'type': 'anyone',
'value': None,
'withLink': True
}
# Insert a file
drive_file = self.__service.files().create(body=body, media_body=media_body).execute()
# Insert new permissions
self.__service.permissions().create(fileId=drive_file['id'], body=permissions).execute()
# Define file instance and get url for download
drive_file = self.__service.files().get(fileId=drive_file['id']).execute()
download_url = self.__G_DRIVE_BASE_DOWNLOAD_URL.format(drive_file.get('id'))
return download_url
def upload(fileName):
filePath = DOWNLOAD_DIR + fileName
try:
with open(G_DRIVE_TOKEN_FILE) as f:
pass
except IOError:
storage = create_token_file(G_DRIVE_TOKEN_FILE)
http = authorize(G_DRIVE_TOKEN_FILE, storage)
def file_ops(self, file_path):
mime_type = guess_type(file_path)[0]
mime_type = mime_type if mime_type else "text/plain"
file_name = file_path.split("/")[-1]
return file_name, mime_type
LOGGER.info("Uploading File: "+fileName)
if os.path.isfile(filePath):
http = authorize(G_DRIVE_TOKEN_FILE, None)
file_name, mime_type = file_ops(filePath)
try:
g_drive_link = upload_file(http, filePath, file_name, mime_type,parent_id)
LOGGER.info("Uploaded To G-Drive: " + filePath)
link = g_drive_link
except Exception as e:
LOGGER.error(str(e))
pass
else:
http = authorize(G_DRIVE_TOKEN_FILE, None)
file_name, mime_type = file_ops(filePath)
try:
dir_id = create_directory(http, os.path.basename(os.path.abspath(fileName)), parent_id)
DoTeskWithDir(http,filePath, dir_id)
LOGGER.info("Uploaded To G-Drive: "+fileName)
dir_link = "https://drive.google.com/folderview?id={}".format(dir_id)
link = dir_link
except Exception as e:
LOGGER.error(str(e))
raise Exception('Error: {}'.format(str(e)))
return link
def upload(self, file_name: str):
self.__listener.onUploadStarted(file_name)
file_dir = "{}/{}".format(DOWNLOAD_DIR, self.__listener.update.update_id)
file_path = "{}/{}".format(file_dir, file_name)
link = None
LOGGER.info("Uploading File: " + file_name)
if os.path.isfile(file_path):
file_name, mime_type = self.file_ops(file_path)
try:
g_drive_link = self.upload_file(file_path, file_name, mime_type, parent_id)
LOGGER.info("Uploaded To G-Drive: " + file_path)
link = g_drive_link
except Exception as e:
LOGGER.error(str(e))
pass
else:
file_name, mime_type = self.file_ops(file_path)
try:
dir_id = self.create_directory(os.path.basename(os.path.abspath(file_name)), parent_id)
self.upload_dir(file_path, dir_id)
LOGGER.info("Uploaded To G-Drive: " + file_name)
link = "https://drive.google.com/folderview?id={}".format(dir_id)
except Exception as e:
LOGGER.error(str(e))
self.__listener.onUploadError(str(e))
raise Exception('Error: {}'.format(str(e)))
del download_list[self.__listener.update.update_id]
LOGGER.info(download_list)
self.__listener.onUploadComplete(link, file_name)
LOGGER.info("Deleting downloaded file/folder..")
rmtree(file_dir)
return link
def create_directory(http, directory_name, parent_id):
drive_service = build("drive", "v2", http=http, cache_discovery=False)
permissions = {
"role": "reader",
"type": "anyone",
"value": None,
"withLink": True
}
file_metadata = {
"title": directory_name,
"mimeType": G_DRIVE_DIR_MIME_TYPE
}
if parent_id is not None:
file_metadata["parents"] = [{"id": parent_id}]
file = drive_service.files().insert(body=file_metadata).execute()
file_id = file.get("id")
drive_service.permissions().insert(fileId=file_id, body=permissions).execute()
LOGGER.info("Created Gdrive Folder:\nName: {}\nID: {} ".format(file.get("title"), file_id))
return file_id
def DoTeskWithDir(http, input_directory, parent_id):
list_dirs = os.listdir(input_directory)
if len(list_dirs) == 0:
return parent_id
r_p_id = None
for a_c_f_name in list_dirs:
current_file_name = os.path.join(input_directory, a_c_f_name)
if os.path.isdir(current_file_name):
current_dir_id = create_directory(http, a_c_f_name, parent_id)
r_p_id = DoTeskWithDir(http, current_file_name,current_dir_id)
else:
file_name, mime_type = file_ops(current_file_name)
# current_file_name will have the full path
g_drive_link = upload_file(http, current_file_name, file_name, mime_type, parent_id)
r_p_id = parent_id
# TODO: there is a #bug here :(
return r_p_id
def file_ops(file_path):
mime_type = guess_type(file_path)[0]
mime_type = mime_type if mime_type else "text/plain"
file_name = file_path.split("/")[-1]
return file_name, mime_type
def create_token_file(token_file):
# Run through the OAuth flow and retrieve credentials
flow = OAuth2WebServerFlow(
CLIENT_ID,
CLIENT_SECRET,
OAUTH_SCOPE,
redirect_uri=REDIRECT_URI
)
authorize_url = flow.step1_get_authorize_url()
print('Go to the following link in your browser: ' + authorize_url)
code = input('Enter verification code: ').strip()
credentials = flow.step2_exchange(code)
storage = Storage(token_file)
storage.put(credentials)
return storage
def authorize(token_file, storage):
# Get credentials
if storage is None:
storage = Storage(token_file)
credentials = storage.get()
# Create an httplib2.Http object and authorize it with our credentials
try:
http = httplib2.Http()
credentials.refresh(http)
http = credentials.authorize(http)
except HttpAccessTokenRefreshError as e:
LOGGER.error(str(e))
LOGGER.info('Deleting {} file'.format(G_DRIVE_TOKEN_FILE))
os.remove(G_DRIVE_TOKEN_FILE)
raise DriveAuthError(str(e))
return http
def upload_file(http, file_path,file_name, mime_type, parent_id):
# Create Google Drive service instance
drive_service = build('drive', 'v2', http=http, cache_discovery=False)
# File body description
media_body = MediaFileUpload(file_path,
mimetype=mime_type,
resumable=True)
body = {
'title': file_name,
'description': 'backup',
'mimeType': mime_type,
}
if parent_id is not None:
body["parents"] = [{"id": parent_id}]
# Permissions body description: anyone who has link can upload
# Other permissions can be found at https://developers.google.com/drive/v2/reference/permissions
permissions = {
'role': 'reader',
'type': 'anyone',
'value': None,
'withLink': True
}
# Insert a file
file = drive_service.files().insert(body=body, media_body=media_body).execute()
# Insert new permissions
drive_service.permissions().insert(fileId=file['id'], body=permissions).execute()
# Define file instance and get url for download
file = drive_service.files().get(fileId=file['id']).execute()
download_url = file.get('webContentLink')
return download_url
def create_directory(self, directory_name, parent_id):
permissions = {
"role": "reader",
"type": "anyone",
"value": None,
"withLink": True
}
file_metadata = {
"title": directory_name,
"mimeType": self.__G_DRIVE_DIR_MIME_TYPE
}
if parent_id is not None:
file_metadata["parents"] = [{"id": parent_id}]
file = self.__service.files().create(body=file_metadata).execute()
file_id = file.get("id")
self.__service.permissions().create(fileId=file_id, body=permissions).execute()
LOGGER.info("Created Google-Drive Folder:\nName: {}\nID: {} ".format(file.get("title"), file_id))
return file_id
def upload_dir(self, input_directory, parent_id):
list_dirs = os.listdir(input_directory)
if len(list_dirs) == 0:
return parent_id
r_p_id = None
for a_c_f_name in list_dirs:
current_file_name = os.path.join(input_directory, a_c_f_name)
if os.path.isdir(current_file_name):
current_dir_id = self.create_directory(a_c_f_name, parent_id)
r_p_id = self.upload_dir(current_file_name, current_dir_id)
else:
file_name, mime_type = self.file_ops(current_file_name)
# current_file_name will have the full path
self.upload_file(current_file_name, file_name, mime_type, parent_id)
r_p_id = parent_id
return r_p_id
def authorize(self):
# Get credentials
credentials = None
if os.path.exists(self.__G_DRIVE_TOKEN_FILE):
with open(self.__G_DRIVE_TOKEN_FILE, 'rb') as f:
credentials = pickle.load(f)
if credentials is None or not credentials.valid:
if credentials and credentials.expired and credentials.refresh_token:
credentials.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', self.__OAUTH_SCOPE)
LOGGER.info(flow)
credentials = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(self.__G_DRIVE_TOKEN_FILE, 'wb') as token:
pickle.dump(credentials, token)
return build('drive', 'v3', credentials=credentials, cache_discovery=False)

26
bot/helper/listeners.py Normal file
View File

@ -0,0 +1,26 @@
class MirrorListeners:
def __init__(self, context, update, reply_message):
self.context = context
self.update = update
self.reply_message = reply_message
def onDownloadStarted(self, link: str):
raise NotImplementedError
def onDownloadProgress(self, progress_str_list: list):
raise NotImplementedError
def onDownloadComplete(self, download):
raise NotImplementedError
def onDownloadError(self, error: str):
raise NotImplementedError
def onUploadStarted(self, filename: str):
raise NotImplementedError
def onUploadComplete(self, link: str, file_name: str):
raise NotImplementedError
def onUploadError(self, error: str):
raise NotImplementedError

View File

@ -1,27 +1,72 @@
from telegram.ext import CommandHandler, run_async
from bot.helper import ariaTools, gdriveTools
from bot.helper import download_tools, gdriveTools, listeners
from bot import config, LOGGER, dispatcher
from bot.helper.exceptions import DriveAuthError
LOGGER.info('mirror.py')
class MirrorListener(listeners.MirrorListeners):
def __init__(self, context, update, reply_message):
super().__init__(context, update, reply_message)
def onDownloadStarted(self, link):
LOGGER.info("Adding link: " + link)
def onDownloadProgress(self, progress_str_list: list):
LOGGER.info("Editing message")
msg = "Status:\n"
for progress_str in progress_str_list:
msg += progress_str + '\n\n'
self.context.bot.edit_message_text(text=msg, message_id=self.reply_message.message_id,
chat_id=self.reply_message.chat.id,
parse_mode='HTMl')
def onDownloadComplete(self, download):
msg = "Downloaded"
LOGGER.info("Download completed")
self.context.bot.edit_message_text(text=msg, message_id=self.reply_message.message_id,
chat_id=self.reply_message.chat.id,
parse_mode='HTMl')
gdrive = gdriveTools.GoogleDriveHelper(self)
gdrive.upload(download.name)
def onDownloadError(self, error):
LOGGER.error(error)
self.context.bot.edit_message_text(text=error, message_id=self.reply_message.message_id,
chat_id=self.reply_message.chat.id,
parse_mode='HTMl')
def onUploadStarted(self, filename: str):
msg = "<i>" + filename + "</i>:- Uploading."
self.context.bot.edit_message_text(text=msg, message_id=self.reply_message.message_id,
chat_id=self.reply_message.chat.id,
parse_mode='HTMl')
def onUploadComplete(self, link: str, file_name: str):
msg = '<a href="{}">{}</a>'.format(link, file_name)
self.context.bot.delete_message(chat_id=self.reply_message.chat.id, message_id=self.reply_message.message_id)
self.context.bot.send_message(chat_id=self.update.message.chat_id,
reply_to_message_id=self.update.message.message_id,
text=msg, parse_mode='HTMl')
def onUploadError(self, error: str):
LOGGER.error(error)
self.context.bot.edit_message_text(text=error, message_id=self.reply_message.message_id,
chat_id=self.reply_message.chat.id,
parse_mode='HTMl')
@run_async
def mirror(update,context):
message = update.message.text
link = message.replace('/mirror','')[1:]
reply_msg = context.bot.send_message(chat_id=update.message.chat_id, reply_to_message_id=update.message.message_id, text="Starting Download")
download = ariaTools.add_download(link,[reply_msg,update])
ariaTools.progress_status(context,reply_msg,previous=None)
file_name = ariaTools.get_file_name(download)
LOGGER.info("File-Name: "+file_name)
try:
link = gdriveTools.upload(file_name)
msg = '<a href="{}">{}</a>'.format(link,file_name)
except DriveAuthError as e:
msg = 'Authentication error: {}'.format(str(e))
except Exception as e:
msg = str(e)
context.bot.edit_message_text(text=msg,message_id=reply_msg.message_id,chat_id=reply_msg.chat.id,parse_mode='HTMl')
def mirror(update, context):
message = update.message.text
link = message.replace('/mirror', '')[1:]
reply_msg = context.bot.send_message(chat_id=update.message.chat_id, reply_to_message_id=update.message.message_id,
text="Starting Download")
listener = MirrorListener(context, update, reply_msg)
aria = download_tools.DownloadHelper(listener)
aria.add_download(link)
mirror_handler = CommandHandler('mirror', mirror)
dispatcher.add_handler(mirror_handler)
dispatcher.add_handler(mirror_handler)

View File

@ -1,7 +1,7 @@
python-telegram-bot
google-api-python-client>=1.7.3
google-auth>=1.5.0
google-auth-httplib2<=0.0.3
google-auth-oauthlib
httplib2>=0.11.3
more-itertools>=4.2.0
ndg-httpsclient>=0.4.0