Skip to content
This repository was archived by the owner on Mar 1, 2025. It is now read-only.

Commit b4c7f0b

Browse files
committed
fast-link
1 parent 38a8fa6 commit b4c7f0b

File tree

9 files changed

+128
-53
lines changed

9 files changed

+128
-53
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.idea
2+
/__pycache__

FileToLink/__main__.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,13 @@ async def main(_, msg: Message):
3838
# Else if the file not exist on the server, Send the message to Archive Channel and Create empty file
3939
gen_msg = await bot.send_message(msg.chat.id, Strings.generating_link,
4040
reply_to_message_id=msg.message_id)
41-
worker = Worker(msg)
41+
4242
archived_msg = await archive_msg(msg)
43+
worker = Worker(archived_msg)
44+
AllWorkers.add(worker)
4345

44-
archive_id = archived_msg.message_id
45-
worker.set_link(archive_id)
46-
AllWorkers.add(worker, archive_id)
47-
if archive_id in NotFound:
48-
NotFound.remove(archive_id)
46+
if archived_msg.message_id in NotFound:
47+
NotFound.remove(archived_msg.message_id)
4948

5049
await worker.create_file() # Create empty file
5150

@@ -59,18 +58,14 @@ async def main(_, msg: Message):
5958
if worker.stream:
6059
st_link = f'{dl_link}?st=1' # Stream Link
6160
buttons.append([InlineKeyboardButton(Strings.st_link, url=st_link)])
62-
61+
buttons.append([InlineKeyboardButton(Strings.update_link, callback_data=f'fast|{worker.archive_id}')])
6362
reply_markup = InlineKeyboardMarkup(buttons)
6463

6564
if gen_msg is not None:
66-
await bot.edit_message_text(msg.chat.id, gen_msg.message_id, text,
67-
reply_markup=reply_markup,
68-
disable_web_page_preview=True)
65+
await gen_msg.edit_text(text, reply_markup=reply_markup, disable_web_page_preview=True)
6966
else:
70-
await bot.send_message(msg.chat.id, text,
71-
reply_to_message_id=msg.message_id,
72-
reply_markup=reply_markup,
73-
disable_web_page_preview=True)
67+
await bot.send_message(msg.chat.id, text, reply_to_message_id=msg.message_id,
68+
reply_markup=reply_markup, disable_web_page_preview=True)
7469

7570

7671
async def wait(chat_id: int):

FileToLink/archive.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
from pyrogram.types import (Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, User)
33
from pyrogram.errors.exceptions import ButtonDataInvalid
44

5-
from FileToLink import bot, Config
5+
from FileToLink import bot, Config, Strings
66

77

88
async def archive_msg(msg: Message):
9-
buttons = [[InlineKeyboardButton("🚫", callback_data=f'delete-file')]]
9+
buttons = [[InlineKeyboardButton("🚫", callback_data='delete-file')]]
1010

1111
forward = msg.forward_from or msg.forward_from_chat
12-
1312
if forward:
1413
if forward.username:
1514
buttons[0].append(InlineKeyboardButton("↪", url=f'https://t.me/{forward.username}'))
@@ -50,5 +49,4 @@ async def from_info(_, cb: CallbackQuery):
5049

5150
@bot.on_callback_query(filters.create(lambda _, __, cb: cb.data == 'time-out'))
5251
async def time_out(_, cb: CallbackQuery):
53-
await cb.answer("The bot can't delete messages older than 48 hours,"
54-
" you can delete this message manually", show_alert=True)
52+
await cb.answer(Strings.delete_forbidden, show_alert=True)

FileToLink/client.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ def __init__(
2020
bot_token: str = None,
2121
sleep_threshold: int = Session.SLEEP_THRESHOLD
2222
):
23-
super(TelegramClient, self).__init__(session_name,
24-
api_id=api_id,
25-
api_hash=api_hash,
26-
bot_token=bot_token,
27-
sleep_threshold=sleep_threshold)
23+
super(TelegramClient, self).__init__(session_name, api_id=api_id, api_hash=api_hash,
24+
bot_token=bot_token, sleep_threshold=sleep_threshold)
2825

