Dr.Caduceus commited on
Commit
35e0008
·
unverified ·
1 Parent(s): f0df283

Bump to v1.7 (Migrate to Hydrogram)

Browse files
README.md CHANGED
@@ -1,7 +1,5 @@
1
  <div align="center"><h1>🌐File Stream Bot</h1>
2
  <b>An open-source Python Telegram bot to transmit Telegram files over HTTP.</b>
3
-
4
- <a href="https://t.me/DrFileStreamBot"><b>Demo Bot</b></a>
5
  </div><br>
6
 
7
  ## **📑 INDEX**
@@ -34,7 +32,7 @@ For Linux:
34
  ```
35
  sudo apt-get update && sudo apt-get install -y python3.11 git pip
36
  ```
37
- For macOS:
38
  ```
39
  brew install python@3.11 git
40
  ```
@@ -71,13 +69,13 @@ pip install -r requirements.txt
71
  **The variables provided below should either be completed within the [config.py](https://github.com/TheCaduceus/FileStreamBot/blob/main/bot/config.py) file or configured as environment variables.**
72
  * `API_ID`|`TELEGRAM_API_ID`: API ID of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `int`
73
  * `API_HASH`|`TELEGRAM_API_HASH`: API hash of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `str`
74
- * `OWNER_ID`: ID of your Telegram account, can be obtained by sending **/info** to [@DrFileStreamBot](https://t.me/DrFileStreamBot). `int`
75
  * `ALLOWED_USER_IDS`: A list of Telegram account IDs (separated by spaces) that are permitted to use the bot. Leave this field empty to allow anyone to use it. `str`
76
  * `BOT_USERNAME`|`TELEGRAM_BOT_USERNAME`: Username of your Telegram bot, create one using [@BotFather](https://t.me/BotFather). `str`
77
  * `BOT_TOKEN`|`TELEGRAM_BOT_TOKEN`: Telegram API token of your bot, can be obtained from [@BotFather](https://t.me/BotFather). `str`
78
- * `CHANNEL_ID`|`TELEGRAM_CHANNEL_ID`: ID of the channel where bot will forward all files received from users, can be obtained by forwarding any message from channel to [@ShowJsonBot](https://t.me/ShowJsonBot) and then looking from `forward_from_chat` key. `int`
79
  * `BOT_WORKERS`: Number of updates bot should process from Telegram at once, by default to 10 updates. `int`
80
- * `SECRET_CODE_LENGTH`: Number of characters that file code should contain, by default to 12 characters. `int`
81
  * `BASE_URL`: Base URL that bot should use while generating file links, can be FQDN and by default to `127.0.0.1`. `str`
82
  * `BIND_ADDRESS`: Bind address for web server, by default to `0.0.0.0` to run on all possible addresses. `str`
83
  * `PORT`: Port for web server to run on, by default to `8080`. `int`
 
1
  <div align="center"><h1>🌐File Stream Bot</h1>
2
  <b>An open-source Python Telegram bot to transmit Telegram files over HTTP.</b>
 
 
3
  </div><br>
4
 
5
  ## **📑 INDEX**
 
32
  ```
33
  sudo apt-get update && sudo apt-get install -y python3.11 git pip
34
  ```
35
+ For MacOS:
36
  ```
37
  brew install python@3.11 git
38
  ```
 
69
  **The variables provided below should either be completed within the [config.py](https://github.com/TheCaduceus/FileStreamBot/blob/main/bot/config.py) file or configured as environment variables.**
70
  * `API_ID`|`TELEGRAM_API_ID`: API ID of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `int`
71
  * `API_HASH`|`TELEGRAM_API_HASH`: API hash of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `str`
72
+ * `OWNER_ID`: ID of your Telegram account, can be obtained by sending **/info** to [@DumpJsonBot](https://t.me/DumpJsonBot). `int`
73
  * `ALLOWED_USER_IDS`: A list of Telegram account IDs (separated by spaces) that are permitted to use the bot. Leave this field empty to allow anyone to use it. `str`
74
  * `BOT_USERNAME`|`TELEGRAM_BOT_USERNAME`: Username of your Telegram bot, create one using [@BotFather](https://t.me/BotFather). `str`
75
  * `BOT_TOKEN`|`TELEGRAM_BOT_TOKEN`: Telegram API token of your bot, can be obtained from [@BotFather](https://t.me/BotFather). `str`
76
+ * `CHANNEL_ID`|`TELEGRAM_CHANNEL_ID`: ID of the channel where bot will forward all files received from users, can be obtained by forwarding any message from channel to [@DumpJsonBot](https://t.me/DumpJsonBot) and then looking from `forward_from_chat` key. `int`
77
  * `BOT_WORKERS`: Number of updates bot should process from Telegram at once, by default to 10 updates. `int`
78
+ * `SECRET_CODE_LENGTH`: Number of characters that file code should contain, by default to 24 characters. `int`
79
  * `BASE_URL`: Base URL that bot should use while generating file links, can be FQDN and by default to `127.0.0.1`. `str`
80
  * `BIND_ADDRESS`: Bind address for web server, by default to `0.0.0.0` to run on all possible addresses. `str`
81
  * `PORT`: Port for web server to run on, by default to `8080`. `int`
bot/__init__.py CHANGED
@@ -1,15 +1,19 @@
1
- from telethon import TelegramClient
2
  from logging import getLogger
3
  from logging.config import dictConfig
4
  from .config import Telegram, LOGGER_CONFIG_JSON
5
 
6
  dictConfig(LOGGER_CONFIG_JSON)
7
 
8
- version = 1.6
9
  logger = getLogger('bot')
10
 
11
- TelegramBot = TelegramClient(
12
- session='bot',
13
- api_id=Telegram.API_ID,
14
- api_hash=Telegram.API_HASH
 
 
 
 
15
  )
 
1
+ from hydrogram import Client
2
  from logging import getLogger
3
  from logging.config import dictConfig
4
  from .config import Telegram, LOGGER_CONFIG_JSON
5
 
6
  dictConfig(LOGGER_CONFIG_JSON)
7
 
8
+ version = 1.7
9
  logger = getLogger('bot')
10
 
11
+ TelegramBot = Client(
12
+ name ='bot',
13
+ api_id = Telegram.API_ID,
14
+ api_hash = Telegram.API_HASH,
15
+ bot_token = Telegram.BOT_TOKEN,
16
+ plugins = {'root': 'bot/plugins'},
17
+ workers = Telegram.BOT_WORKERS,
18
+ max_concurrent_transmissions = 1000
19
  )
bot/__main__.py CHANGED
@@ -1,22 +1,6 @@
1
- from importlib import import_module
2
- from pathlib import Path
3
- from bot import TelegramBot, logger
4
- from bot.config import Telegram
5
  from bot.server import server
6
 
7
- def load_plugins():
8
- count = 0
9
- for path in Path('bot/plugins').rglob('*.py'):
10
- import_module(f'bot.plugins.{path.stem}')
11
- count += 1
12
- logger.info(f'Loaded {count} {"plugins" if count > 1 else "plugin"}.')
13
-
14
  if __name__ == '__main__':
15
- logger.info('initializing...')
16
  TelegramBot.loop.create_task(server.serve())
17
- TelegramBot.start(bot_token=Telegram.BOT_TOKEN)
18
- logger.info('Telegram client is now started.')
19
- logger.info('Loading bot plugins...')
20
- load_plugins()
21
- logger.info('Bot is now ready!')
22
- TelegramBot.run_until_disconnected()
 
1
+ from bot import TelegramBot
 
 
 
2
  from bot.server import server
3
 
 
 
 
 
 
 
 
4
  if __name__ == '__main__':
 
5
  TelegramBot.loop.create_task(server.serve())
6
+ TelegramBot.run()
 
 
 
 
 
bot/config.py CHANGED
@@ -1,14 +1,15 @@
1
  from os import environ as env
2
 
3
  class Telegram:
4
- API_ID = int(env.get("TELEGRAM_API_ID", 1234))
5
  API_HASH = env.get("TELEGRAM_API_HASH", "xyz")
6
- OWNER_ID = int(env.get("OWNER_ID", 1234567890))
7
  ALLOWED_USER_IDS = env.get("ALLOWED_USER_IDS", "").split()
8
  BOT_USERNAME = env.get("TELEGRAM_BOT_USERNAME", "BotFather")
9
- BOT_TOKEN = env.get("TELEGRAM_BOT_TOKEN", "1234:abcd")
10
- CHANNEL_ID = int(env.get("TELEGRAM_CHANNEL_ID", -1001234567890))
11
- SECRET_CODE_LENGTH = int(env.get("SECRET_CODE_LENGTH", 12))
 
12
 
13
  class Server:
14
  BASE_URL = env.get("BASE_URL", "http://127.0.0.1:8080")
@@ -47,6 +48,10 @@ LOGGER_CONFIG_JSON = {
47
  'bot': {
48
  'level': 'INFO',
49
  'handlers': ['file_handler', 'stream_handler']
 
 
 
 
50
  }
51
  }
52
  }
 
1
  from os import environ as env
2
 
3
  class Telegram:
4
+ API_ID = int(env.get("TELEGRAM_API_ID", 12345))
5
  API_HASH = env.get("TELEGRAM_API_HASH", "xyz")
6
+ OWNER_ID = int(env.get("OWNER_ID", 5530237028))
7
  ALLOWED_USER_IDS = env.get("ALLOWED_USER_IDS", "").split()
8
  BOT_USERNAME = env.get("TELEGRAM_BOT_USERNAME", "BotFather")
9
+ BOT_TOKEN = env.get("TELEGRAM_BOT_TOKEN", "1234567:xyz")
10
+ BOT_WORKERS = env.get("BOT_WORKERS", 10)
11
+ CHANNEL_ID = int(env.get("TELEGRAM_CHANNEL_ID", -100123456789))
12
+ SECRET_CODE_LENGTH = int(env.get("SECRET_CODE_LENGTH", 24))
13
 
14
  class Server:
15
  BASE_URL = env.get("BASE_URL", "http://127.0.0.1:8080")
 
48
  'bot': {
49
  'level': 'INFO',
50
  'handlers': ['file_handler', 'stream_handler']
51
+ },
52
+ 'hydrogram': {
53
+ 'level': 'INFO',
54
+ 'handlers': ['file_handler', 'stream_handler']
55
  }
56
  }
57
  }
bot/modules/decorators.py CHANGED
@@ -1,20 +1,16 @@
1
- from telethon.events import NewMessage, CallbackQuery
2
- from typing import Callable
 
3
  from functools import wraps
4
  from bot.config import Telegram
5
 
6
- def verify_user(private: bool = False):
7
-
8
- def decorator(func: Callable):
9
- @wraps(func)
10
- async def wrapper(update: NewMessage.Event | CallbackQuery.Event):
11
- if private and not update.is_private:
12
- return
13
 
14
- chat_id = str(update.chat_id)
 
 
15
 
16
- if not Telegram.ALLOWED_USER_IDS or chat_id in Telegram.ALLOWED_USER_IDS:
17
- return await func(update)
18
-
19
- return wrapper
20
  return decorator
 
1
+ from hydrogram import Client
2
+ from hydrogram.types import Message, CallbackQuery
3
+ from typing import Union, Callable
4
  from functools import wraps
5
  from bot.config import Telegram
6
 
7
+ def verify_user(func: Callable):
 
 
 
 
 
 
8
 
9
+ @wraps(func)
10
+ async def decorator(client: Client, update: Union[Message, CallbackQuery]):
11
+ chat_id = str(update.from_user.id if update.from_user else update.chat.id)
12
 
13
+ if not Telegram.ALLOWED_USER_IDS or chat_id in Telegram.ALLOWED_USER_IDS:
14
+ return await func(client, update)
15
+
 
16
  return decorator
bot/modules/static.py CHANGED
@@ -1,35 +1,17 @@
1
  WelcomeText = \
2
  """
3
- Hi **%(first_name)s**, send me a file or add me as an admin to any channel to instantly generate file links.
4
-
5
- Add me to your channel to instantly generate links for any downloadable media. Once received, I will automatically attach appropriate buttons to the post containing the URL. If you want me to ignore a given post, you can insert `#pass` in the post.
6
-
7
- - /start to get this message.
8
- - /info to get user info.
9
- - /log to get bot logs. (admin only!)
10
  """
11
 
12
- UserInfoText = \
13
  """
14
- **First Name:**
15
- `{sender.first_name}`
16
-
17
- **Last Name:**
18
- `{sender.last_name}`
19
-
20
- **User ID:**
21
- `{sender.id}`
22
-
23
- **Username:**
24
- `@{sender.username}`
25
  """
26
 
27
  FileLinksText = \
28
  """
29
  **Download Link:**
30
  `%(dl_link)s`
31
- **Telegram File:**
32
- `%(tg_link)s`
33
  """
34
 
35
  MediaLinksText = \
@@ -38,8 +20,6 @@ MediaLinksText = \
38
  `%(dl_link)s`
39
  **Stream Link:**
40
  `%(stream_link)s`
41
- **Telegram File:**
42
- `%(tg_link)s`
43
  """
44
 
45
  InvalidQueryText = \
@@ -61,8 +41,3 @@ InvalidPayloadText = \
61
  """
62
  Invalid payload.
63
  """
64
-
65
- MediaTypeNotSupportedText = \
66
- """
67
- Sorry, this media type is not supported.
68
- """
 
1
  WelcomeText = \
2
  """
3
+ Hi **%(first_name)s**, send me a file to instantly generate file links.
 
 
 
 
 
 
4
  """
5
 
6
+ PrivacyText = \
7
  """
8
+ This bot securely stores your files to deliver its service.
 
 
 
 
 
 
 
 
 
 
9
  """
10
 
11
  FileLinksText = \
12
  """
13
  **Download Link:**
14
  `%(dl_link)s`
 
 
15
  """
16
 
17
  MediaLinksText = \
 
20
  `%(dl_link)s`
21
  **Stream Link:**
22
  `%(stream_link)s`
 
 
23
  """
24
 
25
  InvalidQueryText = \
 
41
  """
42
  Invalid payload.
43
  """
 
 
 
 
 
bot/modules/telegram.py CHANGED
@@ -1,5 +1,4 @@
1
- from telethon.events import NewMessage
2
- from telethon.tl.custom import Message
3
  from datetime import datetime
4
  from mimetypes import guess_type
5
  from bot import TelegramBot
@@ -10,55 +9,47 @@ async def get_message(message_id: int) -> Message | None:
10
  message = None
11
 
12
  try:
13
- message = await TelegramBot.get_messages(Telegram.CHANNEL_ID, ids=message_id)
 
14
  except Exception:
15
  pass
16
 
17
  return message
18
 
19
- async def send_message(message:Message, send_to:int = Telegram.CHANNEL_ID) -> Message:
20
- return await TelegramBot.send_message(entity=send_to, message=message)
21
-
22
- def filter_files(update: NewMessage.Event | Message):
23
- return bool(
24
- (
25
- update.document
26
- or update.photo
27
- or update.video
28
- or update.video_note
29
- or update.audio
30
- or update.gif
31
- )
32
- and not update.sticker
33
  )
 
 
 
 
 
 
 
34
 
35
- def get_file_properties(message: Message):
36
- file_name = message.file.name
37
- file_size = message.file.size or 0
38
- mime_type = message.file.mime_type
39
 
40
  if not file_name:
41
- attributes = {
42
  'video': 'mp4',
43
  'audio': 'mp3',
44
  'voice': 'ogg',
45
  'photo': 'jpg',
46
  'video_note': 'mp4'
47
- }
48
-
49
- for attribute in attributes:
50
- media = getattr(message, attribute, None)
51
- if media:
52
- file_type, file_format = attribute, attributes[attribute]
53
- break
54
-
55
- if not media:
56
- abort(400, 'Invalid media type.')
57
-
58
  date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
59
  file_name = f'{file_type}-{date}.{file_format}'
60
 
61
- if not mime_type:
62
- mime_type = guess_type(file_name)[0] or 'application/octet-stream'
63
-
64
  return file_name, file_size, mime_type
 
1
+ from hydrogram.types import Message
 
2
  from datetime import datetime
3
  from mimetypes import guess_type
4
  from bot import TelegramBot
 
9
  message = None
10
 
11
  try:
12
+ message = await TelegramBot.get_messages(Telegram.CHANNEL_ID, message_ids=message_id)
13
+ if message.empty: message = None
14
  except Exception:
15
  pass
16
 
17
  return message
18
 
19
+ async def send_message(msg: Message, send_to: int = Telegram.CHANNEL_ID) -> Message:
20
+ return await TelegramBot.send_message(entity=send_to, message=msg)
21
+
22
+ def get_file_properties(msg: Message):
23
+ attributes = (
24
+ 'document',
25
+ 'video',
26
+ 'audio',
27
+ 'voice',
28
+ 'photo',
29
+ 'video_note'
 
 
 
30
  )
31
+ for attribute in attributes:
32
+ media = getattr(msg, attribute, None)
33
+ if media:
34
+ file_type = attribute
35
+ break
36
+
37
+ if not media: abort(400, 'Unknown file type.')
38
 
39
+ file_name = getattr(media, 'file_name', None)
40
+ file_size = getattr(media, 'file_size', 0)
 
 
41
 
42
  if not file_name:
43
+ file_format = {
44
  'video': 'mp4',
45
  'audio': 'mp3',
46
  'voice': 'ogg',
47
  'photo': 'jpg',
48
  'video_note': 'mp4'
49
+ }.get(file_type)
 
 
 
 
 
 
 
 
 
 
50
  date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
51
  file_name = f'{file_type}-{date}.{file_format}'
52
 
53
+ mime_type = guess_type(file_name)[0] or 'application/octet-stream'
54
+
 
55
  return file_name, file_size, mime_type
bot/plugins/callback.py CHANGED
@@ -1,24 +1,31 @@
1
- from telethon.events import CallbackQuery
2
  from bot import TelegramBot
3
  from bot.modules.decorators import verify_user
4
  from bot.modules.static import *
5
  from bot.modules.telegram import get_message
6
 
7
- @TelegramBot.on(CallbackQuery(pattern=r'^rm_'))
8
- @verify_user(private=True)
9
- async def delete_file(event: CallbackQuery.Event):
10
- query_data = event.query.data.decode().split('_')
11
 
12
- if len(query_data) != 3:
13
- return await event.answer(InvalidQueryText, alert=True)
14
 
15
- message = await get_message(int(query_data[1]))
 
16
 
17
- if not message:
18
- return await event.answer(MessageNotExist, alert=True)
19
- if query_data[2] != message.raw_text:
20
- return await event.answer(InvalidQueryText, alert=True)
21
 
22
- await message.delete()
 
 
 
23
 
24
- return await event.answer(LinkRevokedText, alert=True)
 
 
 
 
 
 
 
1
+ from hydrogram.types import CallbackQuery
2
  from bot import TelegramBot
3
  from bot.modules.decorators import verify_user
4
  from bot.modules.static import *
5
  from bot.modules.telegram import get_message
6
 
7
+ @TelegramBot.on_callback_query()
8
+ @verify_user
9
+ async def manage_callback(bot, q: CallbackQuery):
10
+ query = q.data
11
 
12
+ if query.startswith('rm_'):
13
+ sq = query.split('_')
14
 
15
+ if len(sq) != 3:
16
+ return await q.answer(InvalidQueryText, show_alert=True)
17
 
18
+ message = await get_message(int(sq[1]))
 
 
 
19
 
20
+ if not message:
21
+ return await q.answer(MessageNotExist, show_alert=True)
22
+
23
+ sc = message.caption.split('/')
24
 
25
+ if q.from_user.id != int(sc[1]) or sq[2] != sc[0]:
26
+ return await q.answer(InvalidQueryText, show_alert=True)
27
+
28
+ await message.delete()
29
+ await q.answer(LinkRevokedText, show_alert=True)
30
+ else:
31
+ await q.answer(InvalidQueryText, show_alert=True)
bot/plugins/commands.py CHANGED
@@ -1,28 +1,18 @@
1
- from telethon import Button
2
- from telethon.events import NewMessage
3
- from telethon.tl.custom.message import Message
4
  from bot import TelegramBot
5
- from bot.config import Telegram
6
  from bot.modules.static import *
7
  from bot.modules.decorators import verify_user
8
 
9
- @TelegramBot.on(NewMessage(incoming=True, pattern=r'^/start$'))
10
- @verify_user(private=True)
11
- async def welcome(event: NewMessage.Event | Message):
12
- await event.reply(
13
- message=WelcomeText % {'first_name': event.sender.first_name},
14
- buttons=[
15
- [
16
- Button.url('Add to Channel', f'https://t.me/{Telegram.BOT_USERNAME}?startchannel&admin=post_messages+edit_messages+delete_messages')
17
- ]
18
- ]
19
  )
20
 
21
- @TelegramBot.on(NewMessage(incoming=True, pattern=r'^/info$'))
22
- @verify_user(private=True)
23
- async def user_info(event: Message):
24
- await event.reply(UserInfoText.format(sender=event.sender))
25
-
26
- @TelegramBot.on(NewMessage(chats=Telegram.OWNER_ID, incoming=True, pattern=r'^/log$'))
27
- async def send_log(event: NewMessage.Event | Message):
28
- await event.reply(file='event-log.txt')
 
1
+ from hydrogram import filters
2
+ from hydrogram.types import Message
 
3
  from bot import TelegramBot
 
4
  from bot.modules.static import *
5
  from bot.modules.decorators import verify_user
6
 
7
+ @TelegramBot.on_message(filters.command(['start', 'help']) & filters.private)
8
+ @verify_user
9
+ async def start_command(_, msg: Message):
10
+ await msg.reply(
11
+ text=WelcomeText % {'first_name': msg.from_user.first_name},
12
+ quote=True
 
 
 
 
13
  )
14
 
15
+ @TelegramBot.on_message(filters.command('privacy') & filters.private)
16
+ @verify_user
17
+ async def privacy_command(_, msg: Message):
18
+ await msg.reply(text=PrivacyText, quote=True)
 
 
 
 
bot/plugins/deeplinks.py CHANGED
@@ -1,24 +1,25 @@
1
- from telethon.events import NewMessage
2
- from telethon.tl.custom import Message
3
- from bot import TelegramBot
4
- from bot.modules.decorators import verify_user
5
- from bot.modules.telegram import get_message, send_message
6
- from bot.modules.static import *
7
 
8
- @TelegramBot.on(NewMessage(incoming=True, pattern=r'^/start file_'))
9
- @verify_user(private=True)
10
- async def send_file(event: NewMessage.Event | Message):
11
- payload = event.raw_text.split()[-1].split('_')
 
12
 
13
- if len(payload) != 3:
14
- return await event.reply(InvalidPayloadText)
15
-
16
- message = await get_message(int(payload[1]))
17
 
18
- if not message:
19
- return await event.reply(MessageNotExist)
20
- if payload[2] != message.raw_text:
21
- return await event.reply(InvalidPayloadText)
22
-
23
- message.raw_text = ''
24
- await send_message(message, send_to=event.chat_id)
 
 
 
 
 
 
 
1
+ # Currently not in use.
 
 
 
 
 
2
 
3
+ # from hydrogram.types import Message
4
+ # from bot import TelegramBot
5
+ # from bot.modules.decorators import verify_user
6
+ # from bot.modules.telegram import get_message, send_message
7
+ # from bot.modules.static import *
8
 
9
+ # async def deeplinks(msg: Message, payload: str):
10
+ # if payload.startswith('file_'):
11
+ # sp = payload.split('_')
 
12
 
13
+ # if len(sp) != 3:
14
+ # return await msg.reply(InvalidPayloadText, quote=True)
15
+
16
+ # message = await get_message(int(sp[1]))
17
+
18
+ # if not message:
19
+ # return await msg.reply(MessageNotExist)
20
+ # if sp[2] != message.caption:
21
+ # return await msg.reply(InvalidPayloadText, quote=True)
22
+
23
+ # await message.copy(chat_id=msg.from_user.id, caption="")
24
+ # else:
25
+ # await msg.reply(InvalidPayloadText, quote=True)
bot/plugins/files.py CHANGED
@@ -1,95 +1,60 @@
1
- from telethon import Button
2
- from telethon.events import NewMessage
3
- from telethon.errors import MessageAuthorRequiredError, MessageNotModifiedError, MessageIdInvalidError
4
- from telethon.tl.custom import Message
5
  from secrets import token_hex
6
  from bot import TelegramBot
7
  from bot.config import Telegram, Server
8
  from bot.modules.decorators import verify_user
9
- from bot.modules.telegram import send_message, filter_files
10
  from bot.modules.static import *
11
 
12
- @TelegramBot.on(NewMessage(incoming=True, func=filter_files))
13
- @verify_user(private=True)
14
- async def user_file_handler(event: NewMessage.Event | Message):
 
 
 
 
 
 
 
 
 
 
 
15
  secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
16
- event.message.text = f'`{secret_code}`'
17
- message = await send_message(event.message)
18
- message_id = message.id
 
 
 
19
 
20
- dl_link = f'{Server.BASE_URL}/dl/{message_id}?code={secret_code}'
21
- tg_link = f'{Server.BASE_URL}/file/{message_id}?code={secret_code}'
22
- deep_link = f'https://t.me/{Telegram.BOT_USERNAME}?start=file_{message_id}_{secret_code}'
23
-
24
- if (event.document and 'video' in event.document.mime_type) or event.video:
25
- stream_link = f'{Server.BASE_URL}/stream/{message_id}?code={secret_code}'
26
- await event.reply(
27
- message= MediaLinksText % {'dl_link': dl_link, 'tg_link': tg_link, 'tg_link': tg_link, 'stream_link': stream_link},
28
- buttons=[
29
- [
30
- Button.url('Download', dl_link),
31
- Button.url('Stream', stream_link)
32
- ],
33
  [
34
- Button.url('Get File', deep_link),
35
- Button.inline('Revoke', f'rm_{message_id}_{secret_code}')
 
 
 
 
 
36
  ]
37
- ]
38
  )
39
  else:
40
- await event.reply(
41
- message=FileLinksText % {'dl_link': dl_link, 'tg_link': tg_link},
42
- buttons=[
43
- [
44
- Button.url('Download', dl_link),
45
- Button.url('Get File', deep_link)
46
- ],
47
  [
48
- Button.inline('Revoke', f'rm_{message_id}_{secret_code}')
49
- ]
50
- ]
51
- )
52
-
53
- @TelegramBot.on(NewMessage(incoming=True, func=filter_files, forwards=False))
54
- @verify_user()
55
- async def channel_file_handler(event: NewMessage.Event | Message):
56
- if event.raw_text and '#pass' in event.raw_text:
57
- return
58
-
59
- secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
60
- event.message.text = f"`{secret_code}`"
61
- message = await send_message(event.message)
62
- message_id = message.id
63
-
64
- dl_link = f"{Server.BASE_URL}/dl/{message_id}?code={secret_code}"
65
- tg_link = f"{Server.BASE_URL}/file/{message_id}?code={secret_code}"
66
-
67
- if (event.document and "video" in event.document.mime_type) or event.video:
68
- stream_link = f"{Server.BASE_URL}/stream/{message_id}?code={secret_code}"
69
-
70
- try:
71
- await event.edit(
72
- buttons=[
73
- [Button.url("Download", dl_link), Button.url("Stream", stream_link)],
74
- [Button.url("Get File", tg_link)],
75
  ]
76
  )
77
- except (
78
- MessageAuthorRequiredError,
79
- MessageIdInvalidError,
80
- MessageNotModifiedError,
81
- ):
82
- pass
83
- else:
84
- try:
85
- await event.edit(
86
- buttons=[
87
- [Button.url("Download", dl_link), Button.url("Get File", tg_link)]
88
- ]
89
- )
90
- except (
91
- MessageAuthorRequiredError,
92
- MessageIdInvalidError,
93
- MessageNotModifiedError,
94
- ):
95
- pass
 
1
+ from hydrogram import filters
2
+ from hydrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
 
 
3
  from secrets import token_hex
4
  from bot import TelegramBot
5
  from bot.config import Telegram, Server
6
  from bot.modules.decorators import verify_user
 
7
  from bot.modules.static import *
8
 
9
+ @TelegramBot.on_message(
10
+ filters.private
11
+ & (
12
+ filters.document
13
+ | filters.video
14
+ | filters.video_note
15
+ | filters.audio
16
+ | filters.voice
17
+ | filters.photo
18
+ )
19
+ )
20
+ @verify_user
21
+ async def handle_user_file(_, msg: Message):
22
+ sender_id = msg.from_user.id
23
  secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
24
+ file = await msg.copy(
25
+ chat_id=Telegram.CHANNEL_ID,
26
+ caption=f'||{secret_code}/{sender_id}||'
27
+ )
28
+ file_id = file.id
29
+ dl_link = f'{Server.BASE_URL}/dl/{file_id}?code={secret_code}'
30
 
31
+ if (msg.document and 'video' in msg.document.mime_type) or msg.video:
32
+ stream_link = f'{Server.BASE_URL}/stream/{file_id}?code={secret_code}'
33
+ await msg.reply(
34
+ text=MediaLinksText % {'dl_link': dl_link, 'stream_link': stream_link},
35
+ quote=True,
36
+ reply_markup=InlineKeyboardMarkup(
 
 
 
 
 
 
 
37
  [
38
+ [
39
+ InlineKeyboardButton('Download', url=dl_link),
40
+ InlineKeyboardButton('Stream', url=stream_link)
41
+ ],
42
+ [
43
+ InlineKeyboardButton('Revoke', callback_data=f'rm_{file_id}_{secret_code}')
44
+ ]
45
  ]
46
+ )
47
  )
48
  else:
49
+ await msg.reply(
50
+ text=FileLinksText % {'dl_link': dl_link},
51
+ quote=True,
52
+ reply_markup=InlineKeyboardMarkup(
 
 
 
53
  [
54
+ [
55
+ InlineKeyboardButton('Download', url=dl_link),
56
+ InlineKeyboardButton('Revoke', callback_data=f'rm_{file_id}_{secret_code}')
57
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  ]
59
  )
60
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bot/server/__init__.py CHANGED
@@ -2,12 +2,12 @@ from quart import Quart
2
  from uvicorn import Server as UvicornServer, Config
3
  from logging import getLogger
4
  from bot.config import Server, LOGGER_CONFIG_JSON
5
-
6
  from . import main, error
7
 
8
  logger = getLogger('uvicorn')
9
  instance = Quart(__name__)
10
  instance.config['RESPONSE_TIMEOUT'] = None
 
11
 
12
  @instance.before_serving
13
  async def before_serve():
 
2
  from uvicorn import Server as UvicornServer, Config
3
  from logging import getLogger
4
  from bot.config import Server, LOGGER_CONFIG_JSON
 
5
  from . import main, error
6
 
7
  logger = getLogger('uvicorn')
8
  instance = Quart(__name__)
9
  instance.config['RESPONSE_TIMEOUT'] = None
10
+ instance.config['MAX_CONTENT_LENGTH'] = 999999999999999
11
 
12
  @instance.before_serving
13
  async def before_serve():
bot/server/main.py CHANGED
@@ -1,8 +1,9 @@
1
  from quart import Blueprint, Response, request, render_template, redirect
 
 
2
  from .error import abort
3
  from bot import TelegramBot
4
  from bot.config import Telegram, Server
5
- from math import ceil, floor
6
  from bot.modules.telegram import get_message, get_file_properties
7
 
8
  bp = Blueprint('main', __name__)
@@ -13,73 +14,70 @@ async def home():
13
 
14
  @bp.route('/dl/<int:file_id>')
15
  async def transmit_file(file_id):
16
- file = await get_message(message_id=int(file_id)) or abort(404)
17
  code = request.args.get('code') or abort(401)
18
- range_header = request.headers.get('Range', 0)
19
 
20
- if code != file.raw_text:
21
  abort(403)
22
 
23
  file_name, file_size, mime_type = get_file_properties(file)
24
-
25
- if range_header:
26
- from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
27
- from_bytes = int(from_bytes)
28
- until_bytes = int(until_bytes) if until_bytes else file_size - 1
29
- else:
30
- from_bytes = 0
31
- until_bytes = file_size - 1
32
-
33
- if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes):
34
- abort(416, 'Invalid range.')
35
-
36
- chunk_size = 1024 * 1024
37
- until_bytes = min(until_bytes, file_size - 1)
38
 
39
- offset = from_bytes - (from_bytes % chunk_size)
40
- first_part_cut = from_bytes - offset
41
- last_part_cut = until_bytes % chunk_size + 1
42
 
43
- req_length = until_bytes - from_bytes + 1
44
- part_count = ceil(until_bytes / chunk_size) - floor(offset / chunk_size)
45
-
 
 
 
 
 
 
 
 
 
 
 
 
46
  headers = {
47
- "Content-Type": f"{mime_type}",
48
- "Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
49
- "Content-Length": str(req_length),
50
- "Content-Disposition": f'attachment; filename="{file_name}"',
51
- "Accept-Ranges": "bytes",
52
- }
53
-
54
- async def file_generator():
55
- current_part = 1
56
- async for chunk in TelegramBot.iter_download(file, offset=offset, chunk_size=chunk_size, stride=chunk_size, file_size=file_size):
57
- if not chunk:
 
 
 
 
 
 
 
 
 
 
 
 
58
  break
59
- elif part_count == 1:
60
- yield chunk[first_part_cut:last_part_cut]
61
- elif current_part == 1:
62
- yield chunk[first_part_cut:]
63
- elif current_part == part_count:
64
- yield chunk[:last_part_cut]
65
- else:
66
- yield chunk
67
 
68
- current_part += 1
 
69
 
70
- if current_part > part_count:
71
- break
 
72
 
73
- return Response(file_generator(), headers=headers, status=206 if range_header else 200)
74
 
75
  @bp.route('/stream/<int:file_id>')
76
  async def stream_file(file_id):
77
  code = request.args.get('code') or abort(401)
78
-
79
  return await render_template('player.html', mediaLink=f'{Server.BASE_URL}/dl/{file_id}?code={code}')
80
-
81
- @bp.route('/file/<int:file_id>')
82
- async def file_deeplink(file_id):
83
- code = request.args.get('code') or abort(401)
84
-
85
- return redirect(f'https://t.me/{Telegram.BOT_USERNAME}?start=file_{file_id}_{code}')
 
1
  from quart import Blueprint, Response, request, render_template, redirect
2
+ from math import ceil
3
+ from re import match as re_match
4
  from .error import abort
5
  from bot import TelegramBot
6
  from bot.config import Telegram, Server
 
7
  from bot.modules.telegram import get_message, get_file_properties
8
 
9
  bp = Blueprint('main', __name__)
 
14
 
15
  @bp.route('/dl/<int:file_id>')
16
  async def transmit_file(file_id):
17
+ file = await get_message(file_id) or abort(404)
18
  code = request.args.get('code') or abort(401)
19
+ range_header = request.headers.get('Range')
20
 
21
+ if code != file.caption.split('/')[0]:
22
  abort(403)
23
 
24
  file_name, file_size, mime_type = get_file_properties(file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ start = 0
27
+ end = file_size - 1
28
+ chunk_size = 1 * 1024 * 1024 # 1 MB
29
 
30
+ if range_header:
31
+ range_match = re_match(r'bytes=(\d+)-(\d*)', range_header)
32
+ if range_match:
33
+ start = int(range_match.group(1))
34
+ end = int(range_match.group(2)) if range_match.group(2) else file_size - 1
35
+ if start > end or start >= file_size:
36
+ abort(416, 'Requested range not satisfiable')
37
+ else:
38
+ abort(400, 'Invalid Range header')
39
+
40
+ offset_chunks = start // chunk_size
41
+ total_bytes_to_stream = end - start + 1
42
+ chunks_to_stream = ceil(total_bytes_to_stream / chunk_size)
43
+
44
+ content_length = total_bytes_to_stream
45
  headers = {
46
+ 'Content-Type': mime_type,
47
+ 'Content-Disposition': f'attachment; filename={file_name}',
48
+ 'Content-Range': f'bytes {start}-{end}/{file_size}',
49
+ 'Accept-Ranges': 'bytes',
50
+ 'Content-Length': str(content_length),
51
+ }
52
+ status_code = 206 if range_header else 200
53
+
54
+ async def file_stream():
55
+ bytes_streamed = 0
56
+ chunk_index = 0
57
+ async for chunk in TelegramBot.stream_media(
58
+ file,
59
+ offset=offset_chunks,
60
+ limit=chunks_to_stream,
61
+ ):
62
+ if chunk_index == 0: # Trim the first chunk if necessary
63
+ trim_start = start % chunk_size
64
+ if trim_start > 0:
65
+ chunk = chunk[trim_start:]
66
+
67
+ remaining_bytes = content_length - bytes_streamed
68
+ if remaining_bytes <= 0:
69
  break
 
 
 
 
 
 
 
 
70
 
71
+ if len(chunk) > remaining_bytes: # Trim the last chunk if necessary
72
+ chunk = chunk[:remaining_bytes]
73
 
74
+ yield chunk
75
+ bytes_streamed += len(chunk)
76
+ chunk_index += 1
77
 
78
+ return Response(file_stream(), headers=headers, status=status_code)
79
 
80
  @bp.route('/stream/<int:file_id>')
81
  async def stream_file(file_id):
82
  code = request.args.get('code') or abort(401)
 
83
  return await render_template('player.html', mediaLink=f'{Server.BASE_URL}/dl/{file_id}?code={code}')
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- telethon
2
- cryptg
3
  quart
4
  uvicorn
 
1
+ hydrogram
2
+ tgcrypto
3
  quart
4
  uvicorn