2020-04-23 02:35:13 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Telegram Download Daemon
|
|
|
|
# Author: Alfonso E.M. <alfonso@el-magnifico.org>
|
|
|
|
# You need to install telethon (and cryptg to speed up downloads)
|
|
|
|
|
2021-05-10 17:02:16 +08:00
|
|
|
from os import getenv, path
|
2021-03-09 03:54:24 +08:00
|
|
|
from shutil import move
|
2020-11-09 00:25:17 +08:00
|
|
|
import subprocess
|
2021-01-04 00:14:06 +08:00
|
|
|
import math
|
2021-03-17 02:51:35 +08:00
|
|
|
import time
|
2021-05-10 17:02:16 +08:00
|
|
|
import random
|
|
|
|
import string
|
2021-06-15 16:24:34 +08:00
|
|
|
import os.path
|
2021-07-05 19:17:35 +08:00
|
|
|
from mimetypes import guess_extension
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
from sessionManager import getSession, saveSession
|
|
|
|
|
2021-12-03 16:28:08 +08:00
|
|
|
from telethon import TelegramClient, events, __version__
|
2020-11-07 23:04:50 +08:00
|
|
|
from telethon.tl.types import PeerChannel, DocumentAttributeFilename, DocumentAttributeVideo
|
2020-04-23 02:35:13 +08:00
|
|
|
import logging
|
|
|
|
|
2020-05-24 23:32:29 +08:00
|
|
|
logging.basicConfig(format='[%(levelname) 5s/%(asctime)s]%(name)s:%(message)s',
|
|
|
|
level=logging.WARNING)
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-05-26 11:45:59 +08:00
|
|
|
import multiprocessing
|
2020-04-24 00:32:42 +08:00
|
|
|
import argparse
|
2020-04-24 17:35:53 +08:00
|
|
|
import asyncio
|
2020-04-24 00:32:42 +08:00
|
|
|
|
2021-02-21 20:50:44 +08:00
|
|
|
|
2021-12-03 16:28:08 +08:00
|
|
|
TDD_VERSION="1.13"
|
2021-02-21 20:50:44 +08:00
|
|
|
|
2020-05-10 12:43:13 +08:00
|
|
|
TELEGRAM_DAEMON_API_ID = getenv("TELEGRAM_DAEMON_API_ID")
|
|
|
|
TELEGRAM_DAEMON_API_HASH = getenv("TELEGRAM_DAEMON_API_HASH")
|
|
|
|
TELEGRAM_DAEMON_CHANNEL = getenv("TELEGRAM_DAEMON_CHANNEL")
|
2020-04-24 00:32:42 +08:00
|
|
|
|
2020-05-10 12:43:13 +08:00
|
|
|
TELEGRAM_DAEMON_SESSION_PATH = getenv("TELEGRAM_DAEMON_SESSION_PATH")
|
2020-04-24 00:32:42 +08:00
|
|
|
|
2021-01-28 00:35:18 +08:00
|
|
|
TELEGRAM_DAEMON_DEST=getenv("TELEGRAM_DAEMON_DEST", "/telegram-downloads")
|
|
|
|
TELEGRAM_DAEMON_TEMP=getenv("TELEGRAM_DAEMON_TEMP", "")
|
2021-07-07 19:45:11 +08:00
|
|
|
TELEGRAM_DAEMON_DUPLICATES=getenv("TELEGRAM_DAEMON_DUPLICATES", "rename")
|
2021-01-28 00:35:18 +08:00
|
|
|
|
2021-01-28 00:52:39 +08:00
|
|
|
TELEGRAM_DAEMON_TEMP_SUFFIX="tdd"
|
2021-01-28 00:35:18 +08:00
|
|
|
|
2020-05-24 23:32:29 +08:00
|
|
|
parser = argparse.ArgumentParser(
|
2021-07-07 18:44:55 +08:00
|
|
|
description="Script to download files from a Telegram Channel.")
|
2020-05-24 23:32:29 +08:00
|
|
|
parser.add_argument(
|
|
|
|
"--api-id",
|
|
|
|
required=TELEGRAM_DAEMON_API_ID == None,
|
|
|
|
type=int,
|
|
|
|
default=TELEGRAM_DAEMON_API_ID,
|
|
|
|
help=
|
|
|
|
'api_id from https://core.telegram.org/api/obtaining_api_id (default is TELEGRAM_DAEMON_API_ID env var)'
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--api-hash",
|
|
|
|
required=TELEGRAM_DAEMON_API_HASH == None,
|
|
|
|
type=str,
|
|
|
|
default=TELEGRAM_DAEMON_API_HASH,
|
|
|
|
help=
|
|
|
|
'api_hash from https://core.telegram.org/api/obtaining_api_id (default is TELEGRAM_DAEMON_API_HASH env var)'
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--dest",
|
|
|
|
type=str,
|
2021-01-28 00:35:18 +08:00
|
|
|
default=TELEGRAM_DAEMON_DEST,
|
|
|
|
help=
|
|
|
|
'Destination path for downloaded files (default is /telegram-downloads).')
|
|
|
|
parser.add_argument(
|
|
|
|
"--temp",
|
|
|
|
type=str,
|
2021-02-11 21:06:08 +08:00
|
|
|
default=TELEGRAM_DAEMON_TEMP,
|
2020-05-24 23:32:29 +08:00
|
|
|
help=
|
2021-01-28 00:35:18 +08:00
|
|
|
'Destination path for temporary files (default is using the same downloaded files directory).')
|
2020-05-24 23:32:29 +08:00
|
|
|
parser.add_argument(
|
|
|
|
"--channel",
|
|
|
|
required=TELEGRAM_DAEMON_CHANNEL == None,
|
|
|
|
type=int,
|
|
|
|
default=TELEGRAM_DAEMON_CHANNEL,
|
|
|
|
help=
|
|
|
|
'Channel id to download from it (default is TELEGRAM_DAEMON_CHANNEL env var'
|
|
|
|
)
|
2021-07-07 19:45:11 +08:00
|
|
|
parser.add_argument(
|
|
|
|
"--duplicates",
|
|
|
|
choices=["ignore", "rename", "overwrite"],
|
|
|
|
type=str,
|
|
|
|
default=TELEGRAM_DAEMON_DUPLICATES,
|
|
|
|
help=
|
|
|
|
'"ignore"=do not download duplicated files, "rename"=add a random suffix, "overwrite"=redownload and overwrite.'
|
|
|
|
)
|
2020-04-24 00:32:42 +08:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
api_id = args.api_id
|
|
|
|
api_hash = args.api_hash
|
|
|
|
channel_id = args.channel
|
|
|
|
downloadFolder = args.dest
|
2021-01-28 00:52:39 +08:00
|
|
|
tempFolder = args.temp
|
2021-07-07 19:45:11 +08:00
|
|
|
duplicates=args.duplicates
|
2020-05-26 11:45:59 +08:00
|
|
|
worker_count = multiprocessing.cpu_count()
|
2021-03-17 02:51:35 +08:00
|
|
|
updateFrequency = 10
|
|
|
|
lastUpdate = 0
|
|
|
|
#multiprocessing.Value('f', 0)
|
2020-04-24 00:32:42 +08:00
|
|
|
|
2021-02-11 21:06:08 +08:00
|
|
|
if not tempFolder:
|
|
|
|
tempFolder = downloadFolder
|
|
|
|
|
2020-04-23 02:35:13 +08:00
|
|
|
# Edit these lines:
|
2020-04-24 00:32:42 +08:00
|
|
|
proxy = None
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-05-24 23:32:29 +08:00
|
|
|
# End of interesting parameters
|
2021-02-21 21:27:32 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
async def sendHelloMessage(client, peerChannel):
|
|
|
|
entity = await client.get_entity(peerChannel)
|
2021-12-03 16:28:08 +08:00
|
|
|
print("Telegram Download Daemon "+TDD_VERSION+" using Telethon "+__version__)
|
|
|
|
await client.send_message(entity, "Telegram Download Daemon "+TDD_VERSION+" using Telethon "+__version__)
|
2020-05-25 00:25:39 +08:00
|
|
|
await client.send_message(entity, "Hi! Ready for your files!")
|
2020-05-25 01:09:31 +08:00
|
|
|
|
2020-05-24 23:32:29 +08:00
|
|
|
|
2021-02-16 03:11:01 +08:00
|
|
|
async def log_reply(message, reply):
|
2020-05-24 23:25:57 +08:00
|
|
|
print(reply)
|
2021-02-16 03:11:01 +08:00
|
|
|
await message.edit(reply)
|
2021-05-10 17:02:16 +08:00
|
|
|
|
|
|
|
def getRandomId(len):
|
|
|
|
chars=string.ascii_lowercase + string.digits
|
|
|
|
return ''.join(random.choice(chars) for x in range(len))
|
2021-02-09 02:28:54 +08:00
|
|
|
|
2020-05-25 00:09:45 +08:00
|
|
|
def getFilename(event: events.NewMessage.Event):
|
2021-02-15 12:46:44 +08:00
|
|
|
mediaFileName = "unknown"
|
2021-05-10 17:02:16 +08:00
|
|
|
|
2021-07-12 02:37:18 +08:00
|
|
|
if hasattr(event.media, 'photo'):
|
|
|
|
mediaFileName = str(event.media.photo.id)+".jpeg"
|
|
|
|
elif hasattr(event.media, 'document'):
|
|
|
|
for attribute in event.media.document.attributes:
|
|
|
|
if isinstance(attribute, DocumentAttributeFilename):
|
|
|
|
mediaFileName=attribute.file_name
|
|
|
|
break
|
|
|
|
if isinstance(attribute, DocumentAttributeVideo):
|
|
|
|
if event.original_update.message.message != '':
|
|
|
|
mediaFileName = event.original_update.message.message
|
|
|
|
else:
|
|
|
|
mediaFileName = str(event.message.media.document.id)
|
|
|
|
mediaFileName+=guess_extension(event.message.media.document.mime_type)
|
|
|
|
|
2021-06-30 19:18:35 +08:00
|
|
|
mediaFileName="".join(c for c in mediaFileName if c.isalnum() or c in "()._- ")
|
2021-07-07 19:45:11 +08:00
|
|
|
|
2021-02-15 12:46:44 +08:00
|
|
|
return mediaFileName
|
2020-05-25 00:09:45 +08:00
|
|
|
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2021-01-04 00:14:06 +08:00
|
|
|
in_progress={}
|
|
|
|
|
2021-02-16 03:46:21 +08:00
|
|
|
async def set_progress(filename, message, received, total):
|
2021-03-17 02:51:35 +08:00
|
|
|
global lastUpdate
|
|
|
|
global updateFrequency
|
|
|
|
|
2021-01-04 00:14:06 +08:00
|
|
|
if received >= total:
|
|
|
|
try: in_progress.pop(filename)
|
|
|
|
except: pass
|
|
|
|
return
|
2021-02-16 12:57:20 +08:00
|
|
|
percentage = math.trunc(received / total * 10000) / 100
|
2021-01-04 00:14:06 +08:00
|
|
|
|
2021-02-21 21:27:32 +08:00
|
|
|
progress_message= "{0} % ({1} / {2})".format(percentage, received, total)
|
|
|
|
in_progress[filename] = progress_message
|
2021-01-04 00:14:06 +08:00
|
|
|
|
2021-03-17 02:51:35 +08:00
|
|
|
currentTime=time.time()
|
|
|
|
if (currentTime - lastUpdate) > updateFrequency:
|
2021-02-21 21:58:39 +08:00
|
|
|
await log_reply(message, progress_message)
|
2021-03-17 02:51:35 +08:00
|
|
|
lastUpdate=currentTime
|
2021-02-16 03:46:21 +08:00
|
|
|
|
|
|
|
|
2020-05-24 23:32:29 +08:00
|
|
|
with TelegramClient(getSession(), api_id, api_hash,
|
|
|
|
proxy=proxy).start() as client:
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
saveSession(client.session)
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-05-26 11:45:59 +08:00
|
|
|
queue = asyncio.Queue()
|
2020-04-24 17:35:53 +08:00
|
|
|
peerChannel = PeerChannel(channel_id)
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
@client.on(events.NewMessage())
|
|
|
|
async def handler(event):
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
if event.to_id != peerChannel:
|
|
|
|
return
|
2020-05-24 23:32:29 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
print(event)
|
2021-02-21 21:58:39 +08:00
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
if not event.media and event.message:
|
|
|
|
command = event.message.message
|
|
|
|
command = command.lower()
|
|
|
|
output = "Unknown command"
|
|
|
|
|
|
|
|
if command == "list":
|
2021-02-23 19:55:17 +08:00
|
|
|
output = subprocess.run(["ls -l "+downloadFolder], shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT).stdout.decode('utf-8')
|
2021-02-21 21:58:39 +08:00
|
|
|
elif command == "status":
|
|
|
|
try:
|
|
|
|
output = "".join([ "{0}: {1}\n".format(key,value) for (key, value) in in_progress.items()])
|
|
|
|
if output:
|
|
|
|
output = "Active downloads:\n\n" + output
|
|
|
|
else:
|
|
|
|
output = "No active downloads"
|
|
|
|
except:
|
|
|
|
output = "Some error occured while checking the status. Retry."
|
|
|
|
elif command == "clean":
|
|
|
|
output = "Cleaning "+tempFolder+"\n"
|
2021-02-23 19:55:17 +08:00
|
|
|
output+=subprocess.run(["rm "+tempFolder+"/*."+TELEGRAM_DAEMON_TEMP_SUFFIX], shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT).stdout
|
2021-02-21 21:58:39 +08:00
|
|
|
else:
|
|
|
|
output = "Available commands: list, status, clean"
|
|
|
|
|
|
|
|
await log_reply(event, output)
|
|
|
|
|
|
|
|
if event.media:
|
2021-07-12 02:37:18 +08:00
|
|
|
if hasattr(event.media, 'document') or hasattr(event.media,'photo'):
|
2021-06-10 21:01:24 +08:00
|
|
|
filename=getFilename(event)
|
2021-07-07 19:45:11 +08:00
|
|
|
if ( path.exists("{0}/{1}.{2}".format(tempFolder,filename,TELEGRAM_DAEMON_TEMP_SUFFIX)) or path.exists("{0}/{1}".format(downloadFolder,filename)) ) and duplicates == "ignore":
|
|
|
|
message=await event.reply("{0} already exists. Ignoring it.".format(filename))
|
|
|
|
else:
|
|
|
|
message=await event.reply("{0} added to queue".format(filename))
|
|
|
|
await queue.put([event, message])
|
2021-06-10 21:01:24 +08:00
|
|
|
else:
|
|
|
|
message=await event.reply("That is not downloadable. Try to send it as a file.")
|
|
|
|
|
2021-02-21 21:58:39 +08:00
|
|
|
except Exception as e:
|
|
|
|
print('Events handler error: ', e)
|
2020-05-26 11:45:59 +08:00
|
|
|
|
|
|
|
async def worker():
|
|
|
|
while True:
|
2021-02-21 21:58:39 +08:00
|
|
|
try:
|
|
|
|
element = await queue.get()
|
|
|
|
event=element[0]
|
|
|
|
message=element[1]
|
2020-05-26 17:33:09 +08:00
|
|
|
|
2021-02-21 21:58:39 +08:00
|
|
|
filename=getFilename(event)
|
2021-07-07 19:45:11 +08:00
|
|
|
fileName, fileExtension = os.path.splitext(filename)
|
|
|
|
tempfilename=fileName+"-"+getRandomId(8)+fileExtension
|
|
|
|
|
|
|
|
if path.exists("{0}/{1}.{2}".format(tempFolder,tempfilename,TELEGRAM_DAEMON_TEMP_SUFFIX)) or path.exists("{0}/{1}".format(downloadFolder,filename)):
|
|
|
|
if duplicates == "rename":
|
|
|
|
filename=tempfilename
|
2020-04-23 02:35:13 +08:00
|
|
|
|
2021-07-12 02:37:18 +08:00
|
|
|
|
|
|
|
if hasattr(event.media, 'photo'):
|
|
|
|
size = 0
|
|
|
|
else:
|
|
|
|
size=event.media.document.size
|
|
|
|
|
2021-02-21 21:58:39 +08:00
|
|
|
await log_reply(
|
|
|
|
message,
|
2021-07-12 02:37:18 +08:00
|
|
|
"Downloading file {0} ({1} bytes)".format(filename,size)
|
2021-02-21 21:58:39 +08:00
|
|
|
)
|
2021-01-04 00:14:06 +08:00
|
|
|
|
2021-02-21 21:58:39 +08:00
|
|
|
download_callback = lambda received, total: set_progress(filename, message, received, total)
|
2020-05-26 11:45:59 +08:00
|
|
|
|
2021-02-21 21:58:39 +08:00
|
|
|
await client.download_media(event.message, "{0}/{1}.{2}".format(tempFolder,filename,TELEGRAM_DAEMON_TEMP_SUFFIX), progress_callback = download_callback)
|
|
|
|
set_progress(filename, message, 100, 100)
|
2021-03-09 03:54:24 +08:00
|
|
|
move("{0}/{1}.{2}".format(tempFolder,filename,TELEGRAM_DAEMON_TEMP_SUFFIX), "{0}/{1}".format(downloadFolder,filename))
|
2021-02-21 21:58:39 +08:00
|
|
|
await log_reply(message, "{0} ready".format(filename))
|
2020-05-26 11:45:59 +08:00
|
|
|
|
2021-02-21 21:58:39 +08:00
|
|
|
queue.task_done()
|
|
|
|
except Exception as e:
|
2021-06-14 22:41:21 +08:00
|
|
|
try: await log_reply(message, "Error: {}".format(str(e))) # If it failed, inform the user about it.
|
|
|
|
except: pass
|
2021-02-21 21:58:39 +08:00
|
|
|
print('Queue worker error: ', e)
|
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
async def start():
|
2021-03-17 02:51:35 +08:00
|
|
|
|
2020-05-26 11:45:59 +08:00
|
|
|
tasks = []
|
2020-11-07 22:12:17 +08:00
|
|
|
loop = asyncio.get_event_loop()
|
2020-05-26 11:45:59 +08:00
|
|
|
for i in range(worker_count):
|
2020-11-07 22:12:17 +08:00
|
|
|
task = loop.create_task(worker())
|
2020-05-26 11:45:59 +08:00
|
|
|
tasks.append(task)
|
2020-04-24 17:35:53 +08:00
|
|
|
await sendHelloMessage(client, peerChannel)
|
|
|
|
await client.run_until_disconnected()
|
2020-05-26 11:45:59 +08:00
|
|
|
for task in tasks:
|
|
|
|
task.cancel()
|
|
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
2020-05-24 23:32:29 +08:00
|
|
|
|
2020-04-24 17:35:53 +08:00
|
|
|
client.loop.run_until_complete(start())
|