Spaces:
Running
Running
Upload 29 files
Browse files- Dockerfile +11 -0
- FileStream/__init__.py +5 -0
- FileStream/__main__.py +82 -0
- FileStream/bot/__init__.py +25 -0
- FileStream/bot/clients.py +59 -0
- FileStream/bot/plugins/admin.py +160 -0
- FileStream/bot/plugins/callback.py +198 -0
- FileStream/bot/plugins/start.py +119 -0
- FileStream/bot/plugins/stream.py +95 -0
- FileStream/config.py +37 -0
- FileStream/server/__init__.py +7 -0
- FileStream/server/exceptions.py +5 -0
- FileStream/server/stream_routes.py +147 -0
- FileStream/template/dl.html +56 -0
- FileStream/template/stream.html +154 -0
- FileStream/utils/__init__.py +4 -0
- FileStream/utils/bot_utils.py +177 -0
- FileStream/utils/broadcast_helper.py +19 -0
- FileStream/utils/custom_dl.py +214 -0
- FileStream/utils/database.py +136 -0
- FileStream/utils/file_properties.py +146 -0
- FileStream/utils/human_readable.py +10 -0
- FileStream/utils/keepalive.py +20 -0
- FileStream/utils/render_template.py +25 -0
- FileStream/utils/time_format.py +22 -0
- FileStream/utils/translation.py +72 -0
- Procfile +1 -0
- app.json +111 -0
- requirements.txt +8 -0
Dockerfile
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.8
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
COPY . /app
|
5 |
+
|
6 |
+
RUN pip install --upgrade pip
|
7 |
+
RUN pip install -r requirements.txt
|
8 |
+
|
9 |
+
COPY . .
|
10 |
+
|
11 |
+
CMD ["python", "-m", "FileStream"]
|
FileStream/__init__.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
__version__ = "1.0.1"
|
4 |
+
StartTime = time.time()
|
5 |
+
|
FileStream/__main__.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import asyncio
|
3 |
+
import logging
|
4 |
+
import traceback
|
5 |
+
import logging.handlers as handlers
|
6 |
+
from .config import Telegram, Server
|
7 |
+
from aiohttp import web
|
8 |
+
from pyrogram import idle
|
9 |
+
|
10 |
+
from FileStream.bot import FileStream
|
11 |
+
from FileStream.server import web_server
|
12 |
+
from FileStream.utils import ping_server
|
13 |
+
from FileStream.bot.clients import initialize_clients
|
14 |
+
|
15 |
+
logging.basicConfig(
|
16 |
+
level=logging.INFO,
|
17 |
+
datefmt="%d/%m/%Y %H:%M:%S",
|
18 |
+
format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
|
19 |
+
handlers=[logging.StreamHandler(stream=sys.stdout),
|
20 |
+
handlers.RotatingFileHandler("streambot.log", mode="a", maxBytes=104857600, backupCount=2, encoding="utf-8")],)
|
21 |
+
|
22 |
+
logging.getLogger("aiohttp").setLevel(logging.ERROR)
|
23 |
+
logging.getLogger("pyrogram").setLevel(logging.ERROR)
|
24 |
+
logging.getLogger("aiohttp.web").setLevel(logging.ERROR)
|
25 |
+
|
26 |
+
server = web.AppRunner(web_server())
|
27 |
+
|
28 |
+
loop = asyncio.get_event_loop()
|
29 |
+
|
30 |
+
async def start_services():
|
31 |
+
print()
|
32 |
+
if Telegram.SECONDARY:
|
33 |
+
print("------------------ Starting as Secondary Server ------------------")
|
34 |
+
else:
|
35 |
+
print("------------------- Starting as Primary Server -------------------")
|
36 |
+
print()
|
37 |
+
print("-------------------- Initializing Telegram Bot --------------------")
|
38 |
+
|
39 |
+
|
40 |
+
await FileStream.start()
|
41 |
+
bot_info = await FileStream.get_me()
|
42 |
+
FileStream.id = bot_info.id
|
43 |
+
FileStream.username = bot_info.username
|
44 |
+
FileStream.fname=bot_info.first_name
|
45 |
+
print("------------------------------ DONE ------------------------------")
|
46 |
+
print()
|
47 |
+
print("---------------------- Initializing Clients ----------------------")
|
48 |
+
await initialize_clients()
|
49 |
+
print("------------------------------ DONE ------------------------------")
|
50 |
+
if Server.KEEP_ALIVE:
|
51 |
+
print("------------------ Starting Keep Alive Service ------------------")
|
52 |
+
print()
|
53 |
+
asyncio.create_task(ping_server())
|
54 |
+
print()
|
55 |
+
print("--------------------- Initializing Web Server ---------------------")
|
56 |
+
await server.setup()
|
57 |
+
await web.TCPSite(server, Server.BIND_ADDRESS, Server.PORT).start()
|
58 |
+
print("------------------------------ DONE ------------------------------")
|
59 |
+
print()
|
60 |
+
print("------------------------- Service Started -------------------------")
|
61 |
+
print(" bot =>> {}".format(bot_info.first_name))
|
62 |
+
if bot_info.dc_id:
|
63 |
+
print(" DC ID =>> {}".format(str(bot_info.dc_id)))
|
64 |
+
print(" URL =>> {}".format(Server.URL))
|
65 |
+
print("------------------------------------------------------------------")
|
66 |
+
await idle()
|
67 |
+
|
68 |
+
async def cleanup():
|
69 |
+
await server.cleanup()
|
70 |
+
await FileStream.stop()
|
71 |
+
|
72 |
+
if __name__ == "__main__":
|
73 |
+
try:
|
74 |
+
loop.run_until_complete(start_services())
|
75 |
+
except KeyboardInterrupt:
|
76 |
+
pass
|
77 |
+
except Exception as err:
|
78 |
+
logging.error(traceback.format_exc())
|
79 |
+
finally:
|
80 |
+
loop.run_until_complete(cleanup())
|
81 |
+
loop.stop()
|
82 |
+
print("------------------------ Stopped Services ------------------------")
|
FileStream/bot/__init__.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ..config import Telegram
|
2 |
+
from pyrogram import Client
|
3 |
+
|
4 |
+
if Telegram.SECONDARY:
|
5 |
+
plugins=None
|
6 |
+
no_updates=True
|
7 |
+
else:
|
8 |
+
plugins={"root": "FileStream/bot/plugins"}
|
9 |
+
no_updates=None
|
10 |
+
|
11 |
+
FileStream = Client(
|
12 |
+
name="FileStream",
|
13 |
+
api_id=Telegram.API_ID,
|
14 |
+
api_hash=Telegram.API_HASH,
|
15 |
+
workdir="FileStream",
|
16 |
+
plugins=plugins,
|
17 |
+
bot_token=Telegram.BOT_TOKEN,
|
18 |
+
sleep_threshold=Telegram.SLEEP_THRESHOLD,
|
19 |
+
workers=Telegram.WORKERS,
|
20 |
+
no_updates=no_updates
|
21 |
+
)
|
22 |
+
|
23 |
+
multi_clients = {}
|
24 |
+
work_loads = {}
|
25 |
+
|
FileStream/bot/clients.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import logging
|
3 |
+
from os import environ
|
4 |
+
from ..config import Telegram
|
5 |
+
from pyrogram import Client
|
6 |
+
from . import multi_clients, work_loads, FileStream
|
7 |
+
|
8 |
+
|
9 |
+
async def initialize_clients():
|
10 |
+
all_tokens = dict(
|
11 |
+
(c + 1, t)
|
12 |
+
for c, (_, t) in enumerate(
|
13 |
+
filter(
|
14 |
+
lambda n: n[0].startswith("MULTI_TOKEN"), sorted(environ.items())
|
15 |
+
)
|
16 |
+
)
|
17 |
+
)
|
18 |
+
if not all_tokens:
|
19 |
+
multi_clients[0] = FileStream
|
20 |
+
work_loads[0] = 0
|
21 |
+
print("No additional clients found, using default client")
|
22 |
+
return
|
23 |
+
|
24 |
+
async def start_client(client_id, token):
|
25 |
+
try:
|
26 |
+
if len(token) >= 100:
|
27 |
+
session_string=token
|
28 |
+
bot_token=None
|
29 |
+
print(f'Starting Client - {client_id} Using Session String')
|
30 |
+
else:
|
31 |
+
session_string=None
|
32 |
+
bot_token=token
|
33 |
+
print(f'Starting Client - {client_id} Using Bot Token')
|
34 |
+
if client_id == len(all_tokens):
|
35 |
+
await asyncio.sleep(2)
|
36 |
+
print("This will take some time, please wait...")
|
37 |
+
client = await Client(
|
38 |
+
name=str(client_id),
|
39 |
+
api_id=Telegram.API_ID,
|
40 |
+
api_hash=Telegram.API_HASH,
|
41 |
+
bot_token=bot_token,
|
42 |
+
sleep_threshold=Telegram.SLEEP_THRESHOLD,
|
43 |
+
no_updates=True,
|
44 |
+
session_string=session_string,
|
45 |
+
in_memory=True,
|
46 |
+
).start()
|
47 |
+
client.id = (await client.get_me()).id
|
48 |
+
work_loads[client_id] = 0
|
49 |
+
return client_id, client
|
50 |
+
except Exception:
|
51 |
+
logging.error(f"Failed starting Client - {client_id} Error:", exc_info=True)
|
52 |
+
|
53 |
+
clients = await asyncio.gather(*[start_client(i, token) for i, token in all_tokens.items()])
|
54 |
+
multi_clients.update(dict(clients))
|
55 |
+
if len(multi_clients) != 1:
|
56 |
+
Telegram.MULTI_CLIENT = True
|
57 |
+
print("Multi-Client Mode Enabled")
|
58 |
+
else:
|
59 |
+
print("No additional clients were initialized, using default client")
|
FileStream/bot/plugins/admin.py
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import time
|
3 |
+
import string
|
4 |
+
import random
|
5 |
+
import asyncio
|
6 |
+
import aiofiles
|
7 |
+
import datetime
|
8 |
+
|
9 |
+
from FileStream.utils.broadcast_helper import send_msg
|
10 |
+
from FileStream.utils.database import Database
|
11 |
+
from FileStream.bot import FileStream
|
12 |
+
from FileStream.server.exceptions import FIleNotFound
|
13 |
+
from FileStream.config import Telegram, Server
|
14 |
+
from pyrogram import filters, Client
|
15 |
+
from pyrogram.types import Message
|
16 |
+
from pyrogram.enums.parse_mode import ParseMode
|
17 |
+
|
18 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
19 |
+
broadcast_ids = {}
|
20 |
+
|
21 |
+
|
22 |
+
@FileStream.on_message(filters.command("status") & filters.private & filters.user(Telegram.OWNER_ID))
|
23 |
+
async def sts(c: Client, m: Message):
|
24 |
+
await m.reply_text(text=f"""**Total Users in DB:** `{await db.total_users_count()}`
|
25 |
+
**Banned Users in DB:** `{await db.total_banned_users_count()}`
|
26 |
+
**Total Links Generated: ** `{await db.total_files()}`"""
|
27 |
+
, parse_mode=ParseMode.MARKDOWN, quote=True)
|
28 |
+
|
29 |
+
|
30 |
+
@FileStream.on_message(filters.command("ban") & filters.private & filters.user(Telegram.OWNER_ID))
|
31 |
+
async def sts(b, m: Message):
|
32 |
+
id = m.text.split("/ban ")[-1]
|
33 |
+
if not await db.is_user_banned(int(id)):
|
34 |
+
try:
|
35 |
+
await db.ban_user(int(id))
|
36 |
+
await db.delete_user(int(id))
|
37 |
+
await m.reply_text(text=f"`{id}`** is Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
38 |
+
if not str(id).startswith('-100'):
|
39 |
+
await b.send_message(
|
40 |
+
chat_id=id,
|
41 |
+
text="**Your Banned to Use The Bot**",
|
42 |
+
parse_mode=ParseMode.MARKDOWN,
|
43 |
+
disable_web_page_preview=True
|
44 |
+
)
|
45 |
+
except Exception as e:
|
46 |
+
await m.reply_text(text=f"**something went wrong: {e}** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
47 |
+
else:
|
48 |
+
await m.reply_text(text=f"`{id}`** is Already Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
49 |
+
|
50 |
+
|
51 |
+
@FileStream.on_message(filters.command("unban") & filters.private & filters.user(Telegram.OWNER_ID))
|
52 |
+
async def sts(b, m: Message):
|
53 |
+
id = m.text.split("/unban ")[-1]
|
54 |
+
if await db.is_user_banned(int(id)):
|
55 |
+
try:
|
56 |
+
await db.unban_user(int(id))
|
57 |
+
await m.reply_text(text=f"`{id}`** is Unbanned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
58 |
+
if not str(id).startswith('-100'):
|
59 |
+
await b.send_message(
|
60 |
+
chat_id=id,
|
61 |
+
text="**Your Unbanned now Use can use The Bot**",
|
62 |
+
parse_mode=ParseMode.MARKDOWN,
|
63 |
+
disable_web_page_preview=True
|
64 |
+
)
|
65 |
+
except Exception as e:
|
66 |
+
await m.reply_text(text=f"** something went wrong: {e}**", parse_mode=ParseMode.MARKDOWN, quote=True)
|
67 |
+
else:
|
68 |
+
await m.reply_text(text=f"`{id}`** is not Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
69 |
+
|
70 |
+
|
71 |
+
@FileStream.on_message(filters.command("broadcast") & filters.private & filters.user(Telegram.OWNER_ID) & filters.reply)
|
72 |
+
async def broadcast_(c, m):
|
73 |
+
all_users = await db.get_all_users()
|
74 |
+
broadcast_msg = m.reply_to_message
|
75 |
+
while True:
|
76 |
+
broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)])
|
77 |
+
if not broadcast_ids.get(broadcast_id):
|
78 |
+
break
|
79 |
+
out = await m.reply_text(
|
80 |
+
text=f"Broadcast initiated! You will be notified with log file when all the users are notified."
|
81 |
+
)
|
82 |
+
start_time = time.time()
|
83 |
+
total_users = await db.total_users_count()
|
84 |
+
done = 0
|
85 |
+
failed = 0
|
86 |
+
success = 0
|
87 |
+
broadcast_ids[broadcast_id] = dict(
|
88 |
+
total=total_users,
|
89 |
+
current=done,
|
90 |
+
failed=failed,
|
91 |
+
success=success
|
92 |
+
)
|
93 |
+
async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file:
|
94 |
+
async for user in all_users:
|
95 |
+
sts, msg = await send_msg(
|
96 |
+
user_id=int(user['id']),
|
97 |
+
message=broadcast_msg
|
98 |
+
)
|
99 |
+
if msg is not None:
|
100 |
+
await broadcast_log_file.write(msg)
|
101 |
+
if sts == 200:
|
102 |
+
success += 1
|
103 |
+
else:
|
104 |
+
failed += 1
|
105 |
+
if sts == 400:
|
106 |
+
await db.delete_user(user['id'])
|
107 |
+
done += 1
|
108 |
+
if broadcast_ids.get(broadcast_id) is None:
|
109 |
+
break
|
110 |
+
else:
|
111 |
+
broadcast_ids[broadcast_id].update(
|
112 |
+
dict(
|
113 |
+
current=done,
|
114 |
+
failed=failed,
|
115 |
+
success=success
|
116 |
+
)
|
117 |
+
)
|
118 |
+
try:
|
119 |
+
await out.edit_text(f"Broadcast Status\n\ncurrent: {done}\nfailed:{failed}\nsuccess: {success}")
|
120 |
+
except:
|
121 |
+
pass
|
122 |
+
if broadcast_ids.get(broadcast_id):
|
123 |
+
broadcast_ids.pop(broadcast_id)
|
124 |
+
completed_in = datetime.timedelta(seconds=int(time.time() - start_time))
|
125 |
+
await asyncio.sleep(3)
|
126 |
+
await out.delete()
|
127 |
+
if failed == 0:
|
128 |
+
await m.reply_text(
|
129 |
+
text=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.",
|
130 |
+
quote=True
|
131 |
+
)
|
132 |
+
else:
|
133 |
+
await m.reply_document(
|
134 |
+
document='broadcast.txt',
|
135 |
+
caption=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.",
|
136 |
+
quote=True
|
137 |
+
)
|
138 |
+
os.remove('broadcast.txt')
|
139 |
+
|
140 |
+
|
141 |
+
@FileStream.on_message(filters.command("del") & filters.private & filters.user(Telegram.OWNER_ID))
|
142 |
+
async def sts(c: Client, m: Message):
|
143 |
+
file_id = m.text.split(" ")[-1]
|
144 |
+
try:
|
145 |
+
file_info = await db.get_file(file_id)
|
146 |
+
except FIleNotFound:
|
147 |
+
await m.reply_text(
|
148 |
+
text=f"**File Already Deleted**",
|
149 |
+
quote=True
|
150 |
+
)
|
151 |
+
return
|
152 |
+
await db.delete_one_file(file_info['_id'])
|
153 |
+
await db.count_links(file_info['user_id'], "-")
|
154 |
+
await m.reply_text(
|
155 |
+
text=f"**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !** ",
|
156 |
+
quote=True
|
157 |
+
)
|
158 |
+
|
159 |
+
|
160 |
+
|
FileStream/bot/plugins/callback.py
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import datetime
|
2 |
+
import math
|
3 |
+
from FileStream import __version__
|
4 |
+
from FileStream.bot import FileStream
|
5 |
+
from FileStream.config import Telegram, Server
|
6 |
+
from FileStream.utils.translation import LANG, BUTTON
|
7 |
+
from FileStream.utils.bot_utils import gen_link
|
8 |
+
from FileStream.utils.database import Database
|
9 |
+
from FileStream.utils.human_readable import humanbytes
|
10 |
+
from FileStream.server.exceptions import FIleNotFound
|
11 |
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
12 |
+
from pyrogram.file_id import FileId, FileType, PHOTO_TYPES
|
13 |
+
from pyrogram.enums.parse_mode import ParseMode
|
14 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
15 |
+
|
16 |
+
#---------------------[ START CMD ]---------------------#
|
17 |
+
@FileStream.on_callback_query()
|
18 |
+
async def cb_data(bot, update: CallbackQuery):
|
19 |
+
usr_cmd = update.data.split("_")
|
20 |
+
if usr_cmd[0] == "home":
|
21 |
+
await update.message.edit_text(
|
22 |
+
text=LANG.START_TEXT.format(update.from_user.mention, FileStream.username),
|
23 |
+
disable_web_page_preview=True,
|
24 |
+
reply_markup=BUTTON.START_BUTTONS
|
25 |
+
)
|
26 |
+
elif usr_cmd[0] == "help":
|
27 |
+
await update.message.edit_text(
|
28 |
+
text=LANG.HELP_TEXT.format(Telegram.OWNER_ID),
|
29 |
+
disable_web_page_preview=True,
|
30 |
+
reply_markup=BUTTON.HELP_BUTTONS
|
31 |
+
)
|
32 |
+
elif usr_cmd[0] == "about":
|
33 |
+
await update.message.edit_text(
|
34 |
+
text=LANG.ABOUT_TEXT.format(FileStream.fname, __version__),
|
35 |
+
disable_web_page_preview=True,
|
36 |
+
reply_markup=BUTTON.ABOUT_BUTTONS
|
37 |
+
)
|
38 |
+
|
39 |
+
#---------------------[ MY FILES CMD ]---------------------#
|
40 |
+
|
41 |
+
elif usr_cmd[0] == "N/A":
|
42 |
+
await update.answer("N/A", True)
|
43 |
+
elif usr_cmd[0] == "close":
|
44 |
+
await update.message.delete()
|
45 |
+
elif usr_cmd[0] == "msgdelete":
|
46 |
+
await update.message.edit_caption(
|
47 |
+
caption= "**Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ**\n\n",
|
48 |
+
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʏᴇs", callback_data=f"msgdelyes_{usr_cmd[1]}_{usr_cmd[2]}"), InlineKeyboardButton("ɴᴏ", callback_data=f"myfile_{usr_cmd[1]}_{usr_cmd[2]}")]])
|
49 |
+
)
|
50 |
+
elif usr_cmd[0] == "msgdelyes":
|
51 |
+
await delete_user_file(usr_cmd[1], int(usr_cmd[2]), update)
|
52 |
+
return
|
53 |
+
elif usr_cmd[0] == "msgdelpvt":
|
54 |
+
await update.message.edit_caption(
|
55 |
+
caption= "**Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ**\n\n",
|
56 |
+
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʏᴇs", callback_data=f"msgdelpvtyes_{usr_cmd[1]}"), InlineKeyboardButton("ɴᴏ", callback_data=f"mainstream_{usr_cmd[1]}")]])
|
57 |
+
)
|
58 |
+
elif usr_cmd[0] == "msgdelpvtyes":
|
59 |
+
await delete_user_filex(usr_cmd[1], update)
|
60 |
+
return
|
61 |
+
|
62 |
+
elif usr_cmd[0] == "mainstream":
|
63 |
+
_id = usr_cmd[1]
|
64 |
+
reply_markup, stream_text = await gen_link(_id=_id)
|
65 |
+
await update.message.edit_text(
|
66 |
+
text=stream_text,
|
67 |
+
parse_mode=ParseMode.HTML,
|
68 |
+
disable_web_page_preview=True,
|
69 |
+
reply_markup=reply_markup,
|
70 |
+
)
|
71 |
+
|
72 |
+
elif usr_cmd[0] == "userfiles":
|
73 |
+
file_list, total_files = await gen_file_list_button(int(usr_cmd[1]), update.from_user.id)
|
74 |
+
await update.message.edit_caption(
|
75 |
+
caption="Total files: {}".format(total_files),
|
76 |
+
reply_markup=InlineKeyboardMarkup(file_list)
|
77 |
+
)
|
78 |
+
elif usr_cmd[0] == "myfile":
|
79 |
+
await gen_file_menu(usr_cmd[1], usr_cmd[2], update)
|
80 |
+
return
|
81 |
+
elif usr_cmd[0] == "sendfile":
|
82 |
+
myfile = await db.get_file(usr_cmd[1])
|
83 |
+
file_name = myfile['file_name']
|
84 |
+
await update.answer(f"Sending File {file_name}")
|
85 |
+
await update.message.reply_cached_media(myfile['file_id'], caption=f'**{file_name}**')
|
86 |
+
else:
|
87 |
+
await update.message.delete()
|
88 |
+
|
89 |
+
|
90 |
+
|
91 |
+
#---------------------[ MY FILES FUNC ]---------------------#
|
92 |
+
|
93 |
+
async def gen_file_list_button(file_list_no: int, user_id: int):
|
94 |
+
|
95 |
+
file_range=[file_list_no*10-10+1, file_list_no*10]
|
96 |
+
user_files, total_files=await db.find_files(user_id, file_range)
|
97 |
+
|
98 |
+
file_list=[]
|
99 |
+
async for x in user_files:
|
100 |
+
file_list.append([InlineKeyboardButton(x["file_name"], callback_data=f"myfile_{x['_id']}_{file_list_no}")])
|
101 |
+
if total_files > 10:
|
102 |
+
file_list.append(
|
103 |
+
[InlineKeyboardButton("◄", callback_data="{}".format("userfiles_"+str(file_list_no-1) if file_list_no > 1 else 'N/A')),
|
104 |
+
InlineKeyboardButton(f"{file_list_no}/{math.ceil(total_files/10)}", callback_data="N/A"),
|
105 |
+
InlineKeyboardButton("►", callback_data="{}".format("userfiles_"+str(file_list_no+1) if total_files > file_list_no*10 else 'N/A'))]
|
106 |
+
)
|
107 |
+
if not file_list:
|
108 |
+
file_list.append(
|
109 |
+
[InlineKeyboardButton("ᴇᴍᴘᴛʏ", callback_data="N/A")])
|
110 |
+
file_list.append([InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")])
|
111 |
+
return file_list, total_files
|
112 |
+
|
113 |
+
async def gen_file_menu(_id, file_list_no, update: CallbackQuery):
|
114 |
+
try:
|
115 |
+
myfile_info=await db.get_file(_id)
|
116 |
+
except FIleNotFound:
|
117 |
+
await update.answer("File Not Found")
|
118 |
+
return
|
119 |
+
|
120 |
+
file_id=FileId.decode(myfile_info['file_id'])
|
121 |
+
|
122 |
+
if file_id.file_type in PHOTO_TYPES:
|
123 |
+
file_type = "Image"
|
124 |
+
elif file_id.file_type == FileType.VOICE:
|
125 |
+
file_type = "Voice"
|
126 |
+
elif file_id.file_type in (FileType.VIDEO, FileType.ANIMATION, FileType.VIDEO_NOTE):
|
127 |
+
file_type = "Video"
|
128 |
+
elif file_id.file_type == FileType.DOCUMENT:
|
129 |
+
file_type = "Document"
|
130 |
+
elif file_id.file_type == FileType.STICKER:
|
131 |
+
file_type = "Sticker"
|
132 |
+
elif file_id.file_type == FileType.AUDIO:
|
133 |
+
file_type = "Audio"
|
134 |
+
else:
|
135 |
+
file_type = "Unknown"
|
136 |
+
|
137 |
+
page_link = f"{Server.URL}watch/{myfile_info['_id']}"
|
138 |
+
stream_link = f"{Server.URL}dl/{myfile_info['_id']}"
|
139 |
+
if "video" in file_type.lower():
|
140 |
+
MYFILES_BUTTONS = InlineKeyboardMarkup(
|
141 |
+
[
|
142 |
+
[InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
143 |
+
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", callback_data=f"sendfile_{myfile_info['_id']}"),
|
144 |
+
InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelete_{myfile_info['_id']}_{file_list_no}")],
|
145 |
+
[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="userfiles_{}".format(file_list_no))]
|
146 |
+
]
|
147 |
+
)
|
148 |
+
else:
|
149 |
+
MYFILES_BUTTONS = InlineKeyboardMarkup(
|
150 |
+
[
|
151 |
+
[InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
152 |
+
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", callback_data=f"sendfile_{myfile_info['_id']}"),
|
153 |
+
InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelete_{myfile_info['_id']}_{file_list_no}")],
|
154 |
+
[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="userfiles_{}".format(file_list_no))]
|
155 |
+
]
|
156 |
+
)
|
157 |
+
|
158 |
+
TiMe = myfile_info['time']
|
159 |
+
if type(TiMe) == float:
|
160 |
+
date = datetime.datetime.fromtimestamp(TiMe)
|
161 |
+
await update.edit_message_caption(
|
162 |
+
caption="**File Name :** `{}`\n**File Size :** `{}`\n**File Type :** `{}`\n**Created On :** `{}`".format(myfile_info['file_name'],
|
163 |
+
humanbytes(int(myfile_info['file_size'])),
|
164 |
+
file_type,
|
165 |
+
TiMe if isinstance(TiMe,str) else date.date()),
|
166 |
+
reply_markup=MYFILES_BUTTONS )
|
167 |
+
|
168 |
+
|
169 |
+
async def delete_user_file(_id, file_list_no: int, update:CallbackQuery):
|
170 |
+
|
171 |
+
try:
|
172 |
+
myfile_info=await db.get_file(_id)
|
173 |
+
except FIleNotFound:
|
174 |
+
await update.answer("File Already Deleted")
|
175 |
+
return
|
176 |
+
|
177 |
+
await db.delete_one_file(myfile_info['_id'])
|
178 |
+
await db.count_links(update.from_user.id, "-")
|
179 |
+
await update.message.edit_caption(
|
180 |
+
caption= "**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !**" + update.message.caption.replace("Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ", ""),
|
181 |
+
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data=f"userfiles_1")]])
|
182 |
+
)
|
183 |
+
|
184 |
+
async def delete_user_filex(_id, update:CallbackQuery):
|
185 |
+
|
186 |
+
try:
|
187 |
+
myfile_info=await db.get_file(_id)
|
188 |
+
except FIleNotFound:
|
189 |
+
await update.answer("File Already Deleted")
|
190 |
+
return
|
191 |
+
|
192 |
+
await db.delete_one_file(myfile_info['_id'])
|
193 |
+
await db.count_links(update.from_user.id, "-")
|
194 |
+
await update.message.edit_caption(
|
195 |
+
caption= "**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !**\n\n",
|
196 |
+
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data=f"close")]])
|
197 |
+
)
|
198 |
+
|
FileStream/bot/plugins/start.py
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import math
|
3 |
+
from FileStream import __version__
|
4 |
+
from FileStream.bot import FileStream
|
5 |
+
from FileStream.server.exceptions import FIleNotFound
|
6 |
+
from FileStream.utils.bot_utils import gen_linkx, verify_user
|
7 |
+
from FileStream.config import Telegram
|
8 |
+
from FileStream.utils.database import Database
|
9 |
+
from FileStream.utils.translation import LANG, BUTTON
|
10 |
+
from pyrogram import filters, Client
|
11 |
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
|
12 |
+
from pyrogram.enums.parse_mode import ParseMode
|
13 |
+
|
14 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
15 |
+
|
16 |
+
@FileStream.on_message(filters.command('start') & filters.private)
|
17 |
+
async def start(bot: Client, message: Message):
|
18 |
+
if not await verify_user(bot, message):
|
19 |
+
return
|
20 |
+
usr_cmd = message.text.split("_")[-1]
|
21 |
+
|
22 |
+
if usr_cmd == "/start":
|
23 |
+
await message.reply_text(
|
24 |
+
text=LANG.START_TEXT.format(message.from_user.mention, FileStream.username),
|
25 |
+
parse_mode=ParseMode.HTML,
|
26 |
+
disable_web_page_preview=True,
|
27 |
+
reply_markup=BUTTON.START_BUTTONS
|
28 |
+
)
|
29 |
+
else:
|
30 |
+
if "stream_" in message.text:
|
31 |
+
try:
|
32 |
+
file_check = await db.get_file(usr_cmd)
|
33 |
+
file_id = str(file_check['_id'])
|
34 |
+
if file_id == usr_cmd:
|
35 |
+
reply_markup, stream_text = await gen_linkx(m=message, _id=file_id,
|
36 |
+
name=[FileStream.username, FileStream.fname])
|
37 |
+
await message.reply_text(
|
38 |
+
text=stream_text,
|
39 |
+
parse_mode=ParseMode.HTML,
|
40 |
+
disable_web_page_preview=True,
|
41 |
+
reply_markup=reply_markup,
|
42 |
+
quote=True
|
43 |
+
)
|
44 |
+
|
45 |
+
except FIleNotFound as e:
|
46 |
+
await message.reply_text("File Not Found")
|
47 |
+
except Exception as e:
|
48 |
+
await message.reply_text("Something Went Wrong")
|
49 |
+
logging.error(e)
|
50 |
+
|
51 |
+
elif "file_" in message.text:
|
52 |
+
try:
|
53 |
+
file_check = await db.get_file(usr_cmd)
|
54 |
+
db_id = str(file_check['_id'])
|
55 |
+
file_id = file_check['file_id']
|
56 |
+
file_name = file_check['file_name']
|
57 |
+
if db_id == usr_cmd:
|
58 |
+
await message.reply_cached_media(file_id=file_id, caption=f'**{file_name}**')
|
59 |
+
|
60 |
+
except FIleNotFound as e:
|
61 |
+
await message.reply_text("**File Not Found**")
|
62 |
+
except Exception as e:
|
63 |
+
await message.reply_text("Something Went Wrong")
|
64 |
+
logging.error(e)
|
65 |
+
|
66 |
+
else:
|
67 |
+
await message.reply_text(f"**Invalid Command**")
|
68 |
+
|
69 |
+
@FileStream.on_message(filters.private & filters.command(["about"]))
|
70 |
+
async def start(bot, message):
|
71 |
+
if not await verify_user(bot, message):
|
72 |
+
return
|
73 |
+
await message.reply_text(
|
74 |
+
text=LANG.ABOUT_TEXT.format(FileStream.fname, __version__),
|
75 |
+
disable_web_page_preview=True,
|
76 |
+
reply_markup=BUTTON.ABOUT_BUTTONS
|
77 |
+
)
|
78 |
+
|
79 |
+
|
80 |
+
@FileStream.on_message((filters.command('help')) & filters.private)
|
81 |
+
async def help_handler(bot, message):
|
82 |
+
if not await verify_user(bot, message):
|
83 |
+
return
|
84 |
+
await message.reply_text(
|
85 |
+
text=LANG.HELP_TEXT.format(Telegram.OWNER_ID),
|
86 |
+
parse_mode=ParseMode.HTML,
|
87 |
+
disable_web_page_preview=True,
|
88 |
+
reply_markup=BUTTON.HELP_BUTTONS
|
89 |
+
)
|
90 |
+
|
91 |
+
# ---------------------------------------------------------------------------------------------------
|
92 |
+
|
93 |
+
@FileStream.on_message(filters.command('files') & filters.private)
|
94 |
+
async def my_files(bot: Client, message: Message):
|
95 |
+
if not await verify_user(bot, message):
|
96 |
+
return
|
97 |
+
user_files, total_files = await db.find_files(message.from_user.id, [1, 10])
|
98 |
+
|
99 |
+
file_list = []
|
100 |
+
async for x in user_files:
|
101 |
+
file_list.append([InlineKeyboardButton(x["file_name"], callback_data=f"myfile_{x['_id']}_{1}")])
|
102 |
+
if total_files > 10:
|
103 |
+
file_list.append(
|
104 |
+
[
|
105 |
+
InlineKeyboardButton("◄", callback_data="N/A"),
|
106 |
+
InlineKeyboardButton(f"1/{math.ceil(total_files / 10)}", callback_data="N/A"),
|
107 |
+
InlineKeyboardButton("►", callback_data="userfiles_2")
|
108 |
+
],
|
109 |
+
)
|
110 |
+
if not file_list:
|
111 |
+
file_list.append(
|
112 |
+
[InlineKeyboardButton("ᴇᴍᴘᴛʏ", callback_data="N/A")],
|
113 |
+
)
|
114 |
+
file_list.append([InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")])
|
115 |
+
await message.reply_photo(photo=Telegram.IMAGE_FILEID,
|
116 |
+
caption="Total files: {}".format(total_files),
|
117 |
+
reply_markup=InlineKeyboardMarkup(file_list))
|
118 |
+
|
119 |
+
|
FileStream/bot/plugins/stream.py
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import asyncio
|
3 |
+
from FileStream.bot import FileStream, multi_clients
|
4 |
+
from FileStream.utils.bot_utils import is_user_banned, is_user_exist, is_user_joined, gen_link, is_channel_banned, is_channel_exist, is_user_authorized
|
5 |
+
from FileStream.utils.database import Database
|
6 |
+
from FileStream.utils.file_properties import get_file_ids, get_file_info
|
7 |
+
from FileStream.config import Telegram
|
8 |
+
from pyrogram import filters, Client
|
9 |
+
from pyrogram.errors import FloodWait
|
10 |
+
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
11 |
+
from pyrogram.enums.parse_mode import ParseMode
|
12 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
13 |
+
|
14 |
+
@FileStream.on_message(
|
15 |
+
filters.private
|
16 |
+
& (
|
17 |
+
filters.document
|
18 |
+
| filters.video
|
19 |
+
| filters.video_note
|
20 |
+
| filters.audio
|
21 |
+
| filters.voice
|
22 |
+
| filters.animation
|
23 |
+
| filters.photo
|
24 |
+
),
|
25 |
+
group=4,
|
26 |
+
)
|
27 |
+
async def private_receive_handler(bot: Client, message: Message):
|
28 |
+
if not await is_user_authorized(message):
|
29 |
+
return
|
30 |
+
if await is_user_banned(message):
|
31 |
+
return
|
32 |
+
|
33 |
+
await is_user_exist(bot, message)
|
34 |
+
if Telegram.FORCE_UPDATES_CHANNEL:
|
35 |
+
if not await is_user_joined(bot, message):
|
36 |
+
return
|
37 |
+
try:
|
38 |
+
inserted_id = await db.add_file(get_file_info(message))
|
39 |
+
await get_file_ids(False, inserted_id, multi_clients, message)
|
40 |
+
reply_markup, stream_text = await gen_link(_id=inserted_id)
|
41 |
+
await message.reply_text(
|
42 |
+
text=stream_text,
|
43 |
+
parse_mode=ParseMode.HTML,
|
44 |
+
disable_web_page_preview=True,
|
45 |
+
reply_markup=reply_markup,
|
46 |
+
quote=True
|
47 |
+
)
|
48 |
+
except FloodWait as e:
|
49 |
+
print(f"Sleeping for {str(e.value)}s")
|
50 |
+
await asyncio.sleep(e.value)
|
51 |
+
await bot.send_message(chat_id=Telegram.LOG_CHANNEL,
|
52 |
+
text=f"Gᴏᴛ FʟᴏᴏᴅWᴀɪᴛ ᴏғ {str(e.value)}s from [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n\n**𝚄𝚜𝚎𝚛 𝙸𝙳 :** `{str(message.from_user.id)}`",
|
53 |
+
disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN)
|
54 |
+
|
55 |
+
|
56 |
+
@FileStream.on_message(
|
57 |
+
filters.channel
|
58 |
+
& ~filters.forwarded
|
59 |
+
& ~filters.media_group
|
60 |
+
& (
|
61 |
+
filters.document
|
62 |
+
| filters.video
|
63 |
+
| filters.video_note
|
64 |
+
| filters.audio
|
65 |
+
| filters.voice
|
66 |
+
| filters.photo
|
67 |
+
)
|
68 |
+
)
|
69 |
+
async def channel_receive_handler(bot: Client, message: Message):
|
70 |
+
if await is_channel_banned(bot, message):
|
71 |
+
return
|
72 |
+
await is_channel_exist(bot, message)
|
73 |
+
|
74 |
+
try:
|
75 |
+
inserted_id = await db.add_file(get_file_info(message))
|
76 |
+
await get_file_ids(False, inserted_id, multi_clients, message)
|
77 |
+
reply_markup, stream_link = await gen_link(_id=inserted_id)
|
78 |
+
await bot.edit_message_reply_markup(
|
79 |
+
chat_id=message.chat.id,
|
80 |
+
message_id=message.id,
|
81 |
+
reply_markup=InlineKeyboardMarkup(
|
82 |
+
[[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥",
|
83 |
+
url=f"https://t.me/{FileStream.username}?start=stream_{str(inserted_id)}")]])
|
84 |
+
)
|
85 |
+
|
86 |
+
except FloodWait as w:
|
87 |
+
print(f"Sleeping for {str(w.x)}s")
|
88 |
+
await asyncio.sleep(w.x)
|
89 |
+
await bot.send_message(chat_id=Telegram.LOG_CHANNEL,
|
90 |
+
text=f"ɢᴏᴛ ғʟᴏᴏᴅᴡᴀɪᴛ ᴏғ {str(w.x)}s FROM {message.chat.title}\n\n**CHANNEL ID:** `{str(message.chat.id)}`",
|
91 |
+
disable_web_page_preview=True)
|
92 |
+
except Exception as e:
|
93 |
+
await bot.send_message(chat_id=Telegram.LOG_CHANNEL, text=f"**#EʀʀᴏʀTʀᴀᴄᴋᴇʙᴀᴄᴋ:** `{e}`",
|
94 |
+
disable_web_page_preview=True)
|
95 |
+
print(f"Cᴀɴ'ᴛ Eᴅɪᴛ Bʀᴏᴀᴅᴄᴀsᴛ Mᴇssᴀɢᴇ!\nEʀʀᴏʀ: **Gɪᴠᴇ ᴍᴇ ᴇᴅɪᴛ ᴘᴇʀᴍɪssɪᴏɴ ɪɴ ᴜᴘᴅᴀᴛᴇs ᴀɴᴅ ʙɪɴ Cʜᴀɴɴᴇʟ!{e}**")
|
FileStream/config.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from os import environ as env
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
|
4 |
+
load_dotenv()
|
5 |
+
|
6 |
+
class Telegram:
|
7 |
+
API_ID = int('20295350')
|
8 |
+
API_HASH = str('805a0a86f3b382d904617d0a2fd4fb6f')
|
9 |
+
BOT_TOKEN = str('6951577014:AAGPEWqTj42ndRcmDDibDqfvBymcrJkg7Ss')
|
10 |
+
OWNER_ID = int('1220356966')
|
11 |
+
WORKERS = int(env.get("WORKERS", "6")) # 6 workers = 6 commands at once
|
12 |
+
DATABASE_URL = str('mongodb+srv://npanchayan:jph2G5RdOuII3gYM@cluster0.vycugvp.mongodb.net/?retryWrites=true&w=majority')
|
13 |
+
UPDATES_CHANNEL = str(env.get('UPDATES_CHANNEL', "Telegram"))
|
14 |
+
SESSION_NAME = str(env.get('SESSION_NAME', 'FileStream'))
|
15 |
+
FORCE_UPDATES_CHANNEL = env.get('FORCE_UPDATES_CHANNEL', False)
|
16 |
+
FORCE_UPDATES_CHANNEL = True if str(FORCE_UPDATES_CHANNEL).lower() == "true" else False
|
17 |
+
SLEEP_THRESHOLD = int(env.get("SLEEP_THRESHOLD", "60"))
|
18 |
+
IMAGE_FILEID = env.get('IMAGE_FILEID', "https://telegra.ph/file/5bb9935be0229adf98b73.jpg")
|
19 |
+
MULTI_CLIENT = False
|
20 |
+
LOG_CHANNEL = int('-1001642343016')
|
21 |
+
MODE = env.get("MODE", "primary")
|
22 |
+
SECONDARY = True if MODE.lower() == "secondary" else False
|
23 |
+
AUTH_USERS = list(set(int(x) for x in str(env.get("AUTH_USERS", "")).split()))
|
24 |
+
|
25 |
+
class Server:
|
26 |
+
PORT = int(8080)
|
27 |
+
BIND_ADDRESS = str(env.get("BIND_ADDRESS", "0.0.0.0"))
|
28 |
+
PING_INTERVAL = int(env.get("PING_INTERVAL", "1200")) # 20 minutes
|
29 |
+
HAS_SSL = str(env.get("HAS_SSL", "0").lower()) in ("1", "true", "t", "yes", "y")
|
30 |
+
NO_PORT = str(env.get("NO_PORT", "1").lower()) in ("1", "true", "t", "yes", "y")
|
31 |
+
FQDN = str('')
|
32 |
+
URL = "http{}://{}{}/".format(
|
33 |
+
"s" if HAS_SSL else "", FQDN, "" if NO_PORT else ":" + str(PORT)
|
34 |
+
)
|
35 |
+
KEEP_ALIVE = str(env.get("KEEP_ALIVE", "0").lower()) in ("1", "true", "t", "yes", "y")
|
36 |
+
|
37 |
+
|
FileStream/server/__init__.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from aiohttp import web
|
2 |
+
from .stream_routes import routes
|
3 |
+
|
4 |
+
def web_server():
|
5 |
+
web_app = web.Application(client_max_size=30000000)
|
6 |
+
web_app.add_routes(routes)
|
7 |
+
return web_app
|
FileStream/server/exceptions.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class InvalidHash(Exception):
|
2 |
+
message = "Invalid hash"
|
3 |
+
|
4 |
+
class FIleNotFound(Exception):
|
5 |
+
message = "File not found"
|
FileStream/server/stream_routes.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import math
|
3 |
+
import logging
|
4 |
+
import mimetypes
|
5 |
+
import traceback
|
6 |
+
from aiohttp import web
|
7 |
+
from aiohttp.http_exceptions import BadStatusLine
|
8 |
+
from FileStream.bot import multi_clients, work_loads, FileStream
|
9 |
+
from FileStream.config import Telegram, Server
|
10 |
+
from FileStream.server.exceptions import FIleNotFound, InvalidHash
|
11 |
+
from FileStream import utils, StartTime, __version__
|
12 |
+
from FileStream.utils.render_template import render_page
|
13 |
+
|
14 |
+
routes = web.RouteTableDef()
|
15 |
+
@routes.get("/", allow_head=True)
|
16 |
+
async def root_route_handler(_):
|
17 |
+
return web.json_response(
|
18 |
+
{
|
19 |
+
"message": "HELLO WORLD!",
|
20 |
+
"uptime": utils.get_readable_time(time.time() - StartTime),
|
21 |
+
"telegram_bot": "@" + FileStream.username,
|
22 |
+
"connected_bots": len(multi_clients),
|
23 |
+
"version": __version__,
|
24 |
+
}
|
25 |
+
)
|
26 |
+
|
27 |
+
@routes.get("/status", allow_head=True)
|
28 |
+
async def root_route_handler(_):
|
29 |
+
return web.json_response(
|
30 |
+
{
|
31 |
+
"server_status": "running",
|
32 |
+
"uptime": utils.get_readable_time(time.time() - StartTime),
|
33 |
+
"telegram_bot": "@" + FileStream.username,
|
34 |
+
"connected_bots": len(multi_clients),
|
35 |
+
"loads": dict(
|
36 |
+
("bot" + str(c + 1), l)
|
37 |
+
for c, (_, l) in enumerate(
|
38 |
+
sorted(work_loads.items(), key=lambda x: x[1], reverse=True)
|
39 |
+
)
|
40 |
+
),
|
41 |
+
"version": __version__,
|
42 |
+
}
|
43 |
+
)
|
44 |
+
|
45 |
+
@routes.get("/watch/{path}", allow_head=True)
|
46 |
+
async def stream_handler(request: web.Request):
|
47 |
+
try:
|
48 |
+
path = request.match_info["path"]
|
49 |
+
return web.Response(text=await render_page(path), content_type='text/html')
|
50 |
+
except InvalidHash as e:
|
51 |
+
raise web.HTTPForbidden(text=e.message)
|
52 |
+
except FIleNotFound as e:
|
53 |
+
raise web.HTTPNotFound(text=e.message)
|
54 |
+
except (AttributeError, BadStatusLine, ConnectionResetError):
|
55 |
+
pass
|
56 |
+
|
57 |
+
|
58 |
+
@routes.get("/dl/{path}", allow_head=True)
|
59 |
+
async def stream_handler(request: web.Request):
|
60 |
+
try:
|
61 |
+
path = request.match_info["path"]
|
62 |
+
return await media_streamer(request, path)
|
63 |
+
except InvalidHash as e:
|
64 |
+
raise web.HTTPForbidden(text=e.message)
|
65 |
+
except FIleNotFound as e:
|
66 |
+
raise web.HTTPNotFound(text=e.message)
|
67 |
+
except (AttributeError, BadStatusLine, ConnectionResetError):
|
68 |
+
pass
|
69 |
+
except Exception as e:
|
70 |
+
traceback.print_exc()
|
71 |
+
logging.critical(e.with_traceback(None))
|
72 |
+
logging.debug(traceback.format_exc())
|
73 |
+
raise web.HTTPInternalServerError(text=str(e))
|
74 |
+
|
75 |
+
class_cache = {}
|
76 |
+
|
77 |
+
async def media_streamer(request: web.Request, db_id: str):
|
78 |
+
range_header = request.headers.get("Range", 0)
|
79 |
+
|
80 |
+
index = min(work_loads, key=work_loads.get)
|
81 |
+
faster_client = multi_clients[index]
|
82 |
+
|
83 |
+
if Telegram.MULTI_CLIENT:
|
84 |
+
logging.info(f"Client {index} is now serving {request.headers.get('X-FORWARDED-FOR',request.remote)}")
|
85 |
+
|
86 |
+
if faster_client in class_cache:
|
87 |
+
tg_connect = class_cache[faster_client]
|
88 |
+
logging.debug(f"Using cached ByteStreamer object for client {index}")
|
89 |
+
else:
|
90 |
+
logging.debug(f"Creating new ByteStreamer object for client {index}")
|
91 |
+
tg_connect = utils.ByteStreamer(faster_client)
|
92 |
+
class_cache[faster_client] = tg_connect
|
93 |
+
logging.debug("before calling get_file_properties")
|
94 |
+
file_id = await tg_connect.get_file_properties(db_id, multi_clients)
|
95 |
+
logging.debug("after calling get_file_properties")
|
96 |
+
|
97 |
+
file_size = file_id.file_size
|
98 |
+
|
99 |
+
if range_header:
|
100 |
+
from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
|
101 |
+
from_bytes = int(from_bytes)
|
102 |
+
until_bytes = int(until_bytes) if until_bytes else file_size - 1
|
103 |
+
else:
|
104 |
+
from_bytes = request.http_range.start or 0
|
105 |
+
until_bytes = (request.http_range.stop or file_size) - 1
|
106 |
+
|
107 |
+
if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes):
|
108 |
+
return web.Response(
|
109 |
+
status=416,
|
110 |
+
body="416: Range not satisfiable",
|
111 |
+
headers={"Content-Range": f"bytes */{file_size}"},
|
112 |
+
)
|
113 |
+
|
114 |
+
chunk_size = 1024 * 1024
|
115 |
+
until_bytes = min(until_bytes, file_size - 1)
|
116 |
+
|
117 |
+
offset = from_bytes - (from_bytes % chunk_size)
|
118 |
+
first_part_cut = from_bytes - offset
|
119 |
+
last_part_cut = until_bytes % chunk_size + 1
|
120 |
+
|
121 |
+
req_length = until_bytes - from_bytes + 1
|
122 |
+
part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size)
|
123 |
+
body = tg_connect.yield_file(
|
124 |
+
file_id, index, offset, first_part_cut, last_part_cut, part_count, chunk_size
|
125 |
+
)
|
126 |
+
|
127 |
+
mime_type = file_id.mime_type
|
128 |
+
file_name = utils.get_name(file_id)
|
129 |
+
disposition = "attachment"
|
130 |
+
|
131 |
+
if not mime_type:
|
132 |
+
mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
|
133 |
+
|
134 |
+
# if "video/" in mime_type or "audio/" in mime_type:
|
135 |
+
# disposition = "inline"
|
136 |
+
|
137 |
+
return web.Response(
|
138 |
+
status=206 if range_header else 200,
|
139 |
+
body=body,
|
140 |
+
headers={
|
141 |
+
"Content-Type": f"{mime_type}",
|
142 |
+
"Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
|
143 |
+
"Content-Length": str(req_length),
|
144 |
+
"Content-Disposition": f'{disposition}; filename="{file_name}"',
|
145 |
+
"Accept-Ranges": "bytes",
|
146 |
+
},
|
147 |
+
)
|
FileStream/template/dl.html
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta property="og:image" content="https://www.flaticon.com/premium-icon/icons/svg/2626/2626281.svg" itemprop="thumbnailUrl">
|
6 |
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
8 |
+
<title>%s</title>
|
9 |
+
<link rel="stylesheet" type='text/css' href="https://drive.google.com/uc?export=view&id=1pVLG4gZy7jdow3sO-wFS06aP_A9QX0O6">
|
10 |
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
|
11 |
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Delius">
|
12 |
+
<!-- <link rel="stylesheet" href="./style.css"> -->
|
13 |
+
|
14 |
+
</head>
|
15 |
+
|
16 |
+
<body class='cyber'>
|
17 |
+
<header>
|
18 |
+
<div class="toogle"></div>
|
19 |
+
<div id="file-name" class="cyber">
|
20 |
+
%s
|
21 |
+
</div>
|
22 |
+
</header>
|
23 |
+
|
24 |
+
<div class="container">
|
25 |
+
<a href=%s>
|
26 |
+
<button class="cybr-btn">
|
27 |
+
Download
|
28 |
+
<span aria-hidden>_</span>
|
29 |
+
<span aria-hidden class="cybr-btn__glitch">_404 Error</span>
|
30 |
+
<span aria-hidden class="cybr-btn__tag">_%s</span>
|
31 |
+
</button>
|
32 |
+
</a>
|
33 |
+
</div>
|
34 |
+
|
35 |
+
<footer>
|
36 |
+
<span id="fork-text">Fork me on</span>
|
37 |
+
<span>
|
38 |
+
<a href="https://github.com/DeekshithSH/FileStreamBot" id='github-logo'>
|
39 |
+
<svg id='octo' style="width: 1.2rem; padding-left: 5px; fill: var(--footer-icon-color)" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
40 |
+
</a>
|
41 |
+
</span>
|
42 |
+
</footer>
|
43 |
+
|
44 |
+
<script>
|
45 |
+
const body = document.querySelector('body');
|
46 |
+
const title = document.querySelector('#file-name');
|
47 |
+
const footer = document.querySelector('footer');
|
48 |
+
const toogle = document.querySelector('.toogle');
|
49 |
+
toogle.onclick = () => {
|
50 |
+
body.classList.toggle('dark')
|
51 |
+
footer.classList.toggle('dark')
|
52 |
+
title.classList.toggle('dark')
|
53 |
+
}
|
54 |
+
</script>
|
55 |
+
</body>
|
56 |
+
</html>
|
FileStream/template/stream.html
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<title>streamHeading</title>
|
5 |
+
|
6 |
+
<meta charset="UTF-8">
|
7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
8 |
+
<meta http-equiv="X-Frame-Options" content="deny">
|
9 |
+
|
10 |
+
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
|
11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
12 |
+
|
13 |
+
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
|
14 |
+
|
15 |
+
<style>
|
16 |
+
html, body {
|
17 |
+
margin: 0;
|
18 |
+
height: 100%;
|
19 |
+
}
|
20 |
+
|
21 |
+
#stream-media {
|
22 |
+
height: 100%;
|
23 |
+
width: 100%;
|
24 |
+
}
|
25 |
+
|
26 |
+
#error-message {
|
27 |
+
color: red;
|
28 |
+
font-size: 24px;
|
29 |
+
text-align: center;
|
30 |
+
margin-top: 20px;
|
31 |
+
}
|
32 |
+
|
33 |
+
.plyr__video-wrapper .plyr-download-button{
|
34 |
+
position: absolute;
|
35 |
+
top: 10px;
|
36 |
+
left: 10px;
|
37 |
+
width: 30px;
|
38 |
+
height: 30px;
|
39 |
+
background-color: rgba(0, 0, 0, 0.7);
|
40 |
+
border-radius: 50%;
|
41 |
+
text-align: center;
|
42 |
+
line-height: 30px;
|
43 |
+
color: white;
|
44 |
+
z-index: 10;
|
45 |
+
}
|
46 |
+
|
47 |
+
.plyr__volume {
|
48 |
+
max-width: initial;
|
49 |
+
min-width: initial;
|
50 |
+
width: auto;
|
51 |
+
position: relative;
|
52 |
+
}
|
53 |
+
|
54 |
+
|
55 |
+
.plyr__video-wrapper .plyr-share-button{
|
56 |
+
position: absolute;
|
57 |
+
top: 50px;
|
58 |
+
left: 10px;
|
59 |
+
width: 30px;
|
60 |
+
height: 30px;
|
61 |
+
background-color: rgba(0, 0, 0, 0.7);
|
62 |
+
border-radius: 50%;
|
63 |
+
text-align: center;
|
64 |
+
line-height: 30px;
|
65 |
+
color: white;
|
66 |
+
z-index: 10;
|
67 |
+
}
|
68 |
+
|
69 |
+
.plyr__video-wrapper .plyr-download-button:hover,
|
70 |
+
.plyr__video-wrapper .plyr-share-button:hover{
|
71 |
+
background-color: rgba(255, 255, 255, 0.7);
|
72 |
+
color: black;
|
73 |
+
}
|
74 |
+
|
75 |
+
.plyr__video-wrapper .plyr-download-button:before {
|
76 |
+
font-family: "Font Awesome 5 Free";
|
77 |
+
content: "\f019";
|
78 |
+
font-weight: bold;
|
79 |
+
}
|
80 |
+
|
81 |
+
.plyr__video-wrapper .plyr-share-button:before {
|
82 |
+
font-family: "Font Awesome 5 Free";
|
83 |
+
content: "\f064";
|
84 |
+
font-weight: bold;
|
85 |
+
}
|
86 |
+
|
87 |
+
.plyr, .plyr__video-wrapper, .plyr__video-embed iframe {
|
88 |
+
height: 100%;
|
89 |
+
}
|
90 |
+
|
91 |
+
</style>
|
92 |
+
</head>
|
93 |
+
|
94 |
+
<body>
|
95 |
+
<video id="stream-media" controls preload="auto">
|
96 |
+
<source src="" type="">
|
97 |
+
<p class="vjs-no-js">
|
98 |
+
To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
|
99 |
+
</p>
|
100 |
+
</video>
|
101 |
+
|
102 |
+
<div id="error-message"></div>
|
103 |
+
|
104 |
+
<script>
|
105 |
+
var player = new Plyr('#stream-media', {
|
106 |
+
controls:['play-large', 'rewind', 'play', 'fast-forward', 'progress', 'current-time', 'mute', 'settings', 'pip', 'fullscreen'],
|
107 |
+
settings:['speed','loop'],
|
108 |
+
speed:{selected:1,options:[0.25,0.5,0.75,1,1.25,1.5,1.75,2]},
|
109 |
+
seek: 10,
|
110 |
+
keyboard: { focused: true, global: true },
|
111 |
+
});
|
112 |
+
|
113 |
+
var mediaLink = "streamMediaLink";
|
114 |
+
|
115 |
+
if (mediaLink) {
|
116 |
+
document.querySelector('#stream-media source').setAttribute('src', mediaLink);
|
117 |
+
player.restart();
|
118 |
+
|
119 |
+
var downloadButton = document.createElement('div');
|
120 |
+
downloadButton.className = 'plyr-download-button';
|
121 |
+
|
122 |
+
downloadButton.onclick = function() {
|
123 |
+
event.stopPropagation();
|
124 |
+
var link = document.createElement('a');
|
125 |
+
link.href = mediaLink;
|
126 |
+
document.body.appendChild(link);
|
127 |
+
link.click();
|
128 |
+
document.body.removeChild(link);
|
129 |
+
};
|
130 |
+
|
131 |
+
player.elements.container.querySelector('.plyr__video-wrapper').appendChild(downloadButton);
|
132 |
+
|
133 |
+
var shareButton = document.createElement('div');
|
134 |
+
shareButton.className = 'plyr-share-button';
|
135 |
+
|
136 |
+
shareButton.onclick = function() {
|
137 |
+
event.stopPropagation();
|
138 |
+
if (navigator.share) {
|
139 |
+
navigator.share({
|
140 |
+
title: "Play",
|
141 |
+
url: window.location.href
|
142 |
+
});
|
143 |
+
}
|
144 |
+
};
|
145 |
+
|
146 |
+
player.elements.container.querySelector('.plyr__video-wrapper').appendChild(shareButton);
|
147 |
+
|
148 |
+
} else {
|
149 |
+
document.getElementById('error-message').textContent = 'Error: Media URL not provided';
|
150 |
+
}
|
151 |
+
</script>
|
152 |
+
|
153 |
+
</body>
|
154 |
+
</html>
|
FileStream/utils/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .keepalive import ping_server
|
2 |
+
from .time_format import get_readable_time
|
3 |
+
from .file_properties import get_name, get_file_ids
|
4 |
+
from .custom_dl import ByteStreamer
|
FileStream/utils/bot_utils.py
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pyrogram.errors import UserNotParticipant
|
2 |
+
from pyrogram.enums.parse_mode import ParseMode
|
3 |
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
|
4 |
+
from FileStream.utils.translation import LANG
|
5 |
+
from FileStream.utils.database import Database
|
6 |
+
from FileStream.utils.human_readable import humanbytes
|
7 |
+
from FileStream.config import Telegram, Server
|
8 |
+
from FileStream.bot import FileStream
|
9 |
+
|
10 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
11 |
+
|
12 |
+
async def is_user_joined(bot, message: Message):
|
13 |
+
try:
|
14 |
+
user = await bot.get_chat_member(Telegram.UPDATES_CHANNEL, message.chat.id)
|
15 |
+
if user.status == "BANNED":
|
16 |
+
await message.reply_text(
|
17 |
+
text=LANG.BAN_TEXT.format(Telegram.OWNER_ID),
|
18 |
+
parse_mode=ParseMode.MARKDOWN,
|
19 |
+
disable_web_page_preview=True
|
20 |
+
)
|
21 |
+
return False
|
22 |
+
except UserNotParticipant:
|
23 |
+
await message.reply_text(
|
24 |
+
text = "<i>Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴍᴇ 🔐</i>",
|
25 |
+
reply_markup=InlineKeyboardMarkup(
|
26 |
+
[[
|
27 |
+
InlineKeyboardButton("Jᴏɪɴ ɴᴏᴡ 🔓", url=f"https://t.me/{Telegram.UPDATES_CHANNEL}")
|
28 |
+
]]
|
29 |
+
),
|
30 |
+
parse_mode=ParseMode.HTML
|
31 |
+
)
|
32 |
+
return False
|
33 |
+
except Exception:
|
34 |
+
await message.reply_text(
|
35 |
+
text = f"<i>Sᴏᴍᴇᴛʜɪɴɢ ᴡʀᴏɴɢ ᴄᴏɴᴛᴀᴄᴛ ᴍʏ ᴅᴇᴠᴇʟᴏᴘᴇʀ</i> <b><a href='https://t.me/{Telegram.UPDATES_CHANNEL}'>[ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]</a></b>",
|
36 |
+
parse_mode=ParseMode.HTML,
|
37 |
+
disable_web_page_preview=True)
|
38 |
+
return False
|
39 |
+
return True
|
40 |
+
|
41 |
+
#---------------------[ PRIVATE GEN LINK + CALLBACK ]---------------------#
|
42 |
+
|
43 |
+
async def gen_link(_id):
|
44 |
+
file_info = await db.get_file(_id)
|
45 |
+
file_name = file_info['file_name']
|
46 |
+
file_size = humanbytes(file_info['file_size'])
|
47 |
+
mime_type = file_info['mime_type']
|
48 |
+
|
49 |
+
page_link = f"{Server.URL}watch/{_id}"
|
50 |
+
stream_link = f"{Server.URL}dl/{_id}"
|
51 |
+
file_link = f"https://t.me/{FileStream.username}?start=file_{_id}"
|
52 |
+
|
53 |
+
if "video" in mime_type:
|
54 |
+
stream_text = LANG.STREAM_TEXT.format(file_name, file_size, stream_link, page_link, file_link)
|
55 |
+
reply_markup = InlineKeyboardMarkup(
|
56 |
+
[
|
57 |
+
[InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
58 |
+
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", url=file_link), InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelpvt_{_id}")],
|
59 |
+
[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")]
|
60 |
+
]
|
61 |
+
)
|
62 |
+
else:
|
63 |
+
stream_text = LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, file_link)
|
64 |
+
reply_markup = InlineKeyboardMarkup(
|
65 |
+
[
|
66 |
+
[InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
67 |
+
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", url=file_link), InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelpvt_{_id}")],
|
68 |
+
[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")]
|
69 |
+
]
|
70 |
+
)
|
71 |
+
return reply_markup, stream_text
|
72 |
+
|
73 |
+
#---------------------[ GEN STREAM LINKS FOR CHANNEL ]---------------------#
|
74 |
+
|
75 |
+
async def gen_linkx(m:Message , _id, name: list):
|
76 |
+
file_info = await db.get_file(_id)
|
77 |
+
file_name = file_info['file_name']
|
78 |
+
mime_type = file_info['mime_type']
|
79 |
+
file_size = humanbytes(file_info['file_size'])
|
80 |
+
|
81 |
+
page_link = f"{Server.URL}watch/{_id}"
|
82 |
+
stream_link = f"{Server.URL}dl/{_id}"
|
83 |
+
file_link = f"https://t.me/{FileStream.username}?start=file_{_id}"
|
84 |
+
|
85 |
+
if "video" in mime_type:
|
86 |
+
stream_text= LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, page_link)
|
87 |
+
reply_markup = InlineKeyboardMarkup(
|
88 |
+
[
|
89 |
+
[InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)]
|
90 |
+
]
|
91 |
+
)
|
92 |
+
else:
|
93 |
+
stream_text= LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, file_link)
|
94 |
+
reply_markup = InlineKeyboardMarkup(
|
95 |
+
[
|
96 |
+
[InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)]
|
97 |
+
]
|
98 |
+
)
|
99 |
+
return reply_markup, stream_text
|
100 |
+
|
101 |
+
#---------------------[ USER BANNED ]---------------------#
|
102 |
+
|
103 |
+
async def is_user_banned(message):
|
104 |
+
if await db.is_user_banned(message.from_user.id):
|
105 |
+
await message.reply_text(
|
106 |
+
text=LANG.BAN_TEXT.format(Telegram.OWNER_ID),
|
107 |
+
parse_mode=ParseMode.MARKDOWN,
|
108 |
+
disable_web_page_preview=True
|
109 |
+
)
|
110 |
+
return True
|
111 |
+
return False
|
112 |
+
|
113 |
+
#---------------------[ CHANNEL BANNED ]---------------------#
|
114 |
+
|
115 |
+
async def is_channel_banned(bot, message):
|
116 |
+
if await db.is_user_banned(message.chat.id):
|
117 |
+
await bot.edit_message_reply_markup(
|
118 |
+
chat_id=message.chat.id,
|
119 |
+
message_id=message.id,
|
120 |
+
reply_markup=InlineKeyboardMarkup([[
|
121 |
+
InlineKeyboardButton(f"ᴄʜᴀɴɴᴇʟ ɪs ʙᴀɴɴᴇᴅ", callback_data="N/A")]])
|
122 |
+
)
|
123 |
+
return True
|
124 |
+
return False
|
125 |
+
|
126 |
+
#---------------------[ USER AUTH ]---------------------#
|
127 |
+
|
128 |
+
async def is_user_authorized(message):
|
129 |
+
if hasattr(Telegram, 'AUTH_USERS') and Telegram.AUTH_USERS:
|
130 |
+
user_id = message.from_user.id
|
131 |
+
|
132 |
+
if user_id == Telegram.OWNER_ID:
|
133 |
+
return True
|
134 |
+
|
135 |
+
if not (user_id in Telegram.AUTH_USERS):
|
136 |
+
await message.reply_text(
|
137 |
+
text="You are not authorized to use this bot.",
|
138 |
+
parse_mode=ParseMode.MARKDOWN,
|
139 |
+
disable_web_page_preview=True
|
140 |
+
)
|
141 |
+
return False
|
142 |
+
|
143 |
+
return True
|
144 |
+
|
145 |
+
#---------------------[ USER EXIST ]---------------------#
|
146 |
+
|
147 |
+
async def is_user_exist(bot, message):
|
148 |
+
if not bool(await db.get_user(message.from_user.id)):
|
149 |
+
await db.add_user(message.from_user.id)
|
150 |
+
await bot.send_message(
|
151 |
+
Telegram.LOG_CHANNEL,
|
152 |
+
f"**#NᴇᴡUsᴇʀ**\n**⬩ ᴜsᴇʀ ɴᴀᴍᴇ :** [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n**⬩ ᴜsᴇʀ ɪᴅ :** `{message.from_user.id}`"
|
153 |
+
)
|
154 |
+
|
155 |
+
async def is_channel_exist(bot, message):
|
156 |
+
if not bool(await db.get_user(message.chat.id)):
|
157 |
+
await db.add_user(message.chat.id)
|
158 |
+
members = await bot.get_chat_members_count(message.chat.id)
|
159 |
+
await bot.send_message(
|
160 |
+
Telegram.LOG_CHANNEL,
|
161 |
+
f"**#NᴇᴡCʜᴀɴɴᴇʟ** \n**⬩ ᴄʜᴀᴛ ɴᴀᴍᴇ :** `{message.chat.title}`\n**⬩ ᴄʜᴀᴛ ɪᴅ :** `{message.chat.id}`\n**⬩ ᴛᴏᴛᴀʟ ᴍᴇᴍʙᴇʀs :** `{members}`"
|
162 |
+
)
|
163 |
+
|
164 |
+
async def verify_user(bot, message):
|
165 |
+
if not await is_user_authorized(message):
|
166 |
+
return False
|
167 |
+
|
168 |
+
if await is_user_banned(message):
|
169 |
+
return False
|
170 |
+
|
171 |
+
await is_user_exist(bot, message)
|
172 |
+
|
173 |
+
if Telegram.FORCE_UPDATES_CHANNEL:
|
174 |
+
if not await is_user_joined(bot, message):
|
175 |
+
return False
|
176 |
+
|
177 |
+
return True
|
FileStream/utils/broadcast_helper.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import traceback
|
3 |
+
from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid
|
4 |
+
|
5 |
+
async def send_msg(user_id, message):
|
6 |
+
try:
|
7 |
+
await message.copy(chat_id=user_id)
|
8 |
+
return 200, None
|
9 |
+
except FloodWait as e:
|
10 |
+
await asyncio.sleep(e.value)
|
11 |
+
return send_msg(user_id, message)
|
12 |
+
except InputUserDeactivated:
|
13 |
+
return 400, f"{user_id} : deactivated\n"
|
14 |
+
except UserIsBlocked:
|
15 |
+
return 400, f"{user_id} : blocked the bot\n"
|
16 |
+
except PeerIdInvalid:
|
17 |
+
return 400, f"{user_id} : user id invalid\n"
|
18 |
+
except Exception as e:
|
19 |
+
return 500, f"{user_id} : {traceback.format_exc()}\n"
|
FileStream/utils/custom_dl.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import logging
|
3 |
+
from typing import Dict, Union
|
4 |
+
from FileStream.bot import work_loads
|
5 |
+
from pyrogram import Client, utils, raw
|
6 |
+
from .file_properties import get_file_ids
|
7 |
+
from pyrogram.session import Session, Auth
|
8 |
+
from pyrogram.errors import AuthBytesInvalid
|
9 |
+
from pyrogram.file_id import FileId, FileType, ThumbnailSource
|
10 |
+
from pyrogram.types import Message
|
11 |
+
|
12 |
+
class ByteStreamer:
|
13 |
+
def __init__(self, client: Client):
|
14 |
+
self.clean_timer = 30 * 60
|
15 |
+
self.client: Client = client
|
16 |
+
self.cached_file_ids: Dict[str, FileId] = {}
|
17 |
+
asyncio.create_task(self.clean_cache())
|
18 |
+
|
19 |
+
async def get_file_properties(self, db_id: str, multi_clients) -> FileId:
|
20 |
+
"""
|
21 |
+
Returns the properties of a media of a specific message in a FIleId class.
|
22 |
+
if the properties are cached, then it'll return the cached results.
|
23 |
+
or it'll generate the properties from the Message ID and cache them.
|
24 |
+
"""
|
25 |
+
if not db_id in self.cached_file_ids:
|
26 |
+
logging.debug("Before Calling generate_file_properties")
|
27 |
+
await self.generate_file_properties(db_id, multi_clients)
|
28 |
+
logging.debug(f"Cached file properties for file with ID {db_id}")
|
29 |
+
return self.cached_file_ids[db_id]
|
30 |
+
|
31 |
+
async def generate_file_properties(self, db_id: str, multi_clients) -> FileId:
|
32 |
+
"""
|
33 |
+
Generates the properties of a media file on a specific message.
|
34 |
+
returns ths properties in a FIleId class.
|
35 |
+
"""
|
36 |
+
logging.debug("Before calling get_file_ids")
|
37 |
+
file_id = await get_file_ids(self.client, db_id, multi_clients, Message)
|
38 |
+
logging.debug(f"Generated file ID and Unique ID for file with ID {db_id}")
|
39 |
+
self.cached_file_ids[db_id] = file_id
|
40 |
+
logging.debug(f"Cached media file with ID {db_id}")
|
41 |
+
return self.cached_file_ids[db_id]
|
42 |
+
|
43 |
+
async def generate_media_session(self, client: Client, file_id: FileId) -> Session:
|
44 |
+
"""
|
45 |
+
Generates the media session for the DC that contains the media file.
|
46 |
+
This is required for getting the bytes from Telegram servers.
|
47 |
+
"""
|
48 |
+
|
49 |
+
media_session = client.media_sessions.get(file_id.dc_id, None)
|
50 |
+
|
51 |
+
if media_session is None:
|
52 |
+
if file_id.dc_id != await client.storage.dc_id():
|
53 |
+
media_session = Session(
|
54 |
+
client,
|
55 |
+
file_id.dc_id,
|
56 |
+
await Auth(
|
57 |
+
client, file_id.dc_id, await client.storage.test_mode()
|
58 |
+
).create(),
|
59 |
+
await client.storage.test_mode(),
|
60 |
+
is_media=True,
|
61 |
+
)
|
62 |
+
await media_session.start()
|
63 |
+
|
64 |
+
for _ in range(6):
|
65 |
+
exported_auth = await client.invoke(
|
66 |
+
raw.functions.auth.ExportAuthorization(dc_id=file_id.dc_id)
|
67 |
+
)
|
68 |
+
|
69 |
+
try:
|
70 |
+
await media_session.invoke(
|
71 |
+
raw.functions.auth.ImportAuthorization(
|
72 |
+
id=exported_auth.id, bytes=exported_auth.bytes
|
73 |
+
)
|
74 |
+
)
|
75 |
+
break
|
76 |
+
except AuthBytesInvalid:
|
77 |
+
logging.debug(
|
78 |
+
f"Invalid authorization bytes for DC {file_id.dc_id}"
|
79 |
+
)
|
80 |
+
continue
|
81 |
+
else:
|
82 |
+
await media_session.stop()
|
83 |
+
raise AuthBytesInvalid
|
84 |
+
else:
|
85 |
+
media_session = Session(
|
86 |
+
client,
|
87 |
+
file_id.dc_id,
|
88 |
+
await client.storage.auth_key(),
|
89 |
+
await client.storage.test_mode(),
|
90 |
+
is_media=True,
|
91 |
+
)
|
92 |
+
await media_session.start()
|
93 |
+
logging.debug(f"Created media session for DC {file_id.dc_id}")
|
94 |
+
client.media_sessions[file_id.dc_id] = media_session
|
95 |
+
else:
|
96 |
+
logging.debug(f"Using cached media session for DC {file_id.dc_id}")
|
97 |
+
return media_session
|
98 |
+
|
99 |
+
|
100 |
+
@staticmethod
|
101 |
+
async def get_location(file_id: FileId) -> Union[raw.types.InputPhotoFileLocation,
|
102 |
+
raw.types.InputDocumentFileLocation,
|
103 |
+
raw.types.InputPeerPhotoFileLocation,]:
|
104 |
+
"""
|
105 |
+
Returns the file location for the media file.
|
106 |
+
"""
|
107 |
+
file_type = file_id.file_type
|
108 |
+
|
109 |
+
if file_type == FileType.CHAT_PHOTO:
|
110 |
+
if file_id.chat_id > 0:
|
111 |
+
peer = raw.types.InputPeerUser(
|
112 |
+
user_id=file_id.chat_id, access_hash=file_id.chat_access_hash
|
113 |
+
)
|
114 |
+
else:
|
115 |
+
if file_id.chat_access_hash == 0:
|
116 |
+
peer = raw.types.InputPeerChat(chat_id=-file_id.chat_id)
|
117 |
+
else:
|
118 |
+
peer = raw.types.InputPeerChannel(
|
119 |
+
channel_id=utils.get_channel_id(file_id.chat_id),
|
120 |
+
access_hash=file_id.chat_access_hash,
|
121 |
+
)
|
122 |
+
|
123 |
+
location = raw.types.InputPeerPhotoFileLocation(
|
124 |
+
peer=peer,
|
125 |
+
volume_id=file_id.volume_id,
|
126 |
+
local_id=file_id.local_id,
|
127 |
+
big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG,
|
128 |
+
)
|
129 |
+
elif file_type == FileType.PHOTO:
|
130 |
+
location = raw.types.InputPhotoFileLocation(
|
131 |
+
id=file_id.media_id,
|
132 |
+
access_hash=file_id.access_hash,
|
133 |
+
file_reference=file_id.file_reference,
|
134 |
+
thumb_size=file_id.thumbnail_size,
|
135 |
+
)
|
136 |
+
else:
|
137 |
+
location = raw.types.InputDocumentFileLocation(
|
138 |
+
id=file_id.media_id,
|
139 |
+
access_hash=file_id.access_hash,
|
140 |
+
file_reference=file_id.file_reference,
|
141 |
+
thumb_size=file_id.thumbnail_size,
|
142 |
+
)
|
143 |
+
return location
|
144 |
+
|
145 |
+
async def yield_file(
|
146 |
+
self,
|
147 |
+
file_id: FileId,
|
148 |
+
index: int,
|
149 |
+
offset: int,
|
150 |
+
first_part_cut: int,
|
151 |
+
last_part_cut: int,
|
152 |
+
part_count: int,
|
153 |
+
chunk_size: int,
|
154 |
+
) -> Union[str, None]:
|
155 |
+
"""
|
156 |
+
Custom generator that yields the bytes of the media file.
|
157 |
+
Modded from <https://github.com/eyaadh/megadlbot_oss/blob/master/mega/telegram/utils/custom_download.py#L20>
|
158 |
+
Thanks to Eyaadh <https://github.com/eyaadh>
|
159 |
+
"""
|
160 |
+
client = self.client
|
161 |
+
work_loads[index] += 1
|
162 |
+
logging.debug(f"Starting to yielding file with client {index}.")
|
163 |
+
media_session = await self.generate_media_session(client, file_id)
|
164 |
+
|
165 |
+
current_part = 1
|
166 |
+
|
167 |
+
location = await self.get_location(file_id)
|
168 |
+
|
169 |
+
try:
|
170 |
+
r = await media_session.invoke(
|
171 |
+
raw.functions.upload.GetFile(
|
172 |
+
location=location, offset=offset, limit=chunk_size
|
173 |
+
),
|
174 |
+
)
|
175 |
+
if isinstance(r, raw.types.upload.File):
|
176 |
+
while True:
|
177 |
+
chunk = r.bytes
|
178 |
+
if not chunk:
|
179 |
+
break
|
180 |
+
elif part_count == 1:
|
181 |
+
yield chunk[first_part_cut:last_part_cut]
|
182 |
+
elif current_part == 1:
|
183 |
+
yield chunk[first_part_cut:]
|
184 |
+
elif current_part == part_count:
|
185 |
+
yield chunk[:last_part_cut]
|
186 |
+
else:
|
187 |
+
yield chunk
|
188 |
+
|
189 |
+
current_part += 1
|
190 |
+
offset += chunk_size
|
191 |
+
|
192 |
+
if current_part > part_count:
|
193 |
+
break
|
194 |
+
|
195 |
+
r = await media_session.invoke(
|
196 |
+
raw.functions.upload.GetFile(
|
197 |
+
location=location, offset=offset, limit=chunk_size
|
198 |
+
),
|
199 |
+
)
|
200 |
+
except (TimeoutError, AttributeError):
|
201 |
+
pass
|
202 |
+
finally:
|
203 |
+
logging.debug(f"Finished yielding file with {current_part} parts.")
|
204 |
+
work_loads[index] -= 1
|
205 |
+
|
206 |
+
|
207 |
+
async def clean_cache(self) -> None:
|
208 |
+
"""
|
209 |
+
function to clean the cache to reduce memory usage
|
210 |
+
"""
|
211 |
+
while True:
|
212 |
+
await asyncio.sleep(self.clean_timer)
|
213 |
+
self.cached_file_ids.clear()
|
214 |
+
logging.debug("Cleaned the cache")
|
FileStream/utils/database.py
ADDED
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pymongo
|
2 |
+
import time
|
3 |
+
import motor.motor_asyncio
|
4 |
+
from bson.objectid import ObjectId
|
5 |
+
from bson.errors import InvalidId
|
6 |
+
from FileStream.server.exceptions import FIleNotFound
|
7 |
+
|
8 |
+
class Database:
|
9 |
+
def __init__(self, uri, database_name):
|
10 |
+
self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
|
11 |
+
self.db = self._client[database_name]
|
12 |
+
self.col = self.db.users
|
13 |
+
self.black = self.db.blacklist
|
14 |
+
self.file = self.db.file
|
15 |
+
|
16 |
+
#---------------------[ NEW USER ]---------------------#
|
17 |
+
def new_user(self, id):
|
18 |
+
return dict(
|
19 |
+
id=id,
|
20 |
+
join_date=time.time(),
|
21 |
+
agreed_to_tos=False,
|
22 |
+
Links=0,
|
23 |
+
Plan="Free"
|
24 |
+
)
|
25 |
+
|
26 |
+
# ---------------------[ ADD USER ]---------------------#
|
27 |
+
async def add_user(self, id):
|
28 |
+
user = self.new_user(id)
|
29 |
+
await self.col.insert_one(user)
|
30 |
+
|
31 |
+
# ---------------------[ GET USER ]---------------------#
|
32 |
+
async def get_user(self, id):
|
33 |
+
user = await self.col.find_one({'id': int(id)})
|
34 |
+
return user
|
35 |
+
|
36 |
+
# ---------------------[ CHECK USER ]---------------------#
|
37 |
+
async def total_users_count(self):
|
38 |
+
count = await self.col.count_documents({})
|
39 |
+
return count
|
40 |
+
|
41 |
+
async def get_all_users(self):
|
42 |
+
all_users = self.col.find({})
|
43 |
+
return all_users
|
44 |
+
|
45 |
+
# ---------------------[ REMOVE USER ]---------------------#
|
46 |
+
async def delete_user(self, user_id):
|
47 |
+
await self.col.delete_many({'id': int(user_id)})
|
48 |
+
|
49 |
+
# ---------------------[ BAN, UNBAN USER ]---------------------#
|
50 |
+
def black_user(self, id):
|
51 |
+
return dict(
|
52 |
+
id=id,
|
53 |
+
ban_date=time.time()
|
54 |
+
)
|
55 |
+
|
56 |
+
async def ban_user(self, id):
|
57 |
+
user = self.black_user(id)
|
58 |
+
await self.black.insert_one(user)
|
59 |
+
|
60 |
+
async def unban_user(self, id):
|
61 |
+
await self.black.delete_one({'id': int(id)})
|
62 |
+
|
63 |
+
async def is_user_banned(self, id):
|
64 |
+
user = await self.black.find_one({'id': int(id)})
|
65 |
+
return True if user else False
|
66 |
+
|
67 |
+
async def total_banned_users_count(self):
|
68 |
+
count = await self.black.count_documents({})
|
69 |
+
return count
|
70 |
+
|
71 |
+
# ---------------------[ ADD FILE TO DB ]---------------------#
|
72 |
+
async def add_file(self, file_info):
|
73 |
+
file_info["time"] = time.time()
|
74 |
+
fetch_old = await self.get_file_by_fileuniqueid(file_info["user_id"], file_info["file_unique_id"])
|
75 |
+
if fetch_old:
|
76 |
+
return fetch_old["_id"]
|
77 |
+
await self.count_links(file_info["user_id"], "+")
|
78 |
+
return (await self.file.insert_one(file_info)).inserted_id
|
79 |
+
|
80 |
+
# ---------------------[ FIND FILE IN DB ]---------------------#
|
81 |
+
async def find_files(self, user_id, range):
|
82 |
+
user_files=self.file.find({"user_id": user_id})
|
83 |
+
user_files.skip(range[0] - 1)
|
84 |
+
user_files.limit(range[1] - range[0] + 1)
|
85 |
+
user_files.sort('_id', pymongo.DESCENDING)
|
86 |
+
total_files = await self.file.count_documents({"user_id": user_id})
|
87 |
+
return user_files, total_files
|
88 |
+
|
89 |
+
async def get_file(self, _id):
|
90 |
+
try:
|
91 |
+
file_info=await self.file.find_one({"_id": ObjectId(_id)})
|
92 |
+
if not file_info:
|
93 |
+
raise FIleNotFound
|
94 |
+
return file_info
|
95 |
+
except InvalidId:
|
96 |
+
raise FIleNotFound
|
97 |
+
|
98 |
+
async def get_file_by_fileuniqueid(self, id, file_unique_id, many=False):
|
99 |
+
if many:
|
100 |
+
return self.file.find({"file_unique_id": file_unique_id})
|
101 |
+
else:
|
102 |
+
file_info=await self.file.find_one({"user_id": id, "file_unique_id": file_unique_id})
|
103 |
+
if file_info:
|
104 |
+
return file_info
|
105 |
+
return False
|
106 |
+
|
107 |
+
# ---------------------[ TOTAL FILES ]---------------------#
|
108 |
+
async def total_files(self, id=None):
|
109 |
+
if id:
|
110 |
+
return await self.file.count_documents({"user_id": id})
|
111 |
+
return await self.file.count_documents({})
|
112 |
+
|
113 |
+
# ---------------------[ DELETE FILES ]---------------------#
|
114 |
+
async def delete_one_file(self, _id):
|
115 |
+
await self.file.delete_one({'_id': ObjectId(_id)})
|
116 |
+
|
117 |
+
# ---------------------[ UPDATE FILES ]---------------------#
|
118 |
+
async def update_file_ids(self, _id, file_ids: dict):
|
119 |
+
await self.file.update_one({"_id": ObjectId(_id)}, {"$set": {"file_ids": file_ids}})
|
120 |
+
|
121 |
+
# ---------------------[ PAID SYS ]---------------------#
|
122 |
+
async def link_available(self, id):
|
123 |
+
user = await self.col.find_one({"id": id})
|
124 |
+
if user.get("Plan") == "Plus":
|
125 |
+
return "Plus"
|
126 |
+
elif user.get("Plan") == "Free":
|
127 |
+
files = await self.file.count_documents({"user_id": id})
|
128 |
+
if files < 11:
|
129 |
+
return True
|
130 |
+
return False
|
131 |
+
|
132 |
+
async def count_links(self, id, operation: str):
|
133 |
+
if operation == "-":
|
134 |
+
await self.col.update_one({"id": id}, {"$inc": {"Links": -1}})
|
135 |
+
elif operation == "+":
|
136 |
+
await self.col.update_one({"id": id}, {"$inc": {"Links": 1}})
|
FileStream/utils/file_properties.py
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
import logging
|
3 |
+
from datetime import datetime
|
4 |
+
from pyrogram import Client
|
5 |
+
from typing import Any, Optional
|
6 |
+
|
7 |
+
from pyrogram.enums import ParseMode, ChatType
|
8 |
+
from pyrogram.types import Message
|
9 |
+
from pyrogram.file_id import FileId
|
10 |
+
from FileStream.bot import FileStream
|
11 |
+
from FileStream.utils.database import Database
|
12 |
+
from FileStream.config import Telegram, Server
|
13 |
+
|
14 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
15 |
+
|
16 |
+
|
17 |
+
async def get_file_ids(client: Client | bool, db_id: str, multi_clients, message) -> Optional[FileId]:
|
18 |
+
logging.debug("Starting of get_file_ids")
|
19 |
+
file_info = await db.get_file(db_id)
|
20 |
+
if (not "file_ids" in file_info) or not client:
|
21 |
+
logging.debug("Storing file_id of all clients in DB")
|
22 |
+
log_msg = await send_file(FileStream, db_id, file_info['file_id'], message)
|
23 |
+
await db.update_file_ids(db_id, await update_file_id(log_msg.id, multi_clients))
|
24 |
+
logging.debug("Stored file_id of all clients in DB")
|
25 |
+
if not client:
|
26 |
+
return
|
27 |
+
file_info = await db.get_file(db_id)
|
28 |
+
|
29 |
+
file_id_info = file_info.setdefault("file_ids", {})
|
30 |
+
if not str(client.id) in file_id_info:
|
31 |
+
logging.debug("Storing file_id in DB")
|
32 |
+
log_msg = await send_file(FileStream, db_id, file_info['file_id'], message)
|
33 |
+
msg = await client.get_messages(Telegram.LOG_CHANNEL, log_msg.id)
|
34 |
+
media = get_media_from_message(msg)
|
35 |
+
file_id_info[str(client.id)] = getattr(media, "file_id", "")
|
36 |
+
await db.update_file_ids(db_id, file_id_info)
|
37 |
+
logging.debug("Stored file_id in DB")
|
38 |
+
|
39 |
+
logging.debug("Middle of get_file_ids")
|
40 |
+
file_id = FileId.decode(file_id_info[str(client.id)])
|
41 |
+
setattr(file_id, "file_size", file_info['file_size'])
|
42 |
+
setattr(file_id, "mime_type", file_info['mime_type'])
|
43 |
+
setattr(file_id, "file_name", file_info['file_name'])
|
44 |
+
setattr(file_id, "unique_id", file_info['file_unique_id'])
|
45 |
+
logging.debug("Ending of get_file_ids")
|
46 |
+
return file_id
|
47 |
+
|
48 |
+
|
49 |
+
def get_media_from_message(message: "Message") -> Any:
|
50 |
+
media_types = (
|
51 |
+
"audio",
|
52 |
+
"document",
|
53 |
+
"photo",
|
54 |
+
"sticker",
|
55 |
+
"animation",
|
56 |
+
"video",
|
57 |
+
"voice",
|
58 |
+
"video_note",
|
59 |
+
)
|
60 |
+
for attr in media_types:
|
61 |
+
media = getattr(message, attr, None)
|
62 |
+
if media:
|
63 |
+
return media
|
64 |
+
|
65 |
+
|
66 |
+
def get_media_file_size(m):
|
67 |
+
media = get_media_from_message(m)
|
68 |
+
return getattr(media, "file_size", "None")
|
69 |
+
|
70 |
+
|
71 |
+
def get_name(media_msg: Message | FileId) -> str:
|
72 |
+
if isinstance(media_msg, Message):
|
73 |
+
media = get_media_from_message(media_msg)
|
74 |
+
file_name = getattr(media, "file_name", "")
|
75 |
+
|
76 |
+
elif isinstance(media_msg, FileId):
|
77 |
+
file_name = getattr(media_msg, "file_name", "")
|
78 |
+
|
79 |
+
if not file_name:
|
80 |
+
if isinstance(media_msg, Message) and media_msg.media:
|
81 |
+
media_type = media_msg.media.value
|
82 |
+
elif media_msg.file_type:
|
83 |
+
media_type = media_msg.file_type.name.lower()
|
84 |
+
else:
|
85 |
+
media_type = "file"
|
86 |
+
|
87 |
+
formats = {
|
88 |
+
"photo": "jpg", "audio": "mp3", "voice": "ogg",
|
89 |
+
"video": "mp4", "animation": "mp4", "video_note": "mp4",
|
90 |
+
"sticker": "webp"
|
91 |
+
}
|
92 |
+
|
93 |
+
ext = formats.get(media_type)
|
94 |
+
ext = "." + ext if ext else ""
|
95 |
+
|
96 |
+
date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
97 |
+
file_name = f"{media_type}-{date}{ext}"
|
98 |
+
|
99 |
+
return file_name
|
100 |
+
|
101 |
+
|
102 |
+
def get_file_info(message):
|
103 |
+
media = get_media_from_message(message)
|
104 |
+
if message.chat.type == ChatType.PRIVATE:
|
105 |
+
user_idx = message.from_user.id
|
106 |
+
else:
|
107 |
+
user_idx = message.chat.id
|
108 |
+
return {
|
109 |
+
"user_id": user_idx,
|
110 |
+
"file_id": getattr(media, "file_id", ""),
|
111 |
+
"file_unique_id": getattr(media, "file_unique_id", ""),
|
112 |
+
"file_name": get_name(message),
|
113 |
+
"file_size": getattr(media, "file_size", 0),
|
114 |
+
"mime_type": getattr(media, "mime_type", "None/unknown")
|
115 |
+
}
|
116 |
+
|
117 |
+
|
118 |
+
async def update_file_id(msg_id, multi_clients):
|
119 |
+
file_ids = {}
|
120 |
+
for client_id, client in multi_clients.items():
|
121 |
+
log_msg = await client.get_messages(Telegram.LOG_CHANNEL, msg_id)
|
122 |
+
media = get_media_from_message(log_msg)
|
123 |
+
file_ids[str(client.id)] = getattr(media, "file_id", "")
|
124 |
+
|
125 |
+
return file_ids
|
126 |
+
|
127 |
+
|
128 |
+
async def send_file(client: Client, db_id, file_id: str, message):
|
129 |
+
file_caption = message.caption
|
130 |
+
if file_caption is None:
|
131 |
+
file_caption = 'okay'
|
132 |
+
log_msg = await client.send_cached_media(chat_id=Telegram.LOG_CHANNEL, file_id=file_id,
|
133 |
+
caption=f'**{file_caption}**')
|
134 |
+
|
135 |
+
if message.chat.type == ChatType.PRIVATE:
|
136 |
+
await log_msg.reply_text(
|
137 |
+
text=f"reguested by : [{message.from_user.first_name}](tg://user?id={message.from_user.id})\nuser id : `{message.from_user.id}`\n file id : `{db_id}`\nStream Link : `https://telegbotnavpan2.onrender.com/watch/{db_id}`\nDownload Link : `https://telegbotnavpan2.onrender.com/dl/{db_id}`",
|
138 |
+
disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True)
|
139 |
+
else:
|
140 |
+
await log_msg.reply_text(
|
141 |
+
text=f"file id : `{db_id}`\nStream Link : `https://telegbotnavpan2.onrender.com/watch/{db_id}`\nDownload Link : `https://telegbotnavpan2.onrender.com/dl/{db_id}`",
|
142 |
+
disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True)
|
143 |
+
|
144 |
+
return log_msg
|
145 |
+
# return await client.send_cached_media(Telegram.BIN_CHANNEL, file_id)
|
146 |
+
|
FileStream/utils/human_readable.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def humanbytes(size):
|
2 |
+
if not size:
|
3 |
+
return ""
|
4 |
+
power = 2**10
|
5 |
+
n = 0
|
6 |
+
Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'}
|
7 |
+
while size > power:
|
8 |
+
size /= power
|
9 |
+
n += 1
|
10 |
+
return str(round(size, 2)) + " " + Dic_powerN[n] + 'B'
|
FileStream/utils/keepalive.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import logging
|
3 |
+
import aiohttp
|
4 |
+
import traceback
|
5 |
+
from FileStream.config import Server
|
6 |
+
|
7 |
+
async def ping_server():
|
8 |
+
sleep_time = Server.PING_INTERVAL
|
9 |
+
while True:
|
10 |
+
await asyncio.sleep(sleep_time)
|
11 |
+
try:
|
12 |
+
async with aiohttp.ClientSession(
|
13 |
+
timeout=aiohttp.ClientTimeout(total=10)
|
14 |
+
) as session:
|
15 |
+
async with session.get(Server.URL) as resp:
|
16 |
+
logging.info("Pinged server with response: {}".format(resp.status))
|
17 |
+
except TimeoutError:
|
18 |
+
logging.warning("Couldn't connect to the site URL..!")
|
19 |
+
except Exception:
|
20 |
+
traceback.print_exc()
|
FileStream/utils/render_template.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import aiohttp
|
2 |
+
import aiofiles
|
3 |
+
import urllib.parse
|
4 |
+
from FileStream.config import Telegram, Server
|
5 |
+
from FileStream.utils.database import Database
|
6 |
+
from FileStream.utils.human_readable import humanbytes
|
7 |
+
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
8 |
+
|
9 |
+
async def render_page(db_id):
|
10 |
+
file_data=await db.get_file(db_id)
|
11 |
+
src = urllib.parse.urljoin(Server.URL, f'dl/{file_data["_id"]}')
|
12 |
+
|
13 |
+
if str((file_data['mime_type']).split('/')[0].strip()) == 'video':
|
14 |
+
async with aiofiles.open('FileStream/template/stream.html') as r:
|
15 |
+
heading = 'Watch {}'.format(file_data['file_name'])
|
16 |
+
html_template = await r.read()
|
17 |
+
html = html_template.replace('streamMediaLink', src).replace('streamHeading', heading)
|
18 |
+
else:
|
19 |
+
async with aiofiles.open('FileStream/template/dl.html') as r:
|
20 |
+
async with aiohttp.ClientSession() as s:
|
21 |
+
async with s.get(src) as u:
|
22 |
+
heading = 'Download {}'.format(file_data['file_name'])
|
23 |
+
file_size = humanbytes(int(u.headers.get('Content-Length')))
|
24 |
+
html = (await r.read()) % (heading, file_data['file_name'], src, file_size)
|
25 |
+
return html
|
FileStream/utils/time_format.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def get_readable_time(seconds: int) -> str:
|
2 |
+
count = 0
|
3 |
+
readable_time = ""
|
4 |
+
time_list = []
|
5 |
+
time_suffix_list = ["s", "m", "h", " days"]
|
6 |
+
while count < 4:
|
7 |
+
count += 1
|
8 |
+
if count < 3:
|
9 |
+
remainder, result = divmod(seconds, 60)
|
10 |
+
else:
|
11 |
+
remainder, result = divmod(seconds, 24)
|
12 |
+
if seconds == 0 and remainder == 0:
|
13 |
+
break
|
14 |
+
time_list.append(int(result))
|
15 |
+
seconds = int(remainder)
|
16 |
+
for x in range(len(time_list)):
|
17 |
+
time_list[x] = str(time_list[x]) + time_suffix_list[x]
|
18 |
+
if len(time_list) == 4:
|
19 |
+
readable_time += time_list.pop() + ", "
|
20 |
+
time_list.reverse()
|
21 |
+
readable_time += ": ".join(time_list)
|
22 |
+
return readable_time
|
FileStream/utils/translation.py
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
2 |
+
from FileStream.config import Telegram
|
3 |
+
|
4 |
+
class LANG(object):
|
5 |
+
|
6 |
+
START_TEXT = """
|
7 |
+
<b>👋 Hᴇʏ, </b>{}\n
|
8 |
+
<b>I'ᴍ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇs sᴛʀᴇᴀᴍɪɴɢ ʙᴏᴛ ᴀs ᴡᴇʟʟ ᴅɪʀᴇᴄᴛ ʟɪɴᴋs ɢᴇɴᴇʀᴀᴛᴏʀ</b>\n
|
9 |
+
<b>ᴡᴏʀᴋɪɴɢ ᴏɴ ᴄʜᴀɴɴᴇʟs ᴀɴᴅ ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛ</b>\n
|
10 |
+
<b>💕 @{}</b>\n"""
|
11 |
+
|
12 |
+
HELP_TEXT = """
|
13 |
+
<b>- ᴀᴅᴅ ᴍᴇ ᴀs ᴀɴ ᴀᴅᴍɪɴ ᴏɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ</b>
|
14 |
+
<b>- sᴇɴᴅ ᴍᴇ ᴀɴʏ ᴅᴏᴄᴜᴍᴇɴᴛ ᴏʀ ᴍᴇᴅɪᴀ</b>
|
15 |
+
<b>- ɪ'ʟʟ ᴘʀᴏᴠɪᴅᴇ sᴛʀᴇᴀᴍᴀʙʟᴇ ʟɪɴᴋ</b>\n
|
16 |
+
<b>🔞 ᴀᴅᴜʟᴛ ᴄᴏɴᴛᴇɴᴛ sᴛʀɪᴄᴛʟʏ ᴘʀᴏʜɪʙɪᴛᴇᴅ.</b>\n
|
17 |
+
<i><b> ʀᴇᴘᴏʀᴛ ʙᴜɢs ᴛᴏ <a href='https://telegram.me/AvishkarPatil'>ᴅᴇᴠᴇʟᴏᴘᴇʀ</a></b></i>"""
|
18 |
+
|
19 |
+
ABOUT_TEXT = """
|
20 |
+
<b>⚜ ᴍʏ ɴᴀᴍᴇ : {}</b>\n
|
21 |
+
<b>✦ ᴠᴇʀsɪᴏɴ : {}</b>
|
22 |
+
<b>✦ ᴜᴘᴅᴀᴛᴇᴅ ᴏɴ : 19-November-2023</b>
|
23 |
+
<b>✦ ᴅᴇᴠᴇʟᴏᴘᴇʀ : <a href='https://telegram.me/AvishkarPatil'>Avishkar Patil</a></b>\n
|
24 |
+
"""
|
25 |
+
|
26 |
+
STREAM_TEXT = """
|
27 |
+
<i><u>𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !</u></i>\n
|
28 |
+
<b>📂 Fɪʟᴇ ɴᴀᴍᴇ :</b> <b>{}</b>\n
|
29 |
+
<b>📦 Fɪʟᴇ ꜱɪᴢᴇ :</b> <code>{}</code>\n
|
30 |
+
<b>📥 Dᴏᴡɴʟᴏᴀᴅ :</b> <code>{}</code>\n
|
31 |
+
<b>🖥 Wᴀᴛᴄʜ :</b> <code>{}</code>\n
|
32 |
+
<b>🔗 Sʜᴀʀᴇ :</b> <code>{}</code>\n"""
|
33 |
+
|
34 |
+
STREAM_TEXT_X = """
|
35 |
+
<i><u>𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !</u></i>\n
|
36 |
+
<b>📂 Fɪʟᴇ ɴᴀᴍᴇ :</b> <b>{}</b>\n
|
37 |
+
<b>📦 Fɪʟᴇ ꜱɪᴢᴇ :</b> <code>{}</code>\n
|
38 |
+
<b>📥 Dᴏᴡɴʟᴏᴀᴅ :</b> <code>{}</code>\n
|
39 |
+
<b>🔗 Sʜᴀʀᴇ :</b> <code>{}</code>\n"""
|
40 |
+
|
41 |
+
|
42 |
+
BAN_TEXT = "__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ.__\n\n**[Cᴏɴᴛᴀᴄᴛ Dᴇᴠᴇʟᴏᴘᴇʀ](tg://user?id={}) Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**"
|
43 |
+
|
44 |
+
|
45 |
+
class BUTTON(object):
|
46 |
+
START_BUTTONS = InlineKeyboardMarkup(
|
47 |
+
[[
|
48 |
+
InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'),
|
49 |
+
InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about'),
|
50 |
+
InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close')
|
51 |
+
],
|
52 |
+
[InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')]
|
53 |
+
]
|
54 |
+
)
|
55 |
+
HELP_BUTTONS = InlineKeyboardMarkup(
|
56 |
+
[[
|
57 |
+
InlineKeyboardButton('ʜᴏᴍᴇ', callback_data='home'),
|
58 |
+
InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about'),
|
59 |
+
InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close'),
|
60 |
+
],
|
61 |
+
[InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')]
|
62 |
+
]
|
63 |
+
)
|
64 |
+
ABOUT_BUTTONS = InlineKeyboardMarkup(
|
65 |
+
[[
|
66 |
+
InlineKeyboardButton('ʜᴏᴍᴇ', callback_data='home'),
|
67 |
+
InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'),
|
68 |
+
InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close'),
|
69 |
+
],
|
70 |
+
[InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')]
|
71 |
+
]
|
72 |
+
)
|
Procfile
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
web: python -m FileStream
|
app.json
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "FileStreamBot",
|
3 |
+
"description": "A Telegram Bot to Convert Telegram Files To Direct & Streamable Links.",
|
4 |
+
"keywords": [
|
5 |
+
"telegram",
|
6 |
+
"files",
|
7 |
+
"stream"
|
8 |
+
],
|
9 |
+
"repository": "https://github.com/avipatilpro/FileStreamBot/",
|
10 |
+
"success_url": "/",
|
11 |
+
"logo": "https://i.ibb.co/ZJzJ9Hq/link-3x.png",
|
12 |
+
"env": {
|
13 |
+
"ENV": {
|
14 |
+
"description": "Set this to True if you don't want to crash the bot",
|
15 |
+
"value": "True"
|
16 |
+
},
|
17 |
+
"API_ID": {
|
18 |
+
"description": "Get this value from https://my.telegram.org"
|
19 |
+
},
|
20 |
+
"API_HASH": {
|
21 |
+
"description": "Get this value from https://my.telegram.org"
|
22 |
+
},
|
23 |
+
"BOT_TOKEN": {
|
24 |
+
"description": "Get this value from @BotFather"
|
25 |
+
},
|
26 |
+
"LOG_CHANNEL": {
|
27 |
+
"description": "The BIN Channel ID. Read the readme for more info about this var"
|
28 |
+
},
|
29 |
+
"OWNER_ID": {
|
30 |
+
"description": "Your Telegram User ID as Owner"
|
31 |
+
},
|
32 |
+
"DATABASE_URL": {
|
33 |
+
"description": "MongoDB URI for saving User Data and Files List created by user."
|
34 |
+
},
|
35 |
+
"AUTH_USERS": {
|
36 |
+
"description": "Put IDs of Banned Channels where bot will not work. You can add multiple IDs & separate with Space.",
|
37 |
+
"required": false
|
38 |
+
},
|
39 |
+
"UPDATES_CHANNEL": {
|
40 |
+
"description": "Channel Username without `@` to set channel as Update Channel",
|
41 |
+
"required": false
|
42 |
+
},
|
43 |
+
"FORCE_UPDATES_CHANNEL": {
|
44 |
+
"description": "Set to True, so every user have to Join update channel to use the bot.",
|
45 |
+
"required": false
|
46 |
+
},
|
47 |
+
"SLEEP_THRESHOLD": {
|
48 |
+
"description": "Set global flood wait threshold, auto-retry requests under 60s. ",
|
49 |
+
"required": false
|
50 |
+
},
|
51 |
+
"PING_INTERVAL": {
|
52 |
+
"description": " Heroku ping interval (ms), defaults to 1200 (20 minutes).",
|
53 |
+
"required": false
|
54 |
+
},
|
55 |
+
"WORKERS": {
|
56 |
+
"description": "No. of workers that is to be assigned.",
|
57 |
+
"required": false
|
58 |
+
},
|
59 |
+
"PORT": {
|
60 |
+
"description": "The port that you want your webapp to be listened to",
|
61 |
+
"required": false
|
62 |
+
},
|
63 |
+
"NO_PORT": {
|
64 |
+
"description": "If you don't want your port to be displayed. Set True or False",
|
65 |
+
"value": "True",
|
66 |
+
"required": false
|
67 |
+
},
|
68 |
+
"HAS_SSL": {
|
69 |
+
"description": "(can be either True or False) If you want the generated links in https format.",
|
70 |
+
"value": "True",
|
71 |
+
"required": false
|
72 |
+
},
|
73 |
+
"KEEP_ALIVE": {
|
74 |
+
"description": "Self-ping server every PING_INTERVAL sec to avoid sleeping",
|
75 |
+
"value": "True",
|
76 |
+
"required": false
|
77 |
+
},
|
78 |
+
"BIND_ADRESS": {
|
79 |
+
"description": "Your server bind adress. Defauls to 0.0.0.0",
|
80 |
+
"required": false
|
81 |
+
},
|
82 |
+
"FQDN": {
|
83 |
+
"description": "Heroku app Link without http/s, you can set later. its required",
|
84 |
+
"required": false
|
85 |
+
},
|
86 |
+
"SESSION_NAME": {
|
87 |
+
"description": "Name for the Database created on your MongoDB. Defaults to FileStream",
|
88 |
+
"required": false
|
89 |
+
},
|
90 |
+
"IMAGE_FILEID": {
|
91 |
+
"description": "To set Image at /files command. Defaults to pre-set image.",
|
92 |
+
"required": false
|
93 |
+
},
|
94 |
+
"MODE": {
|
95 |
+
"description": "Should be set to `secondary` if you only want to use the server for serving files.",
|
96 |
+
"required": false
|
97 |
+
}
|
98 |
+
},
|
99 |
+
"buildpacks": [
|
100 |
+
{
|
101 |
+
"url": "heroku/python"
|
102 |
+
}
|
103 |
+
],
|
104 |
+
"stack": "heroku-22",
|
105 |
+
"formation": {
|
106 |
+
"web": {
|
107 |
+
"quantity": 1,
|
108 |
+
"size": "eco"
|
109 |
+
}
|
110 |
+
}
|
111 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
aiohttp
|
2 |
+
pyrogram
|
3 |
+
python-dotenv
|
4 |
+
tgcrypto
|
5 |
+
motor
|
6 |
+
aiofiles
|
7 |
+
dnspython
|
8 |
+
requests
|