2926
async def get_part_file(
3027
self,
@@ -253,8 +250,5 @@ async def download_part(
253250
return rng
254251

255252

256-
bot = TelegramClient(Config.Session,
257-
api_id=Config.API_ID,
258-
api_hash=Config.API_HASH,
259-
bot_token=Config.Token,
260-
sleep_threshold=Config.Sleep_Threshold)
253+
bot = TelegramClient(Config.Session, api_id=Config.API_ID, api_hash=Config.API_HASH,
254+
bot_token=Config.Token, sleep_threshold=Config.Sleep_Threshold)

FileToLink/config.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ class Config:
1212
Port = int(os.environ.get("PORT"))
1313
Archive_Channel_ID = int(os.environ.get("ARCHIVE_CHANNEL_ID"))
1414
Start_Message = os.environ.get("Start_Message")
15+
16+
Link_Root = f"https://{App_Name}.herokuapp.com/"
17+
Download_Folder = "Files"
1518
Bot_Channel = "shadow_bots"
1619
Bot_UserName = None # The bot will set it after starting
1720
Part_size = 10 * 1024 * 1024 # (10MB) For Pyrogram
1821
Buffer_Size = 256 * 1024 # For Quart
1922
Pre_Dl = 3 # How many parts to download from telegram before client request them
2023
Separate_Time = 4 # (seconds) wait time between messages if user send more than one
2124
Sleep_Threshold = 60 # (Seconds) sleep threshold for flood wait exceptions
22-
Link_Root = f"https://{App_Name}.herokuapp.com/"
23-
Download_Folder = "Files"
25+
Max_Fast_Processes = 1 # How many links user can update them to fast links at the same time
2426

2527

2628
class Strings:
@@ -29,3 +31,15 @@ class Strings:
2931
st_link = "🎞 Stream LINK"
3032
generating_link = "**⏳ Generating Link...**"
3133
join_channel = "📢 Bot Channel"
34+
fast = "⚡️**The link has been updated to a fast link**"
35+
update_link = "⚡ Update To Fast Link"
36+
update_limited = (f"⛔ You can update just {Config.Max_Fast_Processes} link in one time, "
37+
"please wait until previous update to complete")
38+
re_update_link = "🔄 Re-Updating the link"
39+
already_updated = "The link is already updated"
40+
wait_update = "⏳ Updating the link..."
41+
wait = "⏳ Please wait..."
42+
progress = "⏳ Progress"
43+
file_not_found = "⚠️File Not Found, Please resend it again"
44+
delete_manually_button = "⚠️You can delete it"
45+
delete_forbidden = "The bot can't delete messages older than 48 hours, you can delete this message manually"

FileToLink/server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ async def download(archive_id: int):
6868
try:
6969
worker: Worker = await create_worker(archive_id)
7070
except (ValueError, MessageIdInvalid):
71+
# This Message not found in Archive Channel
7172
NotFound.append(archive_id)
7273
return abort(404) # Not Found
7374

FileToLink/worker.py

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@
44
import os
55

66
from pyrogram import filters
7-
from pyrogram.errors import MessageDeleteForbidden
7+
from pyrogram.errors import MessageDeleteForbidden, MessageIdInvalid
88
from pyrogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
99

10-
from FileToLink import bot, Config
10+
from FileToLink import bot, Config, Strings
1111

1212

1313
class Worker:
1414
def __init__(self, msg: Message):
15+
"""
16+
:param msg: Message from Archive Channel
17+
"""
1518
if msg.empty:
1619
raise ValueError
1720
self.msg = msg
21+
self.archive_id = msg.message_id
1822
self.media = (msg.video or msg.document or msg.photo or msg.audio or
1923
msg.voice or msg.video_note or msg.sticker or msg.animation)
2024
self.size = self.media.file_size
2125
self.id = self.media.file_unique_id
22-
self.link = None
26+
self.link = f'{Config.Link_Root}dl/{self.archive_id}'
2327
self.current_dl: int = 0 # Number of currently downloading parts
2428

2529
if hasattr(self.media, 'mime_type') and self.media.mime_type not in (None, ''):
@@ -53,11 +57,9 @@ def __init__(self, msg: Message):
5357
self.parts = [False for _ in
5458
range(int(self.size / Config.Part_size) + (1 if self.size % Config.Part_size else 0))]
5559
self.done = False # If All parts are downloaded
60+
self.fast = False # If User update to Fast Link
5661
AllWorkers.add(self)
5762

58-
def set_link(self, archive_id: int):
59-
self.link = f'{Config.Link_Root}dl/{archive_id}'
60-
6163
async def create_file(self):
6264
"""
6365
Create empty file with same size of real file
@@ -151,36 +153,38 @@ def part_number(self, byte_number: int):
151153

152154
class Workers:
153155
def __init__(self):
154-
"""
155-
Store Workers by file_unique_id and by archive_id
156-
"""
156+
""" Store Workers by file_unique_id and by archive_id """
157157
self.by_file_id = {}
158158
self.by_archive_id = {}
159159

160160
def get(self, archive_id: int = None, file_id: str = None):
161+
"""Get the worker by archive_id or by file_id"""
161162
if archive_id is not None and archive_id in self.by_archive_id:
162163
return self.by_archive_id[archive_id]
163164
elif file_id is not None and file_id in self.by_file_id:
164165
return self.by_file_id[file_id]
165166
else:
166167
return None
167168

168-
def add(self, worker, archive_id: int = None):
169+
def add(self, worker):
170+
"""Add the worker to <self.by_file_id> and <self.by_archive_id>"""
169171
if worker.id not in self.by_file_id:
170172
self.by_file_id[worker.id] = worker
171-
if archive_id is not None and archive_id not in self.by_archive_id:
172-
self.by_archive_id[archive_id] = worker
173-
174-
def remove(self, archive_id: int = None, file_id: str = None):
175-
if archive_id is not None and archive_id in self.by_archive_id:
176-
del self.by_file_id[self.by_archive_id[archive_id].id]
173+
if worker.msg.message_id not in self.by_archive_id:
174+
self.by_archive_id[worker.archive_id] = worker
175+
176+
def remove(self, archive_id: int):
177+
"""Remove the worker from <self.by_file_id> and <self.by_archive_id>"""
178+
if archive_id in self.by_archive_id:
179+
file_id = self.by_archive_id[archive_id].id
180+
if file_id in self.by_file_id:
181+
del self.by_file_id[file_id]
177182
del self.by_archive_id[archive_id]
178-
elif file_id is not None and file_id in self.by_file_id:
179-
del self.by_file_id[file_id]
180183

181184

182185
AllWorkers = Workers()
183186
NotFound = [] # Store IDs of messages that not exist in Archive Channel
187+
FastProcesses = {} # {User_ID: Number_Of_Link_Updating}
184188

185189

186190
async def create_worker(archive_msg_id):
@@ -190,17 +194,84 @@ async def create_worker(archive_msg_id):
190194
if msg.empty or not msg.media:
191195
raise ValueError
192196
worker = Worker(msg)
193-
AllWorkers.add(worker, archive_id=archive_msg_id)
197+
AllWorkers.add(worker)
194198
await worker.create_file()
195199
return worker
196200

197201

202+
@bot.on_callback_query(filters.create(lambda _, __, cb: cb.data.split('|')[0] == 'fast'))
203+
async def update_to_fast_link(_, cb: CallbackQuery):
204+
msg = cb.message
205+
user_id = msg.chat.id
206+
if user_id in FastProcesses and FastProcesses[user_id] >= Config.Max_Fast_Processes:
207+
await cb.answer(Strings.update_limited, show_alert=True)
208+
return
209+
210+
archive_id = int(cb.data.split('|')[1])
211+
worker: Worker = AllWorkers.get(archive_id=archive_id)
212+
if worker is None:
213+
try:
214+
worker = await create_worker(archive_id)
215+
except (ValueError, MessageIdInvalid):
216+
await cb.answer(Strings.file_not_found, show_alert=True)
217+
return
218+
if worker.fast:
219+
await cb.answer(Strings.already_updated, show_alert=True)
220+
return
221+
222+
buttons = cb.message.reply_markup.inline_keyboard
223+
update_button_row = -1 # Last Row
224+
old_data = buttons[update_button_row][0].callback_data
225+
buttons[update_button_row][0].text = Strings.wait_update
226+
new_data = f"fast-prog|{archive_id}"
227+
buttons[update_button_row][0].callback_data = new_data
228+
229+
await cb.answer(Strings.wait)
230+
await cb.message.edit_reply_markup(InlineKeyboardMarkup(buttons))
231+
progress = await msg.reply_text(
232+
Strings.wait_update, reply_to_message_id=msg.message_id,
233+
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(Strings.progress, callback_data=new_data)]]))
234+
235+
if user_id in FastProcesses:
236+
FastProcesses[user_id] += 1
237+
else:
238+
FastProcesses[user_id] = 1
239+
240+
await worker.dl_all()
241+
worker.fast = True
242+
243+
FastProcesses[user_id] -= 1
244+
245+
buttons[update_button_row][0].text = Strings.re_update_link
246+
buttons[update_button_row][0].callback_data = old_data
247+
await msg.edit_reply_markup(InlineKeyboardMarkup(buttons))
248+
await progress.edit_text(Strings.fast)
249+
250+
251+
@bot.on_callback_query(filters.create(lambda _, __, cb: cb.data.split('|')[0] == 'fast-prog'))
252+
async def fast_progress(_, cb: CallbackQuery):
253+
archive_id = int(cb.data.split('|')[1])
254+
worker = AllWorkers.get(archive_id=archive_id)
255+
if worker is None:
256+
await cb.answer(Strings.file_not_found, show_alert=True)
257+
return
258+
downloaded = len([i for i in worker.parts if i])
259+
total = len(worker.parts)
260+
await cb.answer(progress_bar(downloaded, total), show_alert=True)
261+
262+
198263
@bot.on_callback_query(filters.create(lambda _, __, cb: cb.data == 'delete-file'))
199264
async def delete_file_handler(_, cb: CallbackQuery):
200265
msg = cb.message
201266
AllWorkers.remove(msg.message_id)
202267
try:
203268
await msg.delete()
204269
except MessageDeleteForbidden:
205-
button = InlineKeyboardButton("⚠️You can delete it", callback_data='time-out')
270+
button = InlineKeyboardButton(Strings.delete_manually_button, callback_data='time-out')
206271
await msg.edit_reply_markup(InlineKeyboardMarkup([[button]]))
272+
273+
274+
def progress_bar(current, total, length=16, finished='█', unfinished='░'):
275+
rate = current / total
276+
finished_len = int(length * rate) if rate <= 1 else length
277+
return f'{finished * finished_len}{unfinished * (length - finished_len)} {int(rate * 100)}%'

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Press the below button to deploy to Heroku
2424

2525
`ARCHIVE_CHANNEL_ID` : Create a new channel (private/public), post anything in the channel, forward the post to @message_ids_bot, now copy and paste chat_forward in this field.
2626

27-
`Session_String` : [Not Required] Use session_generator.py to generate Pyrogram Session String, If you don't add it the bot will create a new session every Restarting.
27+
`Session_String` : [Optional] Use session_generator.py to generate Pyrogram Session String, If you don't add it the bot will create a new session every Restarting.
2828

2929

3030
## How to use the bot

app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"value": "Hello, Send me any file to get download and Stream links"
3333
},
3434
"Session_String": {
35-
"description": "[Not Required] Pyrogram Session String, use session_generator.py to generate it, If you don't add it the bot will create a new session every Restarting.",
35+
"description": "[Optional] Pyrogram Session String, use session_generator.py to generate it, If you don't add it the bot will create a new session every Restarting.",
3636
"required": false
3737
}
3838
},

0 commit comments

Comments
 (0)