mirror of
https://github.com/avipatilpro/FileStreamBot.git
synced 2026-01-16 06:42:54 -03:00
Updated ?
Nothing new but, looks like something changed ;) !
This commit is contained in:
5
FileStream/__init__.py
Normal file
5
FileStream/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import time
|
||||
|
||||
__version__ = "1.0.1"
|
||||
StartTime = time.time()
|
||||
|
||||
82
FileStream/__main__.py
Normal file
82
FileStream/__main__.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
import traceback
|
||||
import logging.handlers as handlers
|
||||
from .config import Telegram, Server
|
||||
from aiohttp import web
|
||||
from pyrogram import idle
|
||||
|
||||
from FileStream.bot import FileStream
|
||||
from FileStream.server import web_server
|
||||
from FileStream.utils import ping_server
|
||||
from FileStream.bot.clients import initialize_clients
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
datefmt="%d/%m/%Y %H:%M:%S",
|
||||
format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
|
||||
handlers=[logging.StreamHandler(stream=sys.stdout),
|
||||
handlers.RotatingFileHandler("streambot.log", mode="a", maxBytes=104857600, backupCount=2, encoding="utf-8")],)
|
||||
|
||||
logging.getLogger("aiohttp").setLevel(logging.ERROR)
|
||||
logging.getLogger("pyrogram").setLevel(logging.ERROR)
|
||||
logging.getLogger("aiohttp.web").setLevel(logging.ERROR)
|
||||
|
||||
server = web.AppRunner(web_server())
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
async def start_services():
|
||||
print()
|
||||
if Telegram.SECONDARY:
|
||||
print("------------------ Starting as Secondary Server ------------------")
|
||||
else:
|
||||
print("------------------- Starting as Primary Server -------------------")
|
||||
print()
|
||||
print("-------------------- Initializing Telegram Bot --------------------")
|
||||
|
||||
|
||||
await FileStream.start()
|
||||
bot_info = await FileStream.get_me()
|
||||
FileStream.id = bot_info.id
|
||||
FileStream.username = bot_info.username
|
||||
FileStream.fname=bot_info.first_name
|
||||
print("------------------------------ DONE ------------------------------")
|
||||
print()
|
||||
print("---------------------- Initializing Clients ----------------------")
|
||||
await initialize_clients()
|
||||
print("------------------------------ DONE ------------------------------")
|
||||
if Server.KEEP_ALIVE:
|
||||
print("------------------ Starting Keep Alive Service ------------------")
|
||||
print()
|
||||
asyncio.create_task(ping_server())
|
||||
print()
|
||||
print("--------------------- Initializing Web Server ---------------------")
|
||||
await server.setup()
|
||||
await web.TCPSite(server, Server.BIND_ADDRESS, Server.PORT).start()
|
||||
print("------------------------------ DONE ------------------------------")
|
||||
print()
|
||||
print("------------------------- Service Started -------------------------")
|
||||
print(" bot =>> {}".format(bot_info.first_name))
|
||||
if bot_info.dc_id:
|
||||
print(" DC ID =>> {}".format(str(bot_info.dc_id)))
|
||||
print(" URL =>> {}".format(Server.URL))
|
||||
print("------------------------------------------------------------------")
|
||||
await idle()
|
||||
|
||||
async def cleanup():
|
||||
await server.cleanup()
|
||||
await FileStream.stop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
loop.run_until_complete(start_services())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception as err:
|
||||
logging.error(traceback.format_exc())
|
||||
finally:
|
||||
loop.run_until_complete(cleanup())
|
||||
loop.stop()
|
||||
print("------------------------ Stopped Services ------------------------")
|
||||
25
FileStream/bot/__init__.py
Normal file
25
FileStream/bot/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from ..config import Telegram
|
||||
from pyrogram import Client
|
||||
|
||||
if Telegram.SECONDARY:
|
||||
plugins=None
|
||||
no_updates=True
|
||||
else:
|
||||
plugins={"root": "FileStream/bot/plugins"}
|
||||
no_updates=None
|
||||
|
||||
FileStream = Client(
|
||||
name="FileStream",
|
||||
api_id=Telegram.API_ID,
|
||||
api_hash=Telegram.API_HASH,
|
||||
workdir="FileStream",
|
||||
plugins=plugins,
|
||||
bot_token=Telegram.BOT_TOKEN,
|
||||
sleep_threshold=Telegram.SLEEP_THRESHOLD,
|
||||
workers=Telegram.WORKERS,
|
||||
no_updates=no_updates
|
||||
)
|
||||
|
||||
multi_clients = {}
|
||||
work_loads = {}
|
||||
|
||||
59
FileStream/bot/clients.py
Normal file
59
FileStream/bot/clients.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from os import environ
|
||||
from ..config import Telegram
|
||||
from pyrogram import Client
|
||||
from . import multi_clients, work_loads, FileStream
|
||||
|
||||
|
||||
async def initialize_clients():
|
||||
all_tokens = dict(
|
||||
(c + 1, t)
|
||||
for c, (_, t) in enumerate(
|
||||
filter(
|
||||
lambda n: n[0].startswith("MULTI_TOKEN"), sorted(environ.items())
|
||||
)
|
||||
)
|
||||
)
|
||||
if not all_tokens:
|
||||
multi_clients[0] = FileStream
|
||||
work_loads[0] = 0
|
||||
print("No additional clients found, using default client")
|
||||
return
|
||||
|
||||
async def start_client(client_id, token):
|
||||
try:
|
||||
if len(token) >= 100:
|
||||
session_string=token
|
||||
bot_token=None
|
||||
print(f'Starting Client - {client_id} Using Session String')
|
||||
else:
|
||||
session_string=None
|
||||
bot_token=token
|
||||
print(f'Starting Client - {client_id} Using Bot Token')
|
||||
if client_id == len(all_tokens):
|
||||
await asyncio.sleep(2)
|
||||
print("This will take some time, please wait...")
|
||||
client = await Client(
|
||||
name=str(client_id),
|
||||
api_id=Telegram.API_ID,
|
||||
api_hash=Telegram.API_HASH,
|
||||
bot_token=bot_token,
|
||||
sleep_threshold=Telegram.SLEEP_THRESHOLD,
|
||||
no_updates=True,
|
||||
session_string=session_string,
|
||||
in_memory=True,
|
||||
).start()
|
||||
client.id = (await client.get_me()).id
|
||||
work_loads[client_id] = 0
|
||||
return client_id, client
|
||||
except Exception:
|
||||
logging.error(f"Failed starting Client - {client_id} Error:", exc_info=True)
|
||||
|
||||
clients = await asyncio.gather(*[start_client(i, token) for i, token in all_tokens.items()])
|
||||
multi_clients.update(dict(clients))
|
||||
if len(multi_clients) != 1:
|
||||
Telegram.MULTI_CLIENT = True
|
||||
print("Multi-Client Mode Enabled")
|
||||
else:
|
||||
print("No additional clients were initialized, using default client")
|
||||
160
FileStream/bot/plugins/admin.py
Normal file
160
FileStream/bot/plugins/admin.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import os
|
||||
import time
|
||||
import string
|
||||
import random
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import datetime
|
||||
|
||||
from FileStream.utils.broadcast_helper import send_msg
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.bot import FileStream
|
||||
from FileStream.server.exceptions import FIleNotFound
|
||||
from FileStream.config import Telegram, Server
|
||||
from pyrogram import filters, Client
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.enums.parse_mode import ParseMode
|
||||
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
broadcast_ids = {}
|
||||
|
||||
|
||||
@FileStream.on_message(filters.command("status") & filters.private & filters.user(Telegram.OWNER_ID))
|
||||
async def sts(c: Client, m: Message):
|
||||
await m.reply_text(text=f"""**Total Users in DB:** `{await db.total_users_count()}`
|
||||
**Banned Users in DB:** `{await db.total_banned_users_count()}`
|
||||
**Total Links Generated: ** `{await db.total_files()}`"""
|
||||
, parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
|
||||
|
||||
@FileStream.on_message(filters.command("ban") & filters.private & filters.user(Telegram.OWNER_ID))
|
||||
async def sts(b, m: Message):
|
||||
id = m.text.split("/ban ")[-1]
|
||||
if not await db.is_user_banned(int(id)):
|
||||
try:
|
||||
await db.ban_user(int(id))
|
||||
await db.delete_user(int(id))
|
||||
await m.reply_text(text=f"`{id}`** is Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
if not str(id).startswith('-100'):
|
||||
await b.send_message(
|
||||
chat_id=id,
|
||||
text="**Your Banned to Use The Bot**",
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
except Exception as e:
|
||||
await m.reply_text(text=f"**something went wrong: {e}** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
else:
|
||||
await m.reply_text(text=f"`{id}`** is Already Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
|
||||
|
||||
@FileStream.on_message(filters.command("unban") & filters.private & filters.user(Telegram.OWNER_ID))
|
||||
async def sts(b, m: Message):
|
||||
id = m.text.split("/unban ")[-1]
|
||||
if await db.is_user_banned(int(id)):
|
||||
try:
|
||||
await db.unban_user(int(id))
|
||||
await m.reply_text(text=f"`{id}`** is Unbanned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
if not str(id).startswith('-100'):
|
||||
await b.send_message(
|
||||
chat_id=id,
|
||||
text="**Your Unbanned now Use can use The Bot**",
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
except Exception as e:
|
||||
await m.reply_text(text=f"** something went wrong: {e}**", parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
else:
|
||||
await m.reply_text(text=f"`{id}`** is not Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
|
||||
|
||||
@FileStream.on_message(filters.command("broadcast") & filters.private & filters.user(Telegram.OWNER_ID) & filters.reply)
|
||||
async def broadcast_(c, m):
|
||||
all_users = await db.get_all_users()
|
||||
broadcast_msg = m.reply_to_message
|
||||
while True:
|
||||
broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)])
|
||||
if not broadcast_ids.get(broadcast_id):
|
||||
break
|
||||
out = await m.reply_text(
|
||||
text=f"Broadcast initiated! You will be notified with log file when all the users are notified."
|
||||
)
|
||||
start_time = time.time()
|
||||
total_users = await db.total_users_count()
|
||||
done = 0
|
||||
failed = 0
|
||||
success = 0
|
||||
broadcast_ids[broadcast_id] = dict(
|
||||
total=total_users,
|
||||
current=done,
|
||||
failed=failed,
|
||||
success=success
|
||||
)
|
||||
async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file:
|
||||
async for user in all_users:
|
||||
sts, msg = await send_msg(
|
||||
user_id=int(user['id']),
|
||||
message=broadcast_msg
|
||||
)
|
||||
if msg is not None:
|
||||
await broadcast_log_file.write(msg)
|
||||
if sts == 200:
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
if sts == 400:
|
||||
await db.delete_user(user['id'])
|
||||
done += 1
|
||||
if broadcast_ids.get(broadcast_id) is None:
|
||||
break
|
||||
else:
|
||||
broadcast_ids[broadcast_id].update(
|
||||
dict(
|
||||
current=done,
|
||||
failed=failed,
|
||||
success=success
|
||||
)
|
||||
)
|
||||
try:
|
||||
await out.edit_text(f"Broadcast Status\n\ncurrent: {done}\nfailed:{failed}\nsuccess: {success}")
|
||||
except:
|
||||
pass
|
||||
if broadcast_ids.get(broadcast_id):
|
||||
broadcast_ids.pop(broadcast_id)
|
||||
completed_in = datetime.timedelta(seconds=int(time.time() - start_time))
|
||||
await asyncio.sleep(3)
|
||||
await out.delete()
|
||||
if failed == 0:
|
||||
await m.reply_text(
|
||||
text=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.",
|
||||
quote=True
|
||||
)
|
||||
else:
|
||||
await m.reply_document(
|
||||
document='broadcast.txt',
|
||||
caption=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.",
|
||||
quote=True
|
||||
)
|
||||
os.remove('broadcast.txt')
|
||||
|
||||
|
||||
@FileStream.on_message(filters.command("del") & filters.private & filters.user(Telegram.OWNER_ID))
|
||||
async def sts(c: Client, m: Message):
|
||||
file_id = m.text.split(" ")[-1]
|
||||
try:
|
||||
file_info = await db.get_file(file_id)
|
||||
except FIleNotFound:
|
||||
await m.reply_text(
|
||||
text=f"**File Already Deleted**",
|
||||
quote=True
|
||||
)
|
||||
return
|
||||
await db.delete_one_file(file_info['_id'])
|
||||
await db.count_links(file_info['user_id'], "-")
|
||||
await m.reply_text(
|
||||
text=f"**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !** ",
|
||||
quote=True
|
||||
)
|
||||
|
||||
|
||||
|
||||
198
FileStream/bot/plugins/callback.py
Normal file
198
FileStream/bot/plugins/callback.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import datetime
|
||||
import math
|
||||
from FileStream import __version__
|
||||
from FileStream.bot import FileStream
|
||||
from FileStream.config import Telegram, Server
|
||||
from FileStream.utils.translation import LANG, BUTTON
|
||||
from FileStream.utils.bot_utils import gen_link
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.utils.human_readable import humanbytes
|
||||
from FileStream.server.exceptions import FIleNotFound
|
||||
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
||||
from pyrogram.file_id import FileId, FileType, PHOTO_TYPES
|
||||
from pyrogram.enums.parse_mode import ParseMode
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
|
||||
#---------------------[ START CMD ]---------------------#
|
||||
@FileStream.on_callback_query()
|
||||
async def cb_data(bot, update: CallbackQuery):
|
||||
usr_cmd = update.data.split("_")
|
||||
if usr_cmd[0] == "home":
|
||||
await update.message.edit_text(
|
||||
text=LANG.START_TEXT.format(update.from_user.mention, FileStream.username),
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=BUTTON.START_BUTTONS
|
||||
)
|
||||
elif usr_cmd[0] == "help":
|
||||
await update.message.edit_text(
|
||||
text=LANG.HELP_TEXT.format(Telegram.OWNER_ID),
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=BUTTON.HELP_BUTTONS
|
||||
)
|
||||
elif usr_cmd[0] == "about":
|
||||
await update.message.edit_text(
|
||||
text=LANG.ABOUT_TEXT.format(FileStream.fname, __version__),
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=BUTTON.ABOUT_BUTTONS
|
||||
)
|
||||
|
||||
#---------------------[ MY FILES CMD ]---------------------#
|
||||
|
||||
elif usr_cmd[0] == "N/A":
|
||||
await update.answer("N/A", True)
|
||||
elif usr_cmd[0] == "close":
|
||||
await update.message.delete()
|
||||
elif usr_cmd[0] == "msgdelete":
|
||||
await update.message.edit_caption(
|
||||
caption= "**Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ**\n\n",
|
||||
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]}")]])
|
||||
)
|
||||
elif usr_cmd[0] == "msgdelyes":
|
||||
await delete_user_file(usr_cmd[1], int(usr_cmd[2]), update)
|
||||
return
|
||||
elif usr_cmd[0] == "msgdelpvt":
|
||||
await update.message.edit_caption(
|
||||
caption= "**Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ**\n\n",
|
||||
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʏᴇs", callback_data=f"msgdelpvtyes_{usr_cmd[1]}"), InlineKeyboardButton("ɴᴏ", callback_data=f"mainstream_{usr_cmd[1]}")]])
|
||||
)
|
||||
elif usr_cmd[0] == "msgdelpvtyes":
|
||||
await delete_user_filex(usr_cmd[1], update)
|
||||
return
|
||||
|
||||
elif usr_cmd[0] == "mainstream":
|
||||
_id = usr_cmd[1]
|
||||
reply_markup, stream_text = await gen_link(_id=_id)
|
||||
await update.message.edit_text(
|
||||
text=stream_text,
|
||||
parse_mode=ParseMode.HTML,
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
elif usr_cmd[0] == "userfiles":
|
||||
file_list, total_files = await gen_file_list_button(int(usr_cmd[1]), update.from_user.id)
|
||||
await update.message.edit_caption(
|
||||
caption="Total files: {}".format(total_files),
|
||||
reply_markup=InlineKeyboardMarkup(file_list)
|
||||
)
|
||||
elif usr_cmd[0] == "myfile":
|
||||
await gen_file_menu(usr_cmd[1], usr_cmd[2], update)
|
||||
return
|
||||
elif usr_cmd[0] == "sendfile":
|
||||
myfile = await db.get_file(usr_cmd[1])
|
||||
file_name = myfile['file_name']
|
||||
await update.answer(f"Sending File {file_name}")
|
||||
await update.message.reply_cached_media(myfile['file_id'], caption=f'**{file_name}**')
|
||||
else:
|
||||
await update.message.delete()
|
||||
|
||||
|
||||
|
||||
#---------------------[ MY FILES FUNC ]---------------------#
|
||||
|
||||
async def gen_file_list_button(file_list_no: int, user_id: int):
|
||||
|
||||
file_range=[file_list_no*10-10+1, file_list_no*10]
|
||||
user_files, total_files=await db.find_files(user_id, file_range)
|
||||
|
||||
file_list=[]
|
||||
async for x in user_files:
|
||||
file_list.append([InlineKeyboardButton(x["file_name"], callback_data=f"myfile_{x['_id']}_{file_list_no}")])
|
||||
if total_files > 10:
|
||||
file_list.append(
|
||||
[InlineKeyboardButton("◄", callback_data="{}".format("userfiles_"+str(file_list_no-1) if file_list_no > 1 else 'N/A')),
|
||||
InlineKeyboardButton(f"{file_list_no}/{math.ceil(total_files/10)}", callback_data="N/A"),
|
||||
InlineKeyboardButton("►", callback_data="{}".format("userfiles_"+str(file_list_no+1) if total_files > file_list_no*10 else 'N/A'))]
|
||||
)
|
||||
if not file_list:
|
||||
file_list.append(
|
||||
[InlineKeyboardButton("ᴇᴍᴘᴛʏ", callback_data="N/A")])
|
||||
file_list.append([InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")])
|
||||
return file_list, total_files
|
||||
|
||||
async def gen_file_menu(_id, file_list_no, update: CallbackQuery):
|
||||
try:
|
||||
myfile_info=await db.get_file(_id)
|
||||
except FIleNotFound:
|
||||
await update.answer("File Not Found")
|
||||
return
|
||||
|
||||
file_id=FileId.decode(myfile_info['file_id'])
|
||||
|
||||
if file_id.file_type in PHOTO_TYPES:
|
||||
file_type = "Image"
|
||||
elif file_id.file_type == FileType.VOICE:
|
||||
file_type = "Voice"
|
||||
elif file_id.file_type in (FileType.VIDEO, FileType.ANIMATION, FileType.VIDEO_NOTE):
|
||||
file_type = "Video"
|
||||
elif file_id.file_type == FileType.DOCUMENT:
|
||||
file_type = "Document"
|
||||
elif file_id.file_type == FileType.STICKER:
|
||||
file_type = "Sticker"
|
||||
elif file_id.file_type == FileType.AUDIO:
|
||||
file_type = "Audio"
|
||||
else:
|
||||
file_type = "Unknown"
|
||||
|
||||
page_link = f"{Server.URL}watch/{myfile_info['_id']}"
|
||||
stream_link = f"{Server.URL}dl/{myfile_info['_id']}"
|
||||
if "video" in file_type.lower():
|
||||
MYFILES_BUTTONS = InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
||||
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", callback_data=f"sendfile_{myfile_info['_id']}"),
|
||||
InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelete_{myfile_info['_id']}_{file_list_no}")],
|
||||
[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="userfiles_{}".format(file_list_no))]
|
||||
]
|
||||
)
|
||||
else:
|
||||
MYFILES_BUTTONS = InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
||||
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", callback_data=f"sendfile_{myfile_info['_id']}"),
|
||||
InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelete_{myfile_info['_id']}_{file_list_no}")],
|
||||
[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="userfiles_{}".format(file_list_no))]
|
||||
]
|
||||
)
|
||||
|
||||
TiMe = myfile_info['time']
|
||||
if type(TiMe) == float:
|
||||
date = datetime.datetime.fromtimestamp(TiMe)
|
||||
await update.edit_message_caption(
|
||||
caption="**File Name :** `{}`\n**File Size :** `{}`\n**File Type :** `{}`\n**Created On :** `{}`".format(myfile_info['file_name'],
|
||||
humanbytes(int(myfile_info['file_size'])),
|
||||
file_type,
|
||||
TiMe if isinstance(TiMe,str) else date.date()),
|
||||
reply_markup=MYFILES_BUTTONS )
|
||||
|
||||
|
||||
async def delete_user_file(_id, file_list_no: int, update:CallbackQuery):
|
||||
|
||||
try:
|
||||
myfile_info=await db.get_file(_id)
|
||||
except FIleNotFound:
|
||||
await update.answer("File Already Deleted")
|
||||
return
|
||||
|
||||
await db.delete_one_file(myfile_info['_id'])
|
||||
await db.count_links(update.from_user.id, "-")
|
||||
await update.message.edit_caption(
|
||||
caption= "**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !**" + update.message.caption.replace("Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ", ""),
|
||||
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data=f"userfiles_1")]])
|
||||
)
|
||||
|
||||
async def delete_user_filex(_id, update:CallbackQuery):
|
||||
|
||||
try:
|
||||
myfile_info=await db.get_file(_id)
|
||||
except FIleNotFound:
|
||||
await update.answer("File Already Deleted")
|
||||
return
|
||||
|
||||
await db.delete_one_file(myfile_info['_id'])
|
||||
await db.count_links(update.from_user.id, "-")
|
||||
await update.message.edit_caption(
|
||||
caption= "**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !**\n\n",
|
||||
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data=f"close")]])
|
||||
)
|
||||
|
||||
119
FileStream/bot/plugins/start.py
Normal file
119
FileStream/bot/plugins/start.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import logging
|
||||
import math
|
||||
from FileStream import __version__
|
||||
from FileStream.bot import FileStream
|
||||
from FileStream.server.exceptions import FIleNotFound
|
||||
from FileStream.utils.bot_utils import gen_linkx, verify_user
|
||||
from FileStream.config import Telegram
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.utils.translation import LANG, BUTTON
|
||||
from pyrogram import filters, Client
|
||||
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
|
||||
from pyrogram.enums.parse_mode import ParseMode
|
||||
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
|
||||
@FileStream.on_message(filters.command('start') & filters.private)
|
||||
async def start(bot: Client, message: Message):
|
||||
if not await verify_user(bot, message):
|
||||
return
|
||||
usr_cmd = message.text.split("_")[-1]
|
||||
|
||||
if usr_cmd == "/start":
|
||||
await message.reply_text(
|
||||
text=LANG.START_TEXT.format(message.from_user.mention, FileStream.username),
|
||||
parse_mode=ParseMode.HTML,
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=BUTTON.START_BUTTONS
|
||||
)
|
||||
else:
|
||||
if "stream_" in message.text:
|
||||
try:
|
||||
file_check = await db.get_file(usr_cmd)
|
||||
file_id = str(file_check['_id'])
|
||||
if file_id == usr_cmd:
|
||||
reply_markup, stream_text = await gen_linkx(m=message, _id=file_id,
|
||||
name=[FileStream.username, FileStream.fname])
|
||||
await message.reply_text(
|
||||
text=stream_text,
|
||||
parse_mode=ParseMode.HTML,
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=reply_markup,
|
||||
quote=True
|
||||
)
|
||||
|
||||
except FIleNotFound as e:
|
||||
await message.reply_text("File Not Found")
|
||||
except Exception as e:
|
||||
await message.reply_text("Something Went Wrong")
|
||||
logging.error(e)
|
||||
|
||||
elif "file_" in message.text:
|
||||
try:
|
||||
file_check = await db.get_file(usr_cmd)
|
||||
db_id = str(file_check['_id'])
|
||||
file_id = file_check['file_id']
|
||||
file_name = file_check['file_name']
|
||||
if db_id == usr_cmd:
|
||||
await message.reply_cached_media(file_id=file_id, caption=f'**{file_name}**')
|
||||
|
||||
except FIleNotFound as e:
|
||||
await message.reply_text("**File Not Found**")
|
||||
except Exception as e:
|
||||
await message.reply_text("Something Went Wrong")
|
||||
logging.error(e)
|
||||
|
||||
else:
|
||||
await message.reply_text(f"**Invalid Command**")
|
||||
|
||||
@FileStream.on_message(filters.private & filters.command(["about"]))
|
||||
async def start(bot, message):
|
||||
if not await verify_user(bot, message):
|
||||
return
|
||||
await message.reply_text(
|
||||
text=LANG.ABOUT_TEXT.format(FileStream.fname, __version__),
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=BUTTON.ABOUT_BUTTONS
|
||||
)
|
||||
|
||||
|
||||
@FileStream.on_message((filters.command('help')) & filters.private)
|
||||
async def help_handler(bot, message):
|
||||
if not await verify_user(bot, message):
|
||||
return
|
||||
await message.reply_text(
|
||||
text=LANG.HELP_TEXT.format(Telegram.OWNER_ID),
|
||||
parse_mode=ParseMode.HTML,
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=BUTTON.HELP_BUTTONS
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
|
||||
@FileStream.on_message(filters.command('files') & filters.private)
|
||||
async def my_files(bot: Client, message: Message):
|
||||
if not await verify_user(bot, message):
|
||||
return
|
||||
user_files, total_files = await db.find_files(message.from_user.id, [1, 10])
|
||||
|
||||
file_list = []
|
||||
async for x in user_files:
|
||||
file_list.append([InlineKeyboardButton(x["file_name"], callback_data=f"myfile_{x['_id']}_{1}")])
|
||||
if total_files > 10:
|
||||
file_list.append(
|
||||
[
|
||||
InlineKeyboardButton("◄", callback_data="N/A"),
|
||||
InlineKeyboardButton(f"1/{math.ceil(total_files / 10)}", callback_data="N/A"),
|
||||
InlineKeyboardButton("►", callback_data="userfiles_2")
|
||||
],
|
||||
)
|
||||
if not file_list:
|
||||
file_list.append(
|
||||
[InlineKeyboardButton("ᴇᴍᴘᴛʏ", callback_data="N/A")],
|
||||
)
|
||||
file_list.append([InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")])
|
||||
await message.reply_photo(photo=Telegram.IMAGE_FILEID,
|
||||
caption="Total files: {}".format(total_files),
|
||||
reply_markup=InlineKeyboardMarkup(file_list))
|
||||
|
||||
|
||||
95
FileStream/bot/plugins/stream.py
Normal file
95
FileStream/bot/plugins/stream.py
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
import asyncio
|
||||
from FileStream.bot import FileStream, multi_clients
|
||||
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
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.utils.file_properties import get_file_ids, get_file_info
|
||||
from FileStream.config import Telegram
|
||||
from pyrogram import filters, Client
|
||||
from pyrogram.errors import FloodWait
|
||||
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from pyrogram.enums.parse_mode import ParseMode
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
|
||||
@FileStream.on_message(
|
||||
filters.private
|
||||
& (
|
||||
filters.document
|
||||
| filters.video
|
||||
| filters.video_note
|
||||
| filters.audio
|
||||
| filters.voice
|
||||
| filters.animation
|
||||
| filters.photo
|
||||
),
|
||||
group=4,
|
||||
)
|
||||
async def private_receive_handler(bot: Client, message: Message):
|
||||
if not await is_user_authorized(message):
|
||||
return
|
||||
if await is_user_banned(message):
|
||||
return
|
||||
|
||||
await is_user_exist(bot, message)
|
||||
if Telegram.FORCE_UPDATES_CHANNEL:
|
||||
if not await is_user_joined(bot, message):
|
||||
return
|
||||
try:
|
||||
inserted_id = await db.add_file(get_file_info(message))
|
||||
await get_file_ids(False, inserted_id, multi_clients, message)
|
||||
reply_markup, stream_text = await gen_link(_id=inserted_id)
|
||||
await message.reply_text(
|
||||
text=stream_text,
|
||||
parse_mode=ParseMode.HTML,
|
||||
disable_web_page_preview=True,
|
||||
reply_markup=reply_markup,
|
||||
quote=True
|
||||
)
|
||||
except FloodWait as e:
|
||||
print(f"Sleeping for {str(e.value)}s")
|
||||
await asyncio.sleep(e.value)
|
||||
await bot.send_message(chat_id=Telegram.LOG_CHANNEL,
|
||||
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)}`",
|
||||
disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN)
|
||||
|
||||
|
||||
@FileStream.on_message(
|
||||
filters.channel
|
||||
& ~filters.forwarded
|
||||
& ~filters.media_group
|
||||
& (
|
||||
filters.document
|
||||
| filters.video
|
||||
| filters.video_note
|
||||
| filters.audio
|
||||
| filters.voice
|
||||
| filters.photo
|
||||
)
|
||||
)
|
||||
async def channel_receive_handler(bot: Client, message: Message):
|
||||
if await is_channel_banned(bot, message):
|
||||
return
|
||||
await is_channel_exist(bot, message)
|
||||
|
||||
try:
|
||||
inserted_id = await db.add_file(get_file_info(message))
|
||||
await get_file_ids(False, inserted_id, multi_clients, message)
|
||||
reply_markup, stream_link = await gen_link(_id=inserted_id)
|
||||
await bot.edit_message_reply_markup(
|
||||
chat_id=message.chat.id,
|
||||
message_id=message.id,
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥",
|
||||
url=f"https://t.me/{FileStream.username}?start=stream_{str(inserted_id)}")]])
|
||||
)
|
||||
|
||||
except FloodWait as w:
|
||||
print(f"Sleeping for {str(w.x)}s")
|
||||
await asyncio.sleep(w.x)
|
||||
await bot.send_message(chat_id=Telegram.LOG_CHANNEL,
|
||||
text=f"ɢᴏᴛ ғʟᴏᴏᴅᴡᴀɪᴛ ᴏғ {str(w.x)}s FROM {message.chat.title}\n\n**CHANNEL ID:** `{str(message.chat.id)}`",
|
||||
disable_web_page_preview=True)
|
||||
except Exception as e:
|
||||
await bot.send_message(chat_id=Telegram.LOG_CHANNEL, text=f"**#EʀʀᴏʀTʀᴀᴄᴋᴇʙᴀᴄᴋ:** `{e}`",
|
||||
disable_web_page_preview=True)
|
||||
print(f"Cᴀɴ'ᴛ Eᴅɪᴛ Bʀᴏᴀᴅᴄᴀsᴛ Mᴇssᴀɢᴇ!\nEʀʀᴏʀ: **Gɪᴠᴇ ᴍᴇ ᴇᴅɪᴛ ᴘᴇʀᴍɪssɪᴏɴ ɪɴ ᴜᴘᴅᴀᴛᴇs ᴀɴᴅ ʙɪɴ Cʜᴀɴɴᴇʟ!{e}**")
|
||||
39
FileStream/config.py
Normal file
39
FileStream/config.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from os import environ as env
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
class Telegram:
|
||||
API_ID = int(env.get("API_ID"))
|
||||
API_HASH = str(env.get("API_HASH"))
|
||||
BOT_TOKEN = str(env.get("BOT_TOKEN"))
|
||||
OWNER_ID = int(env.get('OWNER_ID', '7978482443'))
|
||||
WORKERS = int(env.get("WORKERS", "6")) # 6 workers = 6 commands at once
|
||||
DATABASE_URL = str(env.get('DATABASE_URL'))
|
||||
UPDATES_CHANNEL = str(env.get('UPDATES_CHANNEL', "Telegram"))
|
||||
SESSION_NAME = str(env.get('SESSION_NAME', 'FileStream'))
|
||||
FORCE_UPDATES_CHANNEL = env.get('FORCE_UPDATES_CHANNEL', False)
|
||||
FORCE_UPDATES_CHANNEL = True if str(FORCE_UPDATES_CHANNEL).lower() == "true" else False
|
||||
SLEEP_THRESHOLD = int(env.get("SLEEP_THRESHOLD", "60"))
|
||||
IMAGE_FILEID = env.get('IMAGE_FILEID', "https://telegra.ph/file/5bb9935be0229adf98b73.jpg")
|
||||
MULTI_CLIENT = False
|
||||
LOG_CHANNEL = int(
|
||||
env.get("BIN_CHANNEL", None)
|
||||
) # you NEED to use a CHANNEL when you're using MULTI_CLIENT
|
||||
MODE = env.get("MODE", "primary")
|
||||
SECONDARY = True if MODE.lower() == "secondary" else False
|
||||
AUTH_USERS = list(set(int(x) for x in str(env.get("AUTH_USERS", "")).split()))
|
||||
|
||||
class Server:
|
||||
PORT = int(env.get("PORT", 8080))
|
||||
BIND_ADDRESS = str(env.get("BIND_ADDRESS", "0.0.0.0"))
|
||||
PING_INTERVAL = int(env.get("PING_INTERVAL", "1200")) # 20 minutes
|
||||
HAS_SSL = str(env.get("HAS_SSL", "0").lower()) in ("1", "true", "t", "yes", "y")
|
||||
NO_PORT = str(env.get("NO_PORT", "0").lower()) in ("1", "true", "t", "yes", "y")
|
||||
FQDN = str(env.get("FQDN", BIND_ADDRESS))
|
||||
URL = "http{}://{}{}/".format(
|
||||
"s" if HAS_SSL else "", FQDN, "" if NO_PORT else ":" + str(PORT)
|
||||
)
|
||||
KEEP_ALIVE = str(env.get("KEEP_ALIVE", "0").lower()) in ("1", "true", "t", "yes", "y")
|
||||
|
||||
|
||||
7
FileStream/server/__init__.py
Normal file
7
FileStream/server/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from aiohttp import web
|
||||
from .stream_routes import routes
|
||||
|
||||
def web_server():
|
||||
web_app = web.Application(client_max_size=30000000)
|
||||
web_app.add_routes(routes)
|
||||
return web_app
|
||||
5
FileStream/server/exceptions.py
Normal file
5
FileStream/server/exceptions.py
Normal file
@@ -0,0 +1,5 @@
|
||||
class InvalidHash(Exception):
|
||||
message = "Invalid hash"
|
||||
|
||||
class FIleNotFound(Exception):
|
||||
message = "File not found"
|
||||
136
FileStream/server/stream_routes.py
Normal file
136
FileStream/server/stream_routes.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import time
|
||||
import math
|
||||
import logging
|
||||
import mimetypes
|
||||
import traceback
|
||||
from aiohttp import web
|
||||
from aiohttp.http_exceptions import BadStatusLine
|
||||
from FileStream.bot import multi_clients, work_loads, FileStream
|
||||
from FileStream.config import Telegram, Server
|
||||
from FileStream.server.exceptions import FIleNotFound, InvalidHash
|
||||
from FileStream import utils, StartTime, __version__
|
||||
from FileStream.utils.render_template import render_page
|
||||
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
@routes.get("/status", allow_head=True)
|
||||
async def root_route_handler(_):
|
||||
return web.json_response(
|
||||
{
|
||||
"server_status": "running",
|
||||
"uptime": utils.get_readable_time(time.time() - StartTime),
|
||||
"telegram_bot": "@" + FileStream.username,
|
||||
"connected_bots": len(multi_clients),
|
||||
"loads": dict(
|
||||
("bot" + str(c + 1), l)
|
||||
for c, (_, l) in enumerate(
|
||||
sorted(work_loads.items(), key=lambda x: x[1], reverse=True)
|
||||
)
|
||||
),
|
||||
"version": __version__,
|
||||
}
|
||||
)
|
||||
|
||||
@routes.get("/watch/{path}", allow_head=True)
|
||||
async def stream_handler(request: web.Request):
|
||||
try:
|
||||
path = request.match_info["path"]
|
||||
return web.Response(text=await render_page(path), content_type='text/html')
|
||||
except InvalidHash as e:
|
||||
raise web.HTTPForbidden(text=e.message)
|
||||
except FIleNotFound as e:
|
||||
raise web.HTTPNotFound(text=e.message)
|
||||
except (AttributeError, BadStatusLine, ConnectionResetError):
|
||||
pass
|
||||
|
||||
|
||||
@routes.get("/dl/{path}", allow_head=True)
|
||||
async def stream_handler(request: web.Request):
|
||||
try:
|
||||
path = request.match_info["path"]
|
||||
return await media_streamer(request, path)
|
||||
except InvalidHash as e:
|
||||
raise web.HTTPForbidden(text=e.message)
|
||||
except FIleNotFound as e:
|
||||
raise web.HTTPNotFound(text=e.message)
|
||||
except (AttributeError, BadStatusLine, ConnectionResetError):
|
||||
pass
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logging.critical(e.with_traceback(None))
|
||||
logging.debug(traceback.format_exc())
|
||||
raise web.HTTPInternalServerError(text=str(e))
|
||||
|
||||
class_cache = {}
|
||||
|
||||
async def media_streamer(request: web.Request, db_id: str):
|
||||
range_header = request.headers.get("Range", 0)
|
||||
|
||||
index = min(work_loads, key=work_loads.get)
|
||||
faster_client = multi_clients[index]
|
||||
|
||||
if Telegram.MULTI_CLIENT:
|
||||
logging.info(f"Client {index} is now serving {request.headers.get('X-FORWARDED-FOR',request.remote)}")
|
||||
|
||||
if faster_client in class_cache:
|
||||
tg_connect = class_cache[faster_client]
|
||||
logging.debug(f"Using cached ByteStreamer object for client {index}")
|
||||
else:
|
||||
logging.debug(f"Creating new ByteStreamer object for client {index}")
|
||||
tg_connect = utils.ByteStreamer(faster_client)
|
||||
class_cache[faster_client] = tg_connect
|
||||
logging.debug("before calling get_file_properties")
|
||||
file_id = await tg_connect.get_file_properties(db_id, multi_clients)
|
||||
logging.debug("after calling get_file_properties")
|
||||
|
||||
file_size = file_id.file_size
|
||||
|
||||
if range_header:
|
||||
from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
|
||||
from_bytes = int(from_bytes)
|
||||
until_bytes = int(until_bytes) if until_bytes else file_size - 1
|
||||
else:
|
||||
from_bytes = request.http_range.start or 0
|
||||
until_bytes = (request.http_range.stop or file_size) - 1
|
||||
|
||||
if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes):
|
||||
return web.Response(
|
||||
status=416,
|
||||
body="416: Range not satisfiable",
|
||||
headers={"Content-Range": f"bytes */{file_size}"},
|
||||
)
|
||||
|
||||
chunk_size = 1024 * 1024
|
||||
until_bytes = min(until_bytes, file_size - 1)
|
||||
|
||||
offset = from_bytes - (from_bytes % chunk_size)
|
||||
first_part_cut = from_bytes - offset
|
||||
last_part_cut = until_bytes % chunk_size + 1
|
||||
|
||||
req_length = until_bytes - from_bytes + 1
|
||||
part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size)
|
||||
body = tg_connect.yield_file(
|
||||
file_id, index, offset, first_part_cut, last_part_cut, part_count, chunk_size
|
||||
)
|
||||
|
||||
mime_type = file_id.mime_type
|
||||
file_name = utils.get_name(file_id)
|
||||
disposition = "attachment"
|
||||
|
||||
if not mime_type:
|
||||
mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
|
||||
|
||||
# if "video/" in mime_type or "audio/" in mime_type:
|
||||
# disposition = "inline"
|
||||
|
||||
return web.Response(
|
||||
status=206 if range_header else 200,
|
||||
body=body,
|
||||
headers={
|
||||
"Content-Type": f"{mime_type}",
|
||||
"Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
|
||||
"Content-Length": str(req_length),
|
||||
"Content-Disposition": f'{disposition}; filename="{file_name}"',
|
||||
"Accept-Ranges": "bytes",
|
||||
},
|
||||
)
|
||||
56
FileStream/template/dl.html
Normal file
56
FileStream/template/dl.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta property="og:image" content="https://www.flaticon.com/premium-icon/icons/svg/2626/2626281.svg" itemprop="thumbnailUrl">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>%s</title>
|
||||
<link rel="stylesheet" type='text/css' href="https://drive.google.com/uc?export=view&id=1pVLG4gZy7jdow3sO-wFS06aP_A9QX0O6">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Delius">
|
||||
<!-- <link rel="stylesheet" href="./style.css"> -->
|
||||
|
||||
</head>
|
||||
|
||||
<body class='cyber'>
|
||||
<header>
|
||||
<div class="toogle"></div>
|
||||
<div id="file-name" class="cyber">
|
||||
%s
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<a href=%s>
|
||||
<button class="cybr-btn">
|
||||
Download
|
||||
<span aria-hidden>_</span>
|
||||
<span aria-hidden class="cybr-btn__glitch">_404 Error</span>
|
||||
<span aria-hidden class="cybr-btn__tag">_%s</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span id="fork-text">Fork me on</span>
|
||||
<span>
|
||||
<a href="https://github.com/DeekshithSH/FileStreamBot" id='github-logo'>
|
||||
<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>
|
||||
</a>
|
||||
</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const body = document.querySelector('body');
|
||||
const title = document.querySelector('#file-name');
|
||||
const footer = document.querySelector('footer');
|
||||
const toogle = document.querySelector('.toogle');
|
||||
toogle.onclick = () => {
|
||||
body.classList.toggle('dark')
|
||||
footer.classList.toggle('dark')
|
||||
title.classList.toggle('dark')
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
154
FileStream/template/stream.html
Normal file
154
FileStream/template/stream.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>streamHeading</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-Frame-Options" content="deny">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||
|
||||
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#stream-media {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#error-message {
|
||||
color: red;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.plyr__video-wrapper .plyr-download-button{
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.plyr__volume {
|
||||
max-width: initial;
|
||||
min-width: initial;
|
||||
width: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.plyr__video-wrapper .plyr-share-button{
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.plyr__video-wrapper .plyr-download-button:hover,
|
||||
.plyr__video-wrapper .plyr-share-button:hover{
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.plyr__video-wrapper .plyr-download-button:before {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
content: "\f019";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.plyr__video-wrapper .plyr-share-button:before {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
content: "\f064";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.plyr, .plyr__video-wrapper, .plyr__video-embed iframe {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<video id="stream-media" controls preload="auto">
|
||||
<source src="" type="">
|
||||
<p class="vjs-no-js">
|
||||
To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
|
||||
</p>
|
||||
</video>
|
||||
|
||||
<div id="error-message"></div>
|
||||
|
||||
<script>
|
||||
var player = new Plyr('#stream-media', {
|
||||
controls:['play-large', 'rewind', 'play', 'fast-forward', 'progress', 'current-time', 'mute', 'settings', 'pip', 'fullscreen'],
|
||||
settings:['speed','loop'],
|
||||
speed:{selected:1,options:[0.25,0.5,0.75,1,1.25,1.5,1.75,2]},
|
||||
seek: 10,
|
||||
keyboard: { focused: true, global: true },
|
||||
});
|
||||
|
||||
var mediaLink = "streamMediaLink";
|
||||
|
||||
if (mediaLink) {
|
||||
document.querySelector('#stream-media source').setAttribute('src', mediaLink);
|
||||
player.restart();
|
||||
|
||||
var downloadButton = document.createElement('div');
|
||||
downloadButton.className = 'plyr-download-button';
|
||||
|
||||
downloadButton.onclick = function() {
|
||||
event.stopPropagation();
|
||||
var link = document.createElement('a');
|
||||
link.href = mediaLink;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
player.elements.container.querySelector('.plyr__video-wrapper').appendChild(downloadButton);
|
||||
|
||||
var shareButton = document.createElement('div');
|
||||
shareButton.className = 'plyr-share-button';
|
||||
|
||||
shareButton.onclick = function() {
|
||||
event.stopPropagation();
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: "Play",
|
||||
url: window.location.href
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
player.elements.container.querySelector('.plyr__video-wrapper').appendChild(shareButton);
|
||||
|
||||
} else {
|
||||
document.getElementById('error-message').textContent = 'Error: Media URL not provided';
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
4
FileStream/utils/__init__.py
Normal file
4
FileStream/utils/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .keepalive import ping_server
|
||||
from .time_format import get_readable_time
|
||||
from .file_properties import get_name, get_file_ids
|
||||
from .custom_dl import ByteStreamer
|
||||
177
FileStream/utils/bot_utils.py
Normal file
177
FileStream/utils/bot_utils.py
Normal file
@@ -0,0 +1,177 @@
|
||||
from pyrogram.errors import UserNotParticipant
|
||||
from pyrogram.enums.parse_mode import ParseMode
|
||||
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
|
||||
from FileStream.utils.translation import LANG
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.utils.human_readable import humanbytes
|
||||
from FileStream.config import Telegram, Server
|
||||
from FileStream.bot import FileStream
|
||||
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
|
||||
async def is_user_joined(bot, message: Message):
|
||||
try:
|
||||
user = await bot.get_chat_member(Telegram.UPDATES_CHANNEL, message.chat.id)
|
||||
if user.status == "BANNED":
|
||||
await message.reply_text(
|
||||
text=LANG.BAN_TEXT.format(Telegram.OWNER_ID),
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
return False
|
||||
except UserNotParticipant:
|
||||
await message.reply_text(
|
||||
text = "<i>Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴍᴇ 🔐</i>",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
[[
|
||||
InlineKeyboardButton("Jᴏɪɴ ɴᴏᴡ 🔓", url=f"https://t.me/{Telegram.UPDATES_CHANNEL}")
|
||||
]]
|
||||
),
|
||||
parse_mode=ParseMode.HTML
|
||||
)
|
||||
return False
|
||||
except Exception:
|
||||
await message.reply_text(
|
||||
text = f"<i>Sᴏᴍᴇᴛʜɪɴɢ ᴡʀᴏɴɢ ᴄᴏɴᴛᴀᴄᴛ ᴍʏ ᴅᴇᴠᴇʟᴏᴘᴇʀ</i> <b><a href='https://t.me/{Telegram.UPDATES_CHANNEL}'>[ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]</a></b>",
|
||||
parse_mode=ParseMode.HTML,
|
||||
disable_web_page_preview=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
#---------------------[ PRIVATE GEN LINK + CALLBACK ]---------------------#
|
||||
|
||||
async def gen_link(_id):
|
||||
file_info = await db.get_file(_id)
|
||||
file_name = file_info['file_name']
|
||||
file_size = humanbytes(file_info['file_size'])
|
||||
mime_type = file_info['mime_type']
|
||||
|
||||
page_link = f"{Server.URL}watch/{_id}"
|
||||
stream_link = f"{Server.URL}dl/{_id}"
|
||||
file_link = f"https://t.me/{FileStream.username}?start=file_{_id}"
|
||||
|
||||
if "video" in mime_type:
|
||||
stream_text = LANG.STREAM_TEXT.format(file_name, file_size, stream_link, page_link, file_link)
|
||||
reply_markup = InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
||||
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", url=file_link), InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelpvt_{_id}")],
|
||||
[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")]
|
||||
]
|
||||
)
|
||||
else:
|
||||
stream_text = LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, file_link)
|
||||
reply_markup = InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)],
|
||||
[InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", url=file_link), InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelpvt_{_id}")],
|
||||
[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")]
|
||||
]
|
||||
)
|
||||
return reply_markup, stream_text
|
||||
|
||||
#---------------------[ GEN STREAM LINKS FOR CHANNEL ]---------------------#
|
||||
|
||||
async def gen_linkx(m:Message , _id, name: list):
|
||||
file_info = await db.get_file(_id)
|
||||
file_name = file_info['file_name']
|
||||
mime_type = file_info['mime_type']
|
||||
file_size = humanbytes(file_info['file_size'])
|
||||
|
||||
page_link = f"{Server.URL}watch/{_id}"
|
||||
stream_link = f"{Server.URL}dl/{_id}"
|
||||
file_link = f"https://t.me/{FileStream.username}?start=file_{_id}"
|
||||
|
||||
if "video" in mime_type:
|
||||
stream_text= LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, page_link)
|
||||
reply_markup = InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)]
|
||||
]
|
||||
)
|
||||
else:
|
||||
stream_text= LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, file_link)
|
||||
reply_markup = InlineKeyboardMarkup(
|
||||
[
|
||||
[InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)]
|
||||
]
|
||||
)
|
||||
return reply_markup, stream_text
|
||||
|
||||
#---------------------[ USER BANNED ]---------------------#
|
||||
|
||||
async def is_user_banned(message):
|
||||
if await db.is_user_banned(message.from_user.id):
|
||||
await message.reply_text(
|
||||
text=LANG.BAN_TEXT.format(Telegram.OWNER_ID),
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
#---------------------[ CHANNEL BANNED ]---------------------#
|
||||
|
||||
async def is_channel_banned(bot, message):
|
||||
if await db.is_user_banned(message.chat.id):
|
||||
await bot.edit_message_reply_markup(
|
||||
chat_id=message.chat.id,
|
||||
message_id=message.id,
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton(f"ᴄʜᴀɴɴᴇʟ ɪs ʙᴀɴɴᴇᴅ", callback_data="N/A")]])
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
#---------------------[ USER AUTH ]---------------------#
|
||||
|
||||
async def is_user_authorized(message):
|
||||
if hasattr(Telegram, 'AUTH_USERS') and Telegram.AUTH_USERS:
|
||||
user_id = message.from_user.id
|
||||
|
||||
if user_id == Telegram.OWNER_ID:
|
||||
return True
|
||||
|
||||
if not (user_id in Telegram.AUTH_USERS):
|
||||
await message.reply_text(
|
||||
text="You are not authorized to use this bot.",
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
#---------------------[ USER EXIST ]---------------------#
|
||||
|
||||
async def is_user_exist(bot, message):
|
||||
if not bool(await db.get_user(message.from_user.id)):
|
||||
await db.add_user(message.from_user.id)
|
||||
await bot.send_message(
|
||||
Telegram.LOG_CHANNEL,
|
||||
f"**#NᴇᴡUsᴇʀ**\n**⬩ ᴜsᴇʀ ɴᴀᴍᴇ :** [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n**⬩ ᴜsᴇʀ ɪᴅ :** `{message.from_user.id}`"
|
||||
)
|
||||
|
||||
async def is_channel_exist(bot, message):
|
||||
if not bool(await db.get_user(message.chat.id)):
|
||||
await db.add_user(message.chat.id)
|
||||
members = await bot.get_chat_members_count(message.chat.id)
|
||||
await bot.send_message(
|
||||
Telegram.LOG_CHANNEL,
|
||||
f"**#NᴇᴡCʜᴀɴɴᴇʟ** \n**⬩ ᴄʜᴀᴛ ɴᴀᴍᴇ :** `{message.chat.title}`\n**⬩ ᴄʜᴀᴛ ɪᴅ :** `{message.chat.id}`\n**⬩ ᴛᴏᴛᴀʟ ᴍᴇᴍʙᴇʀs :** `{members}`"
|
||||
)
|
||||
|
||||
async def verify_user(bot, message):
|
||||
if not await is_user_authorized(message):
|
||||
return False
|
||||
|
||||
if await is_user_banned(message):
|
||||
return False
|
||||
|
||||
await is_user_exist(bot, message)
|
||||
|
||||
if Telegram.FORCE_UPDATES_CHANNEL:
|
||||
if not await is_user_joined(bot, message):
|
||||
return False
|
||||
|
||||
return True
|
||||
19
FileStream/utils/broadcast_helper.py
Normal file
19
FileStream/utils/broadcast_helper.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid
|
||||
|
||||
async def send_msg(user_id, message):
|
||||
try:
|
||||
await message.copy(chat_id=user_id)
|
||||
return 200, None
|
||||
except FloodWait as e:
|
||||
await asyncio.sleep(e.value)
|
||||
return send_msg(user_id, message)
|
||||
except InputUserDeactivated:
|
||||
return 400, f"{user_id} : deactivated\n"
|
||||
except UserIsBlocked:
|
||||
return 400, f"{user_id} : blocked the bot\n"
|
||||
except PeerIdInvalid:
|
||||
return 400, f"{user_id} : user id invalid\n"
|
||||
except Exception as e:
|
||||
return 500, f"{user_id} : {traceback.format_exc()}\n"
|
||||
214
FileStream/utils/custom_dl.py
Normal file
214
FileStream/utils/custom_dl.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Union
|
||||
from FileStream.bot import work_loads
|
||||
from pyrogram import Client, utils, raw
|
||||
from .file_properties import get_file_ids
|
||||
from pyrogram.session import Session, Auth
|
||||
from pyrogram.errors import AuthBytesInvalid
|
||||
from pyrogram.file_id import FileId, FileType, ThumbnailSource
|
||||
from pyrogram.types import Message
|
||||
|
||||
class ByteStreamer:
|
||||
def __init__(self, client: Client):
|
||||
self.clean_timer = 30 * 60
|
||||
self.client: Client = client
|
||||
self.cached_file_ids: Dict[str, FileId] = {}
|
||||
asyncio.create_task(self.clean_cache())
|
||||
|
||||
async def get_file_properties(self, db_id: str, multi_clients) -> FileId:
|
||||
"""
|
||||
Returns the properties of a media of a specific message in a FIleId class.
|
||||
if the properties are cached, then it'll return the cached results.
|
||||
or it'll generate the properties from the Message ID and cache them.
|
||||
"""
|
||||
if not db_id in self.cached_file_ids:
|
||||
logging.debug("Before Calling generate_file_properties")
|
||||
await self.generate_file_properties(db_id, multi_clients)
|
||||
logging.debug(f"Cached file properties for file with ID {db_id}")
|
||||
return self.cached_file_ids[db_id]
|
||||
|
||||
async def generate_file_properties(self, db_id: str, multi_clients) -> FileId:
|
||||
"""
|
||||
Generates the properties of a media file on a specific message.
|
||||
returns ths properties in a FIleId class.
|
||||
"""
|
||||
logging.debug("Before calling get_file_ids")
|
||||
file_id = await get_file_ids(self.client, db_id, multi_clients, Message)
|
||||
logging.debug(f"Generated file ID and Unique ID for file with ID {db_id}")
|
||||
self.cached_file_ids[db_id] = file_id
|
||||
logging.debug(f"Cached media file with ID {db_id}")
|
||||
return self.cached_file_ids[db_id]
|
||||
|
||||
async def generate_media_session(self, client: Client, file_id: FileId) -> Session:
|
||||
"""
|
||||
Generates the media session for the DC that contains the media file.
|
||||
This is required for getting the bytes from Telegram servers.
|
||||
"""
|
||||
|
||||
media_session = client.media_sessions.get(file_id.dc_id, None)
|
||||
|
||||
if media_session is None:
|
||||
if file_id.dc_id != await client.storage.dc_id():
|
||||
media_session = Session(
|
||||
client,
|
||||
file_id.dc_id,
|
||||
await Auth(
|
||||
client, file_id.dc_id, await client.storage.test_mode()
|
||||
).create(),
|
||||
await client.storage.test_mode(),
|
||||
is_media=True,
|
||||
)
|
||||
await media_session.start()
|
||||
|
||||
for _ in range(6):
|
||||
exported_auth = await client.invoke(
|
||||
raw.functions.auth.ExportAuthorization(dc_id=file_id.dc_id)
|
||||
)
|
||||
|
||||
try:
|
||||
await media_session.invoke(
|
||||
raw.functions.auth.ImportAuthorization(
|
||||
id=exported_auth.id, bytes=exported_auth.bytes
|
||||
)
|
||||
)
|
||||
break
|
||||
except AuthBytesInvalid:
|
||||
logging.debug(
|
||||
f"Invalid authorization bytes for DC {file_id.dc_id}"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
await media_session.stop()
|
||||
raise AuthBytesInvalid
|
||||
else:
|
||||
media_session = Session(
|
||||
client,
|
||||
file_id.dc_id,
|
||||
await client.storage.auth_key(),
|
||||
await client.storage.test_mode(),
|
||||
is_media=True,
|
||||
)
|
||||
await media_session.start()
|
||||
logging.debug(f"Created media session for DC {file_id.dc_id}")
|
||||
client.media_sessions[file_id.dc_id] = media_session
|
||||
else:
|
||||
logging.debug(f"Using cached media session for DC {file_id.dc_id}")
|
||||
return media_session
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def get_location(file_id: FileId) -> Union[raw.types.InputPhotoFileLocation,
|
||||
raw.types.InputDocumentFileLocation,
|
||||
raw.types.InputPeerPhotoFileLocation,]:
|
||||
"""
|
||||
Returns the file location for the media file.
|
||||
"""
|
||||
file_type = file_id.file_type
|
||||
|
||||
if file_type == FileType.CHAT_PHOTO:
|
||||
if file_id.chat_id > 0:
|
||||
peer = raw.types.InputPeerUser(
|
||||
user_id=file_id.chat_id, access_hash=file_id.chat_access_hash
|
||||
)
|
||||
else:
|
||||
if file_id.chat_access_hash == 0:
|
||||
peer = raw.types.InputPeerChat(chat_id=-file_id.chat_id)
|
||||
else:
|
||||
peer = raw.types.InputPeerChannel(
|
||||
channel_id=utils.get_channel_id(file_id.chat_id),
|
||||
access_hash=file_id.chat_access_hash,
|
||||
)
|
||||
|
||||
location = raw.types.InputPeerPhotoFileLocation(
|
||||
peer=peer,
|
||||
volume_id=file_id.volume_id,
|
||||
local_id=file_id.local_id,
|
||||
big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG,
|
||||
)
|
||||
elif file_type == FileType.PHOTO:
|
||||
location = raw.types.InputPhotoFileLocation(
|
||||
id=file_id.media_id,
|
||||
access_hash=file_id.access_hash,
|
||||
file_reference=file_id.file_reference,
|
||||
thumb_size=file_id.thumbnail_size,
|
||||
)
|
||||
else:
|
||||
location = raw.types.InputDocumentFileLocation(
|
||||
id=file_id.media_id,
|
||||
access_hash=file_id.access_hash,
|
||||
file_reference=file_id.file_reference,
|
||||
thumb_size=file_id.thumbnail_size,
|
||||
)
|
||||
return location
|
||||
|
||||
async def yield_file(
|
||||
self,
|
||||
file_id: FileId,
|
||||
index: int,
|
||||
offset: int,
|
||||
first_part_cut: int,
|
||||
last_part_cut: int,
|
||||
part_count: int,
|
||||
chunk_size: int,
|
||||
) -> Union[str, None]:
|
||||
"""
|
||||
Custom generator that yields the bytes of the media file.
|
||||
Modded from <https://github.com/eyaadh/megadlbot_oss/blob/master/mega/telegram/utils/custom_download.py#L20>
|
||||
Thanks to Eyaadh <https://github.com/eyaadh>
|
||||
"""
|
||||
client = self.client
|
||||
work_loads[index] += 1
|
||||
logging.debug(f"Starting to yielding file with client {index}.")
|
||||
media_session = await self.generate_media_session(client, file_id)
|
||||
|
||||
current_part = 1
|
||||
|
||||
location = await self.get_location(file_id)
|
||||
|
||||
try:
|
||||
r = await media_session.invoke(
|
||||
raw.functions.upload.GetFile(
|
||||
location=location, offset=offset, limit=chunk_size
|
||||
),
|
||||
)
|
||||
if isinstance(r, raw.types.upload.File):
|
||||
while True:
|
||||
chunk = r.bytes
|
||||
if not chunk:
|
||||
break
|
||||
elif part_count == 1:
|
||||
yield chunk[first_part_cut:last_part_cut]
|
||||
elif current_part == 1:
|
||||
yield chunk[first_part_cut:]
|
||||
elif current_part == part_count:
|
||||
yield chunk[:last_part_cut]
|
||||
else:
|
||||
yield chunk
|
||||
|
||||
current_part += 1
|
||||
offset += chunk_size
|
||||
|
||||
if current_part > part_count:
|
||||
break
|
||||
|
||||
r = await media_session.invoke(
|
||||
raw.functions.upload.GetFile(
|
||||
location=location, offset=offset, limit=chunk_size
|
||||
),
|
||||
)
|
||||
except (TimeoutError, AttributeError):
|
||||
pass
|
||||
finally:
|
||||
logging.debug(f"Finished yielding file with {current_part} parts.")
|
||||
work_loads[index] -= 1
|
||||
|
||||
|
||||
async def clean_cache(self) -> None:
|
||||
"""
|
||||
function to clean the cache to reduce memory usage
|
||||
"""
|
||||
while True:
|
||||
await asyncio.sleep(self.clean_timer)
|
||||
self.cached_file_ids.clear()
|
||||
logging.debug("Cleaned the cache")
|
||||
136
FileStream/utils/database.py
Normal file
136
FileStream/utils/database.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import pymongo
|
||||
import time
|
||||
import motor.motor_asyncio
|
||||
from bson.objectid import ObjectId
|
||||
from bson.errors import InvalidId
|
||||
from FileStream.server.exceptions import FIleNotFound
|
||||
|
||||
class Database:
|
||||
def __init__(self, uri, database_name):
|
||||
self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
|
||||
self.db = self._client[database_name]
|
||||
self.col = self.db.users
|
||||
self.black = self.db.blacklist
|
||||
self.file = self.db.file
|
||||
|
||||
#---------------------[ NEW USER ]---------------------#
|
||||
def new_user(self, id):
|
||||
return dict(
|
||||
id=id,
|
||||
join_date=time.time(),
|
||||
agreed_to_tos=False,
|
||||
Links=0,
|
||||
Plan="Free"
|
||||
)
|
||||
|
||||
# ---------------------[ ADD USER ]---------------------#
|
||||
async def add_user(self, id):
|
||||
user = self.new_user(id)
|
||||
await self.col.insert_one(user)
|
||||
|
||||
# ---------------------[ GET USER ]---------------------#
|
||||
async def get_user(self, id):
|
||||
user = await self.col.find_one({'id': int(id)})
|
||||
return user
|
||||
|
||||
# ---------------------[ CHECK USER ]---------------------#
|
||||
async def total_users_count(self):
|
||||
count = await self.col.count_documents({})
|
||||
return count
|
||||
|
||||
async def get_all_users(self):
|
||||
all_users = self.col.find({})
|
||||
return all_users
|
||||
|
||||
# ---------------------[ REMOVE USER ]---------------------#
|
||||
async def delete_user(self, user_id):
|
||||
await self.col.delete_many({'id': int(user_id)})
|
||||
|
||||
# ---------------------[ BAN, UNBAN USER ]---------------------#
|
||||
def black_user(self, id):
|
||||
return dict(
|
||||
id=id,
|
||||
ban_date=time.time()
|
||||
)
|
||||
|
||||
async def ban_user(self, id):
|
||||
user = self.black_user(id)
|
||||
await self.black.insert_one(user)
|
||||
|
||||
async def unban_user(self, id):
|
||||
await self.black.delete_one({'id': int(id)})
|
||||
|
||||
async def is_user_banned(self, id):
|
||||
user = await self.black.find_one({'id': int(id)})
|
||||
return True if user else False
|
||||
|
||||
async def total_banned_users_count(self):
|
||||
count = await self.black.count_documents({})
|
||||
return count
|
||||
|
||||
# ---------------------[ ADD FILE TO DB ]---------------------#
|
||||
async def add_file(self, file_info):
|
||||
file_info["time"] = time.time()
|
||||
fetch_old = await self.get_file_by_fileuniqueid(file_info["user_id"], file_info["file_unique_id"])
|
||||
if fetch_old:
|
||||
return fetch_old["_id"]
|
||||
await self.count_links(file_info["user_id"], "+")
|
||||
return (await self.file.insert_one(file_info)).inserted_id
|
||||
|
||||
# ---------------------[ FIND FILE IN DB ]---------------------#
|
||||
async def find_files(self, user_id, range):
|
||||
user_files=self.file.find({"user_id": user_id})
|
||||
user_files.skip(range[0] - 1)
|
||||
user_files.limit(range[1] - range[0] + 1)
|
||||
user_files.sort('_id', pymongo.DESCENDING)
|
||||
total_files = await self.file.count_documents({"user_id": user_id})
|
||||
return user_files, total_files
|
||||
|
||||
async def get_file(self, _id):
|
||||
try:
|
||||
file_info=await self.file.find_one({"_id": ObjectId(_id)})
|
||||
if not file_info:
|
||||
raise FIleNotFound
|
||||
return file_info
|
||||
except InvalidId:
|
||||
raise FIleNotFound
|
||||
|
||||
async def get_file_by_fileuniqueid(self, id, file_unique_id, many=False):
|
||||
if many:
|
||||
return self.file.find({"file_unique_id": file_unique_id})
|
||||
else:
|
||||
file_info=await self.file.find_one({"user_id": id, "file_unique_id": file_unique_id})
|
||||
if file_info:
|
||||
return file_info
|
||||
return False
|
||||
|
||||
# ---------------------[ TOTAL FILES ]---------------------#
|
||||
async def total_files(self, id=None):
|
||||
if id:
|
||||
return await self.file.count_documents({"user_id": id})
|
||||
return await self.file.count_documents({})
|
||||
|
||||
# ---------------------[ DELETE FILES ]---------------------#
|
||||
async def delete_one_file(self, _id):
|
||||
await self.file.delete_one({'_id': ObjectId(_id)})
|
||||
|
||||
# ---------------------[ UPDATE FILES ]---------------------#
|
||||
async def update_file_ids(self, _id, file_ids: dict):
|
||||
await self.file.update_one({"_id": ObjectId(_id)}, {"$set": {"file_ids": file_ids}})
|
||||
|
||||
# ---------------------[ PAID SYS ]---------------------#
|
||||
async def link_available(self, id):
|
||||
user = await self.col.find_one({"id": id})
|
||||
if user.get("Plan") == "Plus":
|
||||
return "Plus"
|
||||
elif user.get("Plan") == "Free":
|
||||
files = await self.file.count_documents({"user_id": id})
|
||||
if files < 11:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def count_links(self, id, operation: str):
|
||||
if operation == "-":
|
||||
await self.col.update_one({"id": id}, {"$inc": {"Links": -1}})
|
||||
elif operation == "+":
|
||||
await self.col.update_one({"id": id}, {"$inc": {"Links": 1}})
|
||||
146
FileStream/utils/file_properties.py
Normal file
146
FileStream/utils/file_properties.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pyrogram import Client
|
||||
from typing import Any, Optional
|
||||
|
||||
from pyrogram.enums import ParseMode, ChatType
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.file_id import FileId
|
||||
from FileStream.bot import FileStream
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.config import Telegram, Server
|
||||
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
|
||||
|
||||
async def get_file_ids(client: Client | bool, db_id: str, multi_clients, message) -> Optional[FileId]:
|
||||
logging.debug("Starting of get_file_ids")
|
||||
file_info = await db.get_file(db_id)
|
||||
if (not "file_ids" in file_info) or not client:
|
||||
logging.debug("Storing file_id of all clients in DB")
|
||||
log_msg = await send_file(FileStream, db_id, file_info['file_id'], message)
|
||||
await db.update_file_ids(db_id, await update_file_id(log_msg.id, multi_clients))
|
||||
logging.debug("Stored file_id of all clients in DB")
|
||||
if not client:
|
||||
return
|
||||
file_info = await db.get_file(db_id)
|
||||
|
||||
file_id_info = file_info.setdefault("file_ids", {})
|
||||
if not str(client.id) in file_id_info:
|
||||
logging.debug("Storing file_id in DB")
|
||||
log_msg = await send_file(FileStream, db_id, file_info['file_id'], message)
|
||||
msg = await client.get_messages(Telegram.LOG_CHANNEL, log_msg.id)
|
||||
media = get_media_from_message(msg)
|
||||
file_id_info[str(client.id)] = getattr(media, "file_id", "")
|
||||
await db.update_file_ids(db_id, file_id_info)
|
||||
logging.debug("Stored file_id in DB")
|
||||
|
||||
logging.debug("Middle of get_file_ids")
|
||||
file_id = FileId.decode(file_id_info[str(client.id)])
|
||||
setattr(file_id, "file_size", file_info['file_size'])
|
||||
setattr(file_id, "mime_type", file_info['mime_type'])
|
||||
setattr(file_id, "file_name", file_info['file_name'])
|
||||
setattr(file_id, "unique_id", file_info['file_unique_id'])
|
||||
logging.debug("Ending of get_file_ids")
|
||||
return file_id
|
||||
|
||||
|
||||
def get_media_from_message(message: "Message") -> Any:
|
||||
media_types = (
|
||||
"audio",
|
||||
"document",
|
||||
"photo",
|
||||
"sticker",
|
||||
"animation",
|
||||
"video",
|
||||
"voice",
|
||||
"video_note",
|
||||
)
|
||||
for attr in media_types:
|
||||
media = getattr(message, attr, None)
|
||||
if media:
|
||||
return media
|
||||
|
||||
|
||||
def get_media_file_size(m):
|
||||
media = get_media_from_message(m)
|
||||
return getattr(media, "file_size", "None")
|
||||
|
||||
|
||||
def get_name(media_msg: Message | FileId) -> str:
|
||||
if isinstance(media_msg, Message):
|
||||
media = get_media_from_message(media_msg)
|
||||
file_name = getattr(media, "file_name", "")
|
||||
|
||||
elif isinstance(media_msg, FileId):
|
||||
file_name = getattr(media_msg, "file_name", "")
|
||||
|
||||
if not file_name:
|
||||
if isinstance(media_msg, Message) and media_msg.media:
|
||||
media_type = media_msg.media.value
|
||||
elif media_msg.file_type:
|
||||
media_type = media_msg.file_type.name.lower()
|
||||
else:
|
||||
media_type = "file"
|
||||
|
||||
formats = {
|
||||
"photo": "jpg", "audio": "mp3", "voice": "ogg",
|
||||
"video": "mp4", "animation": "mp4", "video_note": "mp4",
|
||||
"sticker": "webp"
|
||||
}
|
||||
|
||||
ext = formats.get(media_type)
|
||||
ext = "." + ext if ext else ""
|
||||
|
||||
date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
file_name = f"{media_type}-{date}{ext}"
|
||||
|
||||
return file_name
|
||||
|
||||
|
||||
def get_file_info(message):
|
||||
media = get_media_from_message(message)
|
||||
if message.chat.type == ChatType.PRIVATE:
|
||||
user_idx = message.from_user.id
|
||||
else:
|
||||
user_idx = message.chat.id
|
||||
return {
|
||||
"user_id": user_idx,
|
||||
"file_id": getattr(media, "file_id", ""),
|
||||
"file_unique_id": getattr(media, "file_unique_id", ""),
|
||||
"file_name": get_name(message),
|
||||
"file_size": getattr(media, "file_size", 0),
|
||||
"mime_type": getattr(media, "mime_type", "None/unknown")
|
||||
}
|
||||
|
||||
|
||||
async def update_file_id(msg_id, multi_clients):
|
||||
file_ids = {}
|
||||
for client_id, client in multi_clients.items():
|
||||
log_msg = await client.get_messages(Telegram.LOG_CHANNEL, msg_id)
|
||||
media = get_media_from_message(log_msg)
|
||||
file_ids[str(client.id)] = getattr(media, "file_id", "")
|
||||
|
||||
return file_ids
|
||||
|
||||
|
||||
async def send_file(client: Client, db_id, file_id: str, message):
|
||||
file_caption = message.caption
|
||||
if file_caption is None:
|
||||
file_caption = message.file_name
|
||||
log_msg = await client.send_cached_media(chat_id=Telegram.LOG_CHANNEL, file_id=file_id,
|
||||
caption=f'**{file_caption}**')
|
||||
|
||||
if message.chat.type == ChatType.PRIVATE:
|
||||
await log_msg.reply_text(
|
||||
text=f"**RᴇQᴜᴇꜱᴛᴇᴅ ʙʏ :** [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n**Uꜱᴇʀ ɪᴅ :** `{message.from_user.id}`\n**Fɪʟᴇ ɪᴅ :** `{db_id}`",
|
||||
disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
else:
|
||||
await log_msg.reply_text(
|
||||
text=f"**RᴇQᴜᴇꜱᴛᴇᴅ ʙʏ :** {message.chat.title} \n**Cʜᴀɴɴᴇʟ ɪᴅ :** `{message.chat.id}`\n**Fɪʟᴇ ɪᴅ :** `{db_id}`",
|
||||
disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True)
|
||||
|
||||
return log_msg
|
||||
# return await client.send_cached_media(Telegram.BIN_CHANNEL, file_id)
|
||||
|
||||
10
FileStream/utils/human_readable.py
Normal file
10
FileStream/utils/human_readable.py
Normal file
@@ -0,0 +1,10 @@
|
||||
def humanbytes(size):
|
||||
if not size:
|
||||
return ""
|
||||
power = 2**10
|
||||
n = 0
|
||||
Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'}
|
||||
while size > power:
|
||||
size /= power
|
||||
n += 1
|
||||
return str(round(size, 2)) + " " + Dic_powerN[n] + 'B'
|
||||
20
FileStream/utils/keepalive.py
Normal file
20
FileStream/utils/keepalive.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import aiohttp
|
||||
import traceback
|
||||
from FileStream.config import Server
|
||||
|
||||
async def ping_server():
|
||||
sleep_time = Server.PING_INTERVAL
|
||||
while True:
|
||||
await asyncio.sleep(sleep_time)
|
||||
try:
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=10)
|
||||
) as session:
|
||||
async with session.get(Server.URL) as resp:
|
||||
logging.info("Pinged server with response: {}".format(resp.status))
|
||||
except TimeoutError:
|
||||
logging.warning("Couldn't connect to the site URL..!")
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
25
FileStream/utils/render_template.py
Normal file
25
FileStream/utils/render_template.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import aiohttp
|
||||
import aiofiles
|
||||
import urllib.parse
|
||||
from FileStream.config import Telegram, Server
|
||||
from FileStream.utils.database import Database
|
||||
from FileStream.utils.human_readable import humanbytes
|
||||
db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME)
|
||||
|
||||
async def render_page(db_id):
|
||||
file_data=await db.get_file(db_id)
|
||||
src = urllib.parse.urljoin(Server.URL, f'dl/{file_data["_id"]}')
|
||||
|
||||
if str((file_data['mime_type']).split('/')[0].strip()) == 'video':
|
||||
async with aiofiles.open('FileStream/template/stream.html') as r:
|
||||
heading = 'Watch {}'.format(file_data['file_name'])
|
||||
html_template = await r.read()
|
||||
html = html_template.replace('streamMediaLink', src).replace('streamHeading', heading)
|
||||
else:
|
||||
async with aiofiles.open('FileStream/template/dl.html') as r:
|
||||
async with aiohttp.ClientSession() as s:
|
||||
async with s.get(src) as u:
|
||||
heading = 'Download {}'.format(file_data['file_name'])
|
||||
file_size = humanbytes(int(u.headers.get('Content-Length')))
|
||||
html = (await r.read()) % (heading, file_data['file_name'], src, file_size)
|
||||
return html
|
||||
22
FileStream/utils/time_format.py
Normal file
22
FileStream/utils/time_format.py
Normal file
@@ -0,0 +1,22 @@
|
||||
def get_readable_time(seconds: int) -> str:
|
||||
count = 0
|
||||
readable_time = ""
|
||||
time_list = []
|
||||
time_suffix_list = ["s", "m", "h", " days"]
|
||||
while count < 4:
|
||||
count += 1
|
||||
if count < 3:
|
||||
remainder, result = divmod(seconds, 60)
|
||||
else:
|
||||
remainder, result = divmod(seconds, 24)
|
||||
if seconds == 0 and remainder == 0:
|
||||
break
|
||||
time_list.append(int(result))
|
||||
seconds = int(remainder)
|
||||
for x in range(len(time_list)):
|
||||
time_list[x] = str(time_list[x]) + time_suffix_list[x]
|
||||
if len(time_list) == 4:
|
||||
readable_time += time_list.pop() + ", "
|
||||
time_list.reverse()
|
||||
readable_time += ": ".join(time_list)
|
||||
return readable_time
|
||||
72
FileStream/utils/translation.py
Normal file
72
FileStream/utils/translation.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from FileStream.config import Telegram
|
||||
|
||||
class LANG(object):
|
||||
|
||||
START_TEXT = """
|
||||
<b>👋 Hᴇʏ, </b>{}\n
|
||||
<b>I'ᴍ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇs sᴛʀᴇᴀᴍɪɴɢ ʙᴏᴛ ᴀs ᴡᴇʟʟ ᴅɪʀᴇᴄᴛ ʟɪɴᴋs ɢᴇɴᴇʀᴀᴛᴏʀ</b>\n
|
||||
<b>ᴡᴏʀᴋɪɴɢ ᴏɴ ᴄʜᴀɴɴᴇʟs ᴀɴᴅ ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛ</b>\n
|
||||
<b>💕 @{}</b>\n"""
|
||||
|
||||
HELP_TEXT = """
|
||||
<b>- ᴀᴅᴅ ᴍᴇ ᴀs ᴀɴ ᴀᴅᴍɪɴ ᴏɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ</b>
|
||||
<b>- sᴇɴᴅ ᴍᴇ ᴀɴʏ ᴅᴏᴄᴜᴍᴇɴᴛ ᴏʀ ᴍᴇᴅɪᴀ</b>
|
||||
<b>- ɪ'ʟʟ ᴘʀᴏᴠɪᴅᴇ sᴛʀᴇᴀᴍᴀʙʟᴇ ʟɪɴᴋ</b>\n
|
||||
<b>🔞 ᴀᴅᴜʟᴛ ᴄᴏɴᴛᴇɴᴛ sᴛʀɪᴄᴛʟʏ ᴘʀᴏʜɪʙɪᴛᴇᴅ.</b>\n
|
||||
<i><b> ʀᴇᴘᴏʀᴛ ʙᴜɢs ᴛᴏ <a href='https://telegram.me/AvishkarPatil'>ᴅᴇᴠᴇʟᴏᴘᴇʀ</a></b></i>"""
|
||||
|
||||
ABOUT_TEXT = """
|
||||
<b>⚜ ᴍʏ ɴᴀᴍᴇ : {}</b>\n
|
||||
<b>✦ ᴠᴇʀsɪᴏɴ : {}</b>
|
||||
<b>✦ ᴜᴘᴅᴀᴛᴇᴅ ᴏɴ : 19-November-2023</b>
|
||||
<b>✦ ᴅᴇᴠᴇʟᴏᴘᴇʀ : <a href='https://telegram.me/AvishkarPatil'>Avishkar Patil</a></b>\n
|
||||
"""
|
||||
|
||||
STREAM_TEXT = """
|
||||
<i><u>𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !</u></i>\n
|
||||
<b>📂 Fɪʟᴇ ɴᴀᴍᴇ :</b> <b>{}</b>\n
|
||||
<b>📦 Fɪʟᴇ ꜱɪᴢᴇ :</b> <code>{}</code>\n
|
||||
<b>📥 Dᴏᴡɴʟᴏᴀᴅ :</b> <code>{}</code>\n
|
||||
<b>🖥 Wᴀᴛᴄʜ :</b> <code>{}</code>\n
|
||||
<b>🔗 Sʜᴀʀᴇ :</b> <code>{}</code>\n"""
|
||||
|
||||
STREAM_TEXT_X = """
|
||||
<i><u>𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !</u></i>\n
|
||||
<b>📂 Fɪʟᴇ ɴᴀᴍᴇ :</b> <b>{}</b>\n
|
||||
<b>📦 Fɪʟᴇ ꜱɪᴢᴇ :</b> <code>{}</code>\n
|
||||
<b>📥 Dᴏᴡɴʟᴏᴀᴅ :</b> <code>{}</code>\n
|
||||
<b>🔗 Sʜᴀʀᴇ :</b> <code>{}</code>\n"""
|
||||
|
||||
|
||||
BAN_TEXT = "__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ.__\n\n**[Cᴏɴᴛᴀᴄᴛ Dᴇᴠᴇʟᴏᴘᴇʀ](tg://user?id={}) Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**"
|
||||
|
||||
|
||||
class BUTTON(object):
|
||||
START_BUTTONS = InlineKeyboardMarkup(
|
||||
[[
|
||||
InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'),
|
||||
InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about'),
|
||||
InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close')
|
||||
],
|
||||
[InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')]
|
||||
]
|
||||
)
|
||||
HELP_BUTTONS = InlineKeyboardMarkup(
|
||||
[[
|
||||
InlineKeyboardButton('ʜᴏᴍᴇ', callback_data='home'),
|
||||
InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about'),
|
||||
InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close'),
|
||||
],
|
||||
[InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')]
|
||||
]
|
||||
)
|
||||
ABOUT_BUTTONS = InlineKeyboardMarkup(
|
||||
[[
|
||||
InlineKeyboardButton('ʜᴏᴍᴇ', callback_data='home'),
|
||||
InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'),
|
||||
InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close'),
|
||||
],
|
||||
[InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')]
|
||||
]
|
||||
)
|
||||
Reference in New Issue
Block a user