October 18, 2025
VCPlayerBot
Telegram-бот для трансляции видео в голосовом чате Telegram как для групп, так и для каналов. Поддерживает прямые трансляции, видео с YouTube и медиафайлы Telegram. Поддерживает запись трансляций, планирование трансляций и многое другое.
Переменные Конфигурации:
Обязательные переменные
API_ID: Получите с my.telegram.orgAPI_HASH: Получите на my.telegram.orgBOT_TOKEN: @BotfatherSESSION_STRING: Генерировать отсюдаCHAT: идентификатор канала/группы, в которой бот воспроизводит музыку.
Рекомендуемые Дополнительные переменные
DATABASE_URI: URL-адрес базы данных MongoDB, полученный из mongodb. Это необязательный параметр, но его рекомендуется использовать для доступа ко всем функциям.HEROKU_API_KEY: Ваш ключ API Heroku. Получите его здесьHEROKU_APP_NAME: Название вашего приложения Heroku.FILTERS: Отфильтруйте результаты поиска по воспроизведению каналов. Воспроизведение каналов означает, что вы можете воспроизвести все файлы на определённом канале с помощью команды /cplay. Текущие фильтры:video document. Для поиска аудиофайлов используйтеvideo document audio. Для поиска только видео используйтеvideoи так далее.
Необязательные переменные
LOG_GROUP: Группа для отправки плейлиста, если CHAT является группой()ADMINS: идентификаторы пользователей, которые могут использовать команды администратора.STARTUP_STREAM: Это будет транслироваться при запуске и перезапуске бота. Вы можете использовать любой STREAM_URL, прямую ссылку на любое видео или ссылку на прямую трансляцию на Youtube. Вы также можете использовать плейлист на Youtube. Найдите ссылку на Telegram для своего плейлиста в PlayList Dumb или получите плейлист в PlayList Extract. Ссылка на плейлист должна быть в форматеhttps://t.me/DumpPlaylist/xxx.REPLY_MESSAGE: Ответ тем, кто отправляет сообщения в личные сообщения аккаунта USER. Оставьте поле пустым, если вам не нужна эта функция. (Настраивается через бота, если добавлен mongodb.)ADMIN_ONLY: ПередайтеTrueЕсли вы хотите, чтобы команда /play была доступна только администраторамCHAT. По умолчанию /play доступна всем. (Настраивается через бота, если добавлен mongodb.)DATABASE_NAME: Имя базы данных для вашей базы данных MongoDB.SHUFFLE: СделайтеFalse, если не хотите, чтобы плейлисты перемешивались. (Настраивается через бота, если добавлен mongodb.)EDIT_TITLE: УкажитеFalse, если вы не хотите, чтобы бот менял название видеочата в соответствии с воспроизводимой песней. (Настраивается через бота, если добавлен mongodb.)RECORDING_DUMP: Идентификатор канала с учетной записью USER в качестве администратора для сброса записей видеочата.RECORDING_TITLE: Пользовательское название для записей видеочатов.TIME_ZONE: Часовой пояс вашей страны, по умолчанию ISTIS_VIDEO_RECORD: Если вы не хотите записывать видео, укажитеFalseи будет записываться только звук. (Настраивается через бота, если добавлен mongodb.)IS_LOOP; СделайтеFalse, если вам не нужен видеочат 24/7. (Настраивается через бота, если добавлен mongodb.)IS_VIDEO: Сделайте егоFalse, если хотите использовать плеер как музыкальный проигрыватель без видео. (Настраивается через бота, если добавлен mongodb.)PORTRAIT: СделайтеTrue, если хотите, чтобы видеозапись велась в портретном режиме. (Настраивается через бота, если добавлен mongodb.)DELAY: Выберите ограничение по времени для удаления команд. По умолчанию 10 секунд.QUALITY: Настройте качество видеочата, выбрав один из вариантов:high,medium,low.BITRATE: Битрейт аудио (не рекомендуется изменять).FPS: Частота кадров воспроизводимого видео (не рекомендуется изменять).
Требования
- Python 3.8 или выше.
- FFMpeg.
Развертывание в Heroku
https://telegram.dog/XTZ_HerokuBot?start=c3ViaW5wcy9WQ1BsYXllckJvdCBtYWlu
Развертывание на VPS
git clone https://github.com/subinps/VCPlayerBot cd VCPlayerBot pip3 install -r requirements.txt # install node js sudo bash install_node.sh # <Create Variables appropriately (.env [optional])> python3 main.py
Характеристики
- Список воспроизведения, очередь.
- Нулевое время простоя в игре.
- Поддерживает запись видео.
- Поддерживает планирование голосовых чатов.
- Крутой интерфейс для управления плеером.
- Настраивается под аудио или видео.
- Индивидуальное качество для видеочатов.
- Поддерживает воспроизведение из плейлиста Youtube.
- Измените заголовок VoiceChat на название текущей воспроизводимой песни.
- Поддерживает прямую трансляцию с YouTube
- Воспроизведение файлов из Telegram поддерживается.
- Включает радио, если в плейлисте нет песен.
- Автоматический перезапуск даже в случае перезапуска heroku. (настраивается)
- Поддержка экспорта и импорта списков воспроизведения.
Примечание
Примечание для так называемого разработчика:
Если вы скопируете этот код, отредактируете несколько строк и выпустите версию V.x или альфа, бета, гамма-ветки вашего репозитория, это не сделает вас разработчиком. Форкните репозиторий и редактируйте его в соответствии со своими потребностями.
Код:
✉Плагины
↪️ callback.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from pyrogram import Client
from contextlib import suppress
from config import Config
from asyncio import sleep
import datetime
import pytz
import calendar
from utils import (
cancel_all_schedules,
delete_messages,
get_admins,
get_buttons,
get_playlist_str,
leave_call,
mute,
pause,
recorder_settings,
restart,
restart_playout,
resume,
schedule_a_play,
seek_file,
set_config,
settings_panel,
shuffle_playlist,
skip,
start_record_stream,
stop_recording,
sync_to_db,
unmute,
volume,
volume_buttons
)
from pyrogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton,
CallbackQuery
)
from pyrogram.errors import (
MessageNotModified,
MessageIdInvalid,
QueryIdInvalid
)
from pyrogram.types import (
InlineKeyboardButton,
InlineKeyboardMarkup
)IST = pytz.timezone(Config.TIME_ZONE)
@Client.on_callback_query()
async def cb_handler(client: Client, query: CallbackQuery):
with suppress(MessageIdInvalid, MessageNotModified, QueryIdInvalid):
admins = await get_admins(Config.CHAT)
if query.data.startswith("info"):
me, you = query.data.split("_")
text="Join @subin_works"
if you == "volume":
await query.answer()
await query.message.edit_reply_markup(reply_markup=await volume_buttons())
return
if you == "player":
if not Config.CALL_STATUS:
return await query.answer("Not Playing anything.", show_alert=True)
await query.message.edit_reply_markup(reply_markup=await get_buttons())
await query.answer()
return
if you == "video":
text="Toggle your bot to Video / Audio Player."
elif you == "shuffle":
text="Enable or disable auto playlist shuffling"
elif you == "admin":
text="Enable to restrict the play command only for admins."
elif you == "mode":
text="Enabling Non- stop playback will make the player running 24 / 7 and automatic startup when restarting. "
elif you == "title":
text="Enable to edit the VideoChat title to Current playing song's title."
elif you == "reply":
text="Choose whether to auto-reply messaged for userbot. "
elif you == "videorecord":
text = "Enable to record both video and audio, if disabled only audio will be recorded."
elif you == "videodimension":
text = "Choose the recording video's dimensions"
elif you == "rectitle":
text = "A custom title for your chat recordings, Use /rtitle command to set a title"
elif you == "recdumb":
text = "A channel to which all the recordings are forwarded. Make sure The User account is admin over there. Set one using /env or /config."
await query.answer(text=text, show_alert=True)
return
elif query.data.startswith("help"):
if query.message.chat.type != "private" and query.message.reply_to_message.from_user is None:
return await query.answer("I cant help you here, since you are an anonymous admin, message me in private chat.", show_alert=True)
elif query.message.chat.type != "private" and query.from_user.id != query.message.reply_to_message.from_user.id:
return await query.answer("Okda", show_alert=True)
me, nyav = query.data.split("_")
back=InlineKeyboardMarkup(
[
[
InlineKeyboardButton("Back", callback_data="help_main"),
InlineKeyboardButton("Close", callback_data="close"),
],
]
)
if nyav == 'main':
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"Play", callback_data='help_play'),
InlineKeyboardButton(f"Settings", callback_data=f"help_settings"),
InlineKeyboardButton(f"Recording", callback_data='help_record'),
],
[
InlineKeyboardButton("Scheduling", callback_data="help_schedule"),
InlineKeyboardButton("Controling", callback_data='help_control'),
InlineKeyboardButton("Admins", callback_data="help_admin"),
],
[
InlineKeyboardButton(f"Misc", callback_data='help_misc'),
InlineKeyboardButton("Config Vars", callback_data='help_env'),
InlineKeyboardButton("Close", callback_data="close"),
],
]
)
await query.message.edit("Showing help menu, Choose from the below options.", reply_markup=reply_markup, disable_web_page_preview=True)
elif nyav == 'play':
await query.message.edit(Config.PLAY_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'settings':
await query.message.edit(Config.SETTINGS_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'schedule':
await query.message.edit(Config.SCHEDULER_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'control':
await query.message.edit(Config.CONTROL_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'admin':
await query.message.edit(Config.ADMIN_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'misc':
await query.message.edit(Config.MISC_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'record':
await query.message.edit(Config.RECORDER_HELP, reply_markup=back, disable_web_page_preview=True)
elif nyav == 'env':
await query.message.edit(Config.ENV_HELP, reply_markup=back, disable_web_page_preview=True)
return
if not query.from_user.id in admins:
await query.answer(
"😒 Played Joji.mp3",
show_alert=True
)
return
#scheduler stuffs
if query.data.startswith("sch"):
if query.message.chat.type != "private" and query.message.reply_to_message.from_user is None:
return await query.answer("You cant use scheduling here, since you are an anonymous admin. Schedule from private chat.", show_alert=True)
if query.message.chat.type != "private" and query.from_user.id != query.message.reply_to_message.from_user.id:
return await query.answer("Okda", show_alert=True)
data = query.data
today = datetime.datetime.now(IST)
smonth=today.strftime("%B")
obj = calendar.Calendar()
thisday = today.day
year = today.year
month = today.month
if data.startswith("sch_month"):
none, none , yea_r, month_, day = data.split("_")
if yea_r == "choose":
year=int(year)
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
button=[]
button_=[]
k=0
for month in months:
k+=1
year_ = year
if k < int(today.month):
year_ += 1
button_.append([InlineKeyboardButton(text=f"{str(month)} {str(year_)}",callback_data=f"sch_showdate_{year_}_{k}")])
else:
button.append([InlineKeyboardButton(text=f"{str(month)} {str(year_)}",callback_data=f"sch_showdate_{year_}_{k}")])
button = button + button_
button.append([InlineKeyboardButton("Close", callback_data="schclose")])
await query.message.edit("Now Choose the month to schedule a voicechatㅤ ㅤㅤ", reply_markup=InlineKeyboardMarkup(button))
elif day == "none":
return
else:
year = int(yea_r)
month = int(month_)
date = int(day)
datetime_object = datetime.datetime.strptime(str(month), "%m")
smonth = datetime_object.strftime("%B")
button=[]
if year == today.year and month == today.month and date == today.day:
now = today.hour
else:
now=0
l = list()
for i in range(now, 24):
l.append(i)
splited=[l[i:i + 6] for i in range(0, len(l), 6)]
for i in splited:
k=[]
for d in i:
k.append(InlineKeyboardButton(text=f"{d}",callback_data=f"sch_day_{year}_{month}_{date}_{d}"))
button.append(k)
if month == today.month and date < today.day and year==today.year+1:
pyear=year-1
else:
pyear=year
button.append([InlineKeyboardButton("Back", callback_data=f"sch_showdate_{pyear}_{month}"), InlineKeyboardButton("Close", callback_data="schclose")])
await query.message.edit(f"Choose the hour of {date} {smonth} {year} to schedule a voicechat.", reply_markup=InlineKeyboardMarkup(button)) elif data.startswith("sch_day"):
none, none, year, month, day, hour = data.split("_")
year = int(year)
month = int(month)
day = int(day)
hour = int(hour)
datetime_object = datetime.datetime.strptime(str(month), "%m")
smonth = datetime_object.strftime("%B")
if year == today.year and month == today.month and day == today.day and hour == today.hour:
now=today.minute
else:
now=0
button=[]
l = list()
for i in range(now, 60):
l.append(i)
for i in range(0, len(l), 6):
chunk = l[i:i + 6]
k=[]
for d in chunk:
k.append(InlineKeyboardButton(text=f"{d}",callback_data=f"sch_minute_{year}_{month}_{day}_{hour}_{d}"))
button.append(k)
button.append([InlineKeyboardButton("Back", callback_data=f"sch_month_{year}_{month}_{day}"), InlineKeyboardButton("Close", callback_data="schclose")])
await query.message.edit(f"Choose minute of {hour}th hour on {day} {smonth} {year} to schedule Voicechat.", reply_markup=InlineKeyboardMarkup(button)) elif data.startswith("sch_minute"):
none, none, year, month, day, hour, minute = data.split("_")
year = int(year)
month = int(month)
day = int(day)
hour = int(hour)
minute = int(minute)
datetime_object = datetime.datetime.strptime(str(month), "%m")
smonth = datetime_object.strftime("%B")
if year == today.year and month == today.month and day == today.day and hour == today.hour and minute <= today.minute:
await query.answer("I dont have a timemachine to go to past!!!.")
return
final=f"{day}th {smonth} {year} at {hour}:{minute}"
button=[
[
InlineKeyboardButton("Confirm", callback_data=f"schconfirm_{year}-{month}-{day} {hour}:{minute}"),
InlineKeyboardButton("Back", callback_data=f"sch_day_{year}_{month}_{day}_{hour}")
],
[
InlineKeyboardButton("Close", callback_data="schclose")
]
]
data=Config.SCHEDULED_STREAM.get(f"{query.message.chat.id}_{query.message.message_id}")
if not data:
await query.answer("This schedule is expired", show_alert=True)
if data['3'] == "telegram":
title=data['1']
else:
title=f"[{data['1']}]({data['2']})"
await query.message.edit(f"Your Stream {title} is now scheduled to start on {final}\n\nClick Confirm to confirm the time.", reply_markup=InlineKeyboardMarkup(button), disable_web_page_preview=True) elif data.startswith("sch_showdate"):
tyear=year
none, none, year, month = data.split("_")
datetime_object = datetime.datetime.strptime(month, "%m")
thissmonth = datetime_object.strftime("%B")
obj = calendar.Calendar()
thisday = today.day
year = int(year)
month = int(month)
m=obj.monthdayscalendar(year, month)
button=[]
button.append([InlineKeyboardButton(text=f"{str(thissmonth)} {str(year)}",callback_data=f"sch_month_choose_none_none")])
days=["Mon", "Tues", "Wed", "Thu", "Fri", "Sat", "Sun"]
f=[]
for day in days:
f.append(InlineKeyboardButton(text=f"{day}",callback_data=f"day_info_none"))
button.append(f)
for one in m:
f=[]
for d in one:
year_=year
if year==today.year and month == today.month and d < int(today.day):
year_ += 1
if d == 0:
k="\u2063"
d="none"
else:
k=d
f.append(InlineKeyboardButton(text=f"{k}",callback_data=f"sch_month_{year_}_{month}_{d}"))
button.append(f)
button.append([InlineKeyboardButton("Close", callback_data="schclose")])
await query.message.edit(f"Choose the day of the month you want to schedule the voicechat.\nToday is {thisday} {smonth} {tyear}. Chooosing a date preceeding today will be considered as next year {year+1}", reply_markup=InlineKeyboardMarkup(button)) elif data.startswith("schconfirm"):
none, date = data.split("_")
date = datetime.datetime.strptime(date, '%Y-%m-%d %H:%M')
local_dt = IST.localize(date, is_dst=None)
utc_dt = local_dt.astimezone(pytz.utc).replace(tzinfo=None)
job_id=f"{query.message.chat.id}_{query.message.message_id}"
Config.SCHEDULE_LIST.append({"job_id":job_id, "date":utc_dt})
Config.SCHEDULE_LIST = sorted(Config.SCHEDULE_LIST, key=lambda k: k['date'])
await schedule_a_play(job_id, utc_dt)
await query.message.edit(f"Succesfully scheduled to stream on <code> {date.strftime('%b %d %Y, %I:%M %p')} </code>")
await delete_messages([query.message, query.message.reply_to_message])
elif query.data == 'schcancelall':
await cancel_all_schedules()
await query.message.edit("All Scheduled Streams are cancelled succesfully.") elif query.data == "schcancel":
buttons = [
[
InlineKeyboardButton('Yes, Iam Sure!!', callback_data='schcancelall'),
InlineKeyboardButton('No', callback_data='schclose'),
]
]
await query.message.edit("Are you sure that you want to cancel all the scheduled streams?", reply_markup=InlineKeyboardMarkup(buttons))
elif data == "schclose":
await query.answer("Menu Closed")
await query.message.delete()
await query.message.reply_to_message.delete() elif query.data == "shuffle":
if not Config.playlist:
await query.answer("Playlist is empty.", show_alert=True)
return
await shuffle_playlist()
await query.answer("Playlist shuffled.")
await sleep(1)
await query.message.edit_reply_markup(reply_markup=await get_buttons())
elif query.data.lower() == "pause":
if Config.PAUSE:
await query.answer("Already Paused", show_alert=True)
else:
await pause()
await query.answer("Stream Paused")
await sleep(1) await query.message.edit_reply_markup(reply_markup=await get_buttons())
elif query.data.lower() == "resume":
if not Config.PAUSE:
await query.answer("Nothing Paused to resume", show_alert=True)
else:
await resume()
await query.answer("Redumed the stream")
await sleep(1)
await query.message.edit_reply_markup(reply_markup=await get_buttons())
elif query.data=="skip":
if not Config.playlist:
await query.answer("No songs in playlist", show_alert=True)
else:
await query.answer("Trying to skip from playlist.")
await skip()
await sleep(1)
if Config.playlist:
title=f"<b>{Config.playlist[0][1]}</b>\nㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
elif Config.STREAM_LINK:
title=f"<b>Stream Using [Url]({Config.DATA['FILE_DATA']['file']})</b>ㅤ ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
title=f"<b>Streaming Startup [stream]({Config.STREAM_URL})</b> ㅤ ㅤ ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
await query.message.edit(f"<b>{title}</b>",
disable_web_page_preview=True,
reply_markup=await get_buttons()
) elif query.data=="replay":
if not Config.playlist:
await query.answer("No songs in playlist", show_alert=True)
else:
await query.answer("trying to restart player")
await restart_playout()
await sleep(1)
await query.message.edit_reply_markup(reply_markup=await get_buttons())
elif query.data.lower() == "mute":
if Config.MUTED:
await unmute()
await query.answer("Unmuted stream")
else:
await mute()
await query.answer("Muted stream")
await sleep(1)
await query.message.edit_reply_markup(reply_markup=await volume_buttons()) elif query.data.lower() == 'seek':
if not Config.CALL_STATUS:
return await query.answer("Not Playing anything.", show_alert=True)
#if not (Config.playlist or Config.STREAM_LINK):
#return await query.answer("Startup stream cant be seeked.", show_alert=True)
await query.answer("trying to seek.")
data=Config.DATA.get('FILE_DATA')
if not data.get('dur', 0) or \
data.get('dur') == 0:
return await query.answer("This is a live stream and cannot be seeked.", show_alert=True)
k, reply = await seek_file(10)
if k == False:
return await query.answer(reply, show_alert=True)
await query.message.edit_reply_markup(reply_markup=await get_buttons()) elif query.data.lower() == 'rewind':
if not Config.CALL_STATUS:
return await query.answer("Not Playing anything.", show_alert=True)
#if not (Config.playlist or Config.STREAM_LINK):
#return await query.answer("Startup stream cant be seeked.", show_alert=True)
await query.answer("trying to rewind.")
data=Config.DATA.get('FILE_DATA')
if not data.get('dur', 0) or \
data.get('dur') == 0:
return await query.answer("This is a live stream and cannot be seeked.", show_alert=True)
k, reply = await seek_file(-10)
if k == False:
return await query.answer(reply, show_alert=True)
await query.message.edit_reply_markup(reply_markup=await get_buttons())
elif query.data == 'restart':
if not Config.CALL_STATUS:
if not Config.playlist:
await query.answer("Player is empty, starting STARTUP_STREAM.")
else:
await query.answer('Resuming the playlist')
await query.answer("Restrating the player")
await restart()
await query.message.edit(text=await get_playlist_str(), reply_markup=await get_buttons(), disable_web_page_preview=True) elif query.data.startswith("volume"):
me, you = query.data.split("_")
if you == "main":
await query.message.edit_reply_markup(reply_markup=await volume_buttons())
if you == "add":
if 190 <= Config.VOLUME <=200:
vol=200
else:
vol=Config.VOLUME+10
if not (1 <= vol <= 200):
return await query.answer("Only 1-200 range accepted.")
await volume(vol)
Config.VOLUME=vol
await query.message.edit_reply_markup(reply_markup=await volume_buttons())
elif you == "less":
if 1 <= Config.VOLUME <=10:
vol=1
else:
vol=Config.VOLUME-10
if not (1 <= vol <= 200):
return await query.answer("Only 1-200 range accepted.")
await volume(vol)
Config.VOLUME=vol
await query.message.edit_reply_markup(reply_markup=await volume_buttons())
elif you == "back":
await query.message.edit_reply_markup(reply_markup=await get_buttons())
elif query.data in ["is_loop", "is_video", "admin_only", "edit_title", "set_shuffle", "reply_msg", "set_new_chat", "record", "record_video", "record_dim"]:
if query.data == "is_loop":
Config.IS_LOOP = set_config(Config.IS_LOOP)
await query.message.edit_reply_markup(reply_markup=await settings_panel())
elif query.data == "is_video":
Config.IS_VIDEO = set_config(Config.IS_VIDEO)
await query.message.edit_reply_markup(reply_markup=await settings_panel())
data=Config.DATA.get('FILE_DATA')
if not data \
or data.get('dur', 0) == 0:
await restart_playout()
return
k, reply = await seek_file(0)
if k == False:
await restart_playout() elif query.data == "admin_only":
Config.ADMIN_ONLY = set_config(Config.ADMIN_ONLY)
await query.message.edit_reply_markup(reply_markup=await settings_panel())
elif query.data == "edit_title":
Config.EDIT_TITLE = set_config(Config.EDIT_TITLE)
await query.message.edit_reply_markup(reply_markup=await settings_panel())
elif query.data == "set_shuffle":
Config.SHUFFLE = set_config(Config.SHUFFLE)
await query.message.edit_reply_markup(reply_markup=await settings_panel())
elif query.data == "reply_msg":
Config.REPLY_PM = set_config(Config.REPLY_PM)
await query.message.edit_reply_markup(reply_markup=await settings_panel())
elif query.data == "record_dim":
if not Config.IS_VIDEO_RECORD:
return await query.answer("This cant be used for audio recordings")
Config.PORTRAIT=set_config(Config.PORTRAIT)
await query.message.edit_reply_markup(reply_markup=(await recorder_settings()))
elif query.data == 'record_video':
Config.IS_VIDEO_RECORD=set_config(Config.IS_VIDEO_RECORD)
await query.message.edit_reply_markup(reply_markup=(await recorder_settings())) elif query.data == 'record':
if Config.IS_RECORDING:
k, msg = await stop_recording()
if k == False:
await query.answer(msg, show_alert=True)
else:
await query.answer("Recording Stopped")
else:
k, msg = await start_record_stream()
if k == False:
await query.answer(msg, show_alert=True)
else:
await query.answer("Recording started")
await query.message.edit_reply_markup(reply_markup=(await recorder_settings())) elif query.data == "set_new_chat":
if query.from_user is None:
return await query.answer("You cant do scheduling here, since you are an anonymous admin. Schedule from private chat.", show_alert=True)
if query.from_user.id in Config.SUDO:
await query.answer("Setting up new CHAT")
chat=query.message.chat.id
if Config.IS_RECORDING:
await stop_recording()
await cancel_all_schedules()
await leave_call()
Config.CHAT=chat
Config.ADMIN_CACHE=False
await restart()
await query.message.edit("Succesfully Changed Chat")
await sync_to_db()
else:
await query.answer("This can only be used by SUDO users", show_alert=True)
if not Config.DATABASE_URI:
await query.answer("No DATABASE found, this changes are saved temporarly and will be reverted on restart. Add MongoDb to make this permanant.")
elif query.data.startswith("close"):
if "sudo" in query.data:
if query.from_user.id in Config.SUDO:
await query.message.delete()
else:
await query.answer("This can only be used by SUDO users", show_alert=True)
else:
if query.message.chat.type != "private" and query.message.reply_to_message:
if query.message.reply_to_message.from_user is None:
pass
elif query.from_user.id != query.message.reply_to_message.from_user.id:
return await query.answer("Okda", show_alert=True)
elif query.from_user.id in Config.ADMINS:
pass
else:
return await query.answer("Okda", show_alert=True)
await query.answer("Menu Closed")
await query.message.delete()
await query.answer()↪️commands.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from contextlib import suppress
from config import Config
import calendar
import pytz
from datetime import datetime
import asyncio
import os
from pyrogram.errors.exceptions.bad_request_400 import (
MessageIdInvalid,
MessageNotModified
)
from pyrogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton
)
from utils import (
cancel_all_schedules,
edit_config,
is_admin,
leave_call,
restart,
restart_playout,
stop_recording,
sync_to_db,
update,
is_admin,
chat_filter,
sudo_filter,
delete_messages,
seek_file
)
from pyrogram import (
Client,
filters
)IST = pytz.timezone(Config.TIME_ZONE)
if Config.DATABASE_URI:
from utils import dbHOME_TEXT = "<b>Hey [{}](tg://user?id={}) 🙋♂️\n\nIam A Bot Built To Play or Stream Videos In Telegram VoiceChats.\nI Can Stream Any YouTube Video Or A Telegram File Or Even A YouTube Live.</b>"
admin_filter=filters.create(is_admin) @Client.on_message(filters.command(['start', f"start@{Config.BOT_USERNAME}"]))
async def start(client, message):
if len(message.command) > 1:
if message.command[1] == 'help':
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"Play", callback_data='help_play'),
InlineKeyboardButton(f"Settings", callback_data=f"help_settings"),
InlineKeyboardButton(f"Recording", callback_data='help_record'),
],
[
InlineKeyboardButton("Scheduling", callback_data="help_schedule"),
InlineKeyboardButton("Controling", callback_data='help_control'),
InlineKeyboardButton("Admins", callback_data="help_admin"),
],
[
InlineKeyboardButton(f"Misc", callback_data='help_misc'),
InlineKeyboardButton("Close", callback_data="close"),
],
]
)
await message.reply("Learn to use the VCPlayer, Showing help menu, Choose from the below options.",
reply_markup=reply_markup,
disable_web_page_preview=True
)
elif 'sch' in message.command[1]:
msg=await message.reply("Checking schedules..")
you, me = message.command[1].split("_", 1)
who=Config.SCHEDULED_STREAM.get(me)
if not who:
return await msg.edit("Something gone somewhere.")
del Config.SCHEDULED_STREAM[me]
whom=f"{message.chat.id}_{msg.message_id}"
Config.SCHEDULED_STREAM[whom] = who
await sync_to_db()
if message.from_user.id not in Config.ADMINS:
return await msg.edit("OK da")
today = datetime.now(IST)
smonth=today.strftime("%B")
obj = calendar.Calendar()
thisday = today.day
year = today.year
month = today.month
m=obj.monthdayscalendar(year, month)
button=[]
button.append([InlineKeyboardButton(text=f"{str(smonth)} {str(year)}",callback_data=f"sch_month_choose_none_none")])
days=["Mon", "Tues", "Wed", "Thu", "Fri", "Sat", "Sun"]
f=[]
for day in days:
f.append(InlineKeyboardButton(text=f"{day}",callback_data=f"day_info_none"))
button.append(f)
for one in m:
f=[]
for d in one:
year_=year
if d < int(today.day):
year_ += 1
if d == 0:
k="\u2063"
d="none"
else:
k=d
f.append(InlineKeyboardButton(text=f"{k}",callback_data=f"sch_month_{year_}_{month}_{d}"))
button.append(f)
button.append([InlineKeyboardButton("Close", callback_data="schclose")])
await msg.edit(f"Choose the day of the month you want to schedule the voicechat.\nToday is {thisday} {smonth} {year}. Chooosing a date preceeding today will be considered as next year {year+1}", reply_markup=InlineKeyboardMarkup(button)) return
buttons = [
[
InlineKeyboardButton('⚙️ Update Channel', url='https://t.me/subin_works'),
InlineKeyboardButton('🧩 Source', url='https://github.com/subinps/VCPlayerBot')
],
[
InlineKeyboardButton('👨🏼🦯 Help', callback_data='help_main'),
InlineKeyboardButton('🗑 Close', callback_data='close'),
]
]
reply_markup = InlineKeyboardMarkup(buttons)
k = await message.reply(HOME_TEXT.format(message.from_user.first_name, message.from_user.id), reply_markup=reply_markup)
await delete_messages([message, k])@Client.on_message(filters.command(["help", f"help@{Config.BOT_USERNAME}"]))
async def show_help(client, message):
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton("Play", callback_data='help_play'),
InlineKeyboardButton("Settings", callback_data=f"help_settings"),
InlineKeyboardButton("Recording", callback_data='help_record'),
],
[
InlineKeyboardButton("Scheduling", callback_data="help_schedule"),
InlineKeyboardButton("Controling", callback_data='help_control'),
InlineKeyboardButton("Admins", callback_data="help_admin"),
],
[
InlineKeyboardButton("Misc", callback_data='help_misc'),
InlineKeyboardButton("Config Vars", callback_data='help_env'),
InlineKeyboardButton("Close", callback_data="close"),
],
]
)
if message.chat.type != "private" and message.from_user is None:
k=await message.reply(
text="I cant help you here, since you are an anonymous admin. Get help in PM",
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"Help", url=f"https://telegram.dog/{Config.BOT_USERNAME}?start=help"),
]
]
),)
await delete_messages([message, k])
return
if Config.msg.get('help') is not None:
await Config.msg['help'].delete()
Config.msg['help'] = await message.reply_text(
"Learn to use the VCPlayer, Showing help menu, Choose from the below options.",
reply_markup=reply_markup,
disable_web_page_preview=True
)
#await delete_messages([message])
@Client.on_message(filters.command(['repo', f"repo@{Config.BOT_USERNAME}"]))
async def repo_(client, message):
buttons = [
[
InlineKeyboardButton('🧩 Repository', url='https://github.com/subinps/VCPlayerBot'),
InlineKeyboardButton('⚙️ Update Channel', url='https://t.me/subin_works'),
],
[
InlineKeyboardButton("🎞 How to Deploy", url='https://youtu.be/mnWgZMrNe_0'),
InlineKeyboardButton('🗑 Close', callback_data='close'),
]
]
await message.reply("<b>The source code of this bot is public and can be found at <a href=https://github.com/subinps/VCPlayerBot>VCPlayerBot.</a>\nYou can deploy your own bot and use in your group.\n\nFeel free to star☀️ the repo if you liked it 🙃.</b>", reply_markup=InlineKeyboardMarkup(buttons), disable_web_page_preview=True)
await delete_messages([message])@Client.on_message(filters.command(['restart', 'update', f"restart@{Config.BOT_USERNAME}", f"update@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def update_handler(client, message):
if Config.HEROKU_APP:
k = await message.reply("Heroku APP found, Restarting app to update.")
if Config.DATABASE_URI:
msg = {"msg_id":k.message_id, "chat_id":k.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
await sync_to_db()
else:
k = await message.reply("No Heroku APP found, Trying to restart.")
if Config.DATABASE_URI:
msg = {"msg_id":k.message_id, "chat_id":k.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
try:
await message.delete()
except:
pass
await update()@Client.on_message(filters.command(['logs', f"logs@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def get_logs(client, message):
m=await message.reply("Checking logs..")
if os.path.exists("botlog.txt"):
await message.reply_document('botlog.txt', caption="Bot Logs")
await m.delete()
await delete_messages([message])
else:
k = await m.edit("No log files found.")
await delete_messages([message, k])@Client.on_message(filters.command(['env', f"env@{Config.BOT_USERNAME}", "config", f"config@{Config.BOT_USERNAME}"]) & sudo_filter & chat_filter)
async def set_heroku_var(client, message):
with suppress(MessageIdInvalid, MessageNotModified):
m = await message.reply("Checking config vars..")
if " " in message.text:
cmd, env = message.text.split(" ", 1)
if "=" in env:
var, value = env.split("=", 1)
else:
if env == "STARTUP_STREAM":
env_ = "STREAM_URL"
elif env == "QUALITY":
env_ = "CUSTOM_QUALITY"
else:
env_ = env
ENV_VARS = ["ADMINS", "SUDO", "CHAT", "LOG_GROUP", "STREAM_URL", "SHUFFLE", "ADMIN_ONLY", "REPLY_MESSAGE",
"EDIT_TITLE", "RECORDING_DUMP", "RECORDING_TITLE", "IS_VIDEO", "IS_LOOP", "DELAY", "PORTRAIT",
"IS_VIDEO_RECORD", "PTN", "CUSTOM_QUALITY"]
if env_ in ENV_VARS:
await m.edit(f"Current Value for `{env}` is `{getattr(Config, env_)}`")
await delete_messages([message])
return
else:
await m.edit("This is an invalid env value. Read help on env to know about available env vars.")
await delete_messages([message, m])
return
else:
await m.edit("You haven't provided any value for env, you should follow the correct format.\nExample: <code>/env CHAT=-1020202020202</code> to change or set CHAT var.\n<code>/env REPLY_MESSAGE= <code>To delete REPLY_MESSAGE.")
await delete_messages([message, m])
return if Config.DATABASE_URI and var in ["STARTUP_STREAM", "CHAT", "LOG_GROUP", "REPLY_MESSAGE", "DELAY", "RECORDING_DUMP", "QUALITY"]:
await m.edit("Mongo DB Found, Setting up config vars...")
await asyncio.sleep(2)
if not value:
await m.edit(f"No value for env specified. Trying to delete env {var}.")
await asyncio.sleep(2)
if var in ["STARTUP_STREAM", "CHAT", "DELAY"]:
await m.edit("This is a mandatory var and cannot be deleted.")
await delete_messages([message, m])
return
await edit_config(var, False)
await m.edit(f"Sucessfully deleted {var}")
await delete_messages([message, m])
return
else:
if var in ["CHAT", "LOG_GROUP", "RECORDING_DUMP", "QUALITY"]:
try:
value=int(value)
except:
if var == "QUALITY":
if not value.lower() in ["low", "medium", "high"]:
await m.edit("You should specify a value between 10 - 100.")
await delete_messages([message, m])
return
else:
value = value.lower()
if value == "high":
value = 100
elif value == "medium":
value = 66.9
elif value == "low":
value = 50
else:
await m.edit("You should give me a chat id . It should be an interger.")
await delete_messages([message, m])
return
if var == "CHAT":
await leave_call()
Config.ADMIN_CACHE=False
if Config.IS_RECORDING:
await stop_recording()
await cancel_all_schedules()
Config.CHAT=int(value)
await restart()
await edit_config(var, int(value))
if var == "QUALITY":
if Config.CALL_STATUS:
data=Config.DATA.get('FILE_DATA')
if not data \
or data.get('dur', 0) == 0:
await restart_playout()
return
k, reply = await seek_file(0)
if k == False:
await restart_playout()
await m.edit(f"Succesfully changed {var} to {value}")
await delete_messages([message, m])
return
else:
if var == "STARTUP_STREAM":
Config.STREAM_SETUP=False
await edit_config(var, value)
await m.edit(f"Succesfully changed {var} to {value}")
await delete_messages([message, m])
await restart_playout()
return
else:
if not Config.HEROKU_APP:
buttons = [[InlineKeyboardButton('Heroku API_KEY', url='https://dashboard.heroku.com/account/applications/authorizations/new'), InlineKeyboardButton('🗑 Close', callback_data='close'),]]
await m.edit(
text="No heroku app found, this command needs the following heroku vars to be set.\n\n1. <code>HEROKU_API_KEY</code>: Your heroku account api key.\n2. <code>HEROKU_APP_NAME</code>: Your heroku app name.",
reply_markup=InlineKeyboardMarkup(buttons))
await delete_messages([message])
return
config = Config.HEROKU_APP.config()
if not value:
await m.edit(f"No value for env specified. Trying to delete env {var}.")
await asyncio.sleep(2)
if var in ["STARTUP_STREAM", "CHAT", "DELAY", "API_ID", "API_HASH", "BOT_TOKEN", "SESSION_STRING", "ADMINS"]:
await m.edit("These are mandatory vars and cannot be deleted.")
await delete_messages([message, m])
return
if var in config:
await m.edit(f"Sucessfully deleted {var}")
await asyncio.sleep(2)
await m.edit("Now restarting the app to make changes.")
if Config.DATABASE_URI:
msg = {"msg_id":m.message_id, "chat_id":m.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
del config[var]
config[var] = None
else:
k = await m.edit(f"No env named {var} found. Nothing was changed.")
await delete_messages([message, k])
return
if var in config:
await m.edit(f"Variable already found. Now edited to {value}")
else:
await m.edit(f"Variable not found, Now setting as new var.")
await asyncio.sleep(2)
await m.edit(f"Succesfully set {var} with value {value}, Now Restarting to take effect of changes...")
if Config.DATABASE_URI:
msg = {"msg_id":m.message_id, "chat_id":m.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
config[var] = str(value)↪️controls.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from pyrogram.types import Message
from config import Config
from pyrogram import (
Client,
filters
)
from utils import (
clear_db_playlist,
get_playlist_str,
is_admin,
mute,
restart_playout,
settings_panel,
skip,
pause,
resume,
unmute,
volume,
get_buttons,
is_admin,
seek_file,
delete_messages,
chat_filter,
volume_buttons
)admin_filter=filters.create(is_admin)
@Client.on_message(filters.command(["playlist", f"playlist@{Config.BOT_USERNAME}"]) & chat_filter)
async def player(client, message):
if not Config.CALL_STATUS:
await message.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([message])
return
pl = await get_playlist_str()
if message.chat.type == "private":
await message.reply_text(
pl,
disable_web_page_preview=True,
reply_markup=await get_buttons(),
)
else:
if Config.msg.get('player') is not None:
await Config.msg['player'].delete()
Config.msg['player'] = await message.reply_text(
pl,
disable_web_page_preview=True,
reply_markup=await get_buttons(),
)
await delete_messages([message])@Client.on_message(filters.command(["skip", f"skip@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def skip_track(_, m: Message):
msg=await m.reply('trying to skip from queue..')
if not Config.CALL_STATUS:
await msg.edit(
"Player is idle, start the player using below button. ㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
if not Config.playlist:
await msg.edit("Playlist is Empty.")
await delete_messages([m, msg])
return
if len(m.command) == 1:
await skip()
else:
#https://github.com/callsmusic/tgvc-userbot/blob/dev/plugins/vc/player.py#L268-L288
try:
items = list(dict.fromkeys(m.command[1:]))
items = [int(x) for x in items if x.isdigit()]
items.sort(reverse=True)
for i in items:
if 2 <= i <= (len(Config.playlist) - 1):
await msg.edit(f"Succesfully Removed from Playlist- {i}. **{Config.playlist[i][1]}**")
await clear_db_playlist(song=Config.playlist[i])
Config.playlist.pop(i)
await delete_messages([m, msg])
else:
await msg.edit(f"You cant skip first two songs- {i}")
await delete_messages([m, msg])
except (ValueError, TypeError):
await msg.edit("Invalid input")
await delete_messages([m, msg])
pl=await get_playlist_str()
if m.chat.type == "private":
await msg.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
elif not Config.LOG_GROUP and m.chat.type == "supergroup":
if Config.msg.get('player'):
await Config.msg['player'].delete()
Config.msg['player'] = await msg.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
await delete_messages([m])@Client.on_message(filters.command(["pause", f"pause@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def pause_playing(_, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
if Config.PAUSE:
k = await m.reply("Already Paused")
await delete_messages([m, k])
return
k = await m.reply("Paused Video Call")
await pause()
await delete_messages([m, k])
@Client.on_message(filters.command(["resume", f"resume@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def resume_playing(_, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
if not Config.PAUSE:
k = await m.reply("Nothing paused to resume")
await delete_messages([m, k])
return
k = await m.reply("Resumed Video Call")
await resume()
await delete_messages([m, k])
@Client.on_message(filters.command(['volume', f"volume@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def set_vol(_, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
if len(m.command) < 2:
await m.reply_text('Change Volume of Your VCPlayer. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ', reply_markup=await volume_buttons())
await delete_messages([m])
return
if not 1 < int(m.command[1]) < 200:
await m.reply_text(f"Only 1-200 range is accepeted. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ", reply_markup=await volume_buttons())
else:
await volume(int(m.command[1]))
await m.reply_text(f"Succesfully set volume to {m.command[1]} ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ", reply_markup=await volume_buttons())
await delete_messages([m])
@Client.on_message(filters.command(['vcmute', f"vcmute@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def set_mute(_, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
if Config.MUTED:
k = await m.reply_text("Already muted.")
await delete_messages([m, k])
return
k=await mute()
if k:
k = await m.reply_text(f" 🔇 Succesfully Muted ")
await delete_messages([m, k])
else:
k = await m.reply_text("Already muted.")
await delete_messages([m, k])
@Client.on_message(filters.command(['vcunmute', f"vcunmute@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def set_unmute(_, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
if not Config.MUTED:
k = await m.reply("Stream already unmuted.")
await delete_messages([m, k])
return
k=await unmute()
if k:
k = await m.reply_text(f"🔊 Succesfully Unmuted ")
await delete_messages([m, k])
return
else:
k=await m.reply_text("Not muted, already unmuted.")
await delete_messages([m, k])
@Client.on_message(filters.command(["replay", f"replay@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def replay_playout(client, m: Message):
msg = await m.reply('Checking player')
if not Config.CALL_STATUS:
await msg.edit(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
await msg.edit(f"Replaying from begining")
await restart_playout()
await delete_messages([m, msg])
@Client.on_message(filters.command(["player", f"player@{Config.BOT_USERNAME}"]) & chat_filter)
async def show_player(client, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
data=Config.DATA.get('FILE_DATA')
if not data.get('dur', 0) or \
data.get('dur') == 0:
title="<b>Playing Live Stream</b> ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
if Config.playlist:
title=f"<b>{Config.playlist[0][1]}</b> ㅤㅤㅤㅤ\n ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
elif Config.STREAM_LINK:
title=f"<b>Stream Using [Url]({data['file']}) </b> ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
title=f"<b>Streaming Startup [stream]({Config.STREAM_URL})</b> ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
if m.chat.type == "private":
await m.reply_text(
title,
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
else:
if Config.msg.get('player') is not None:
await Config.msg['player'].delete()
Config.msg['player'] = await m.reply_text(
title,
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
@Client.on_message(filters.command(["seek", f"seek@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def seek_playout(client, m: Message):
if not Config.CALL_STATUS:
await m.reply_text(
"Player is idle, start the player using below button. ㅤㅤㅤ ㅤㅤ",
disable_web_page_preview=True,
reply_markup=await get_buttons()
)
await delete_messages([m])
return
data=Config.DATA.get('FILE_DATA')
k=await m.reply("Trying to seek..")
if not data.get('dur', 0) or \
data.get('dur') == 0:
await k.edit("This stream cant be seeked.")
await delete_messages([m, k])
return
if ' ' in m.text:
i, time = m.text.split(" ")
try:
time=int(time)
except:
await k.edit('Invalid time specified')
await delete_messages([m, k])
return
nyav, string=await seek_file(time)
if nyav == False:
await k.edit(string)
await delete_messages([m, k])
return
if not data.get('dur', 0)\
or data.get('dur') == 0:
title="<b>Playing Live Stream</b> ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
if Config.playlist:
title=f"<b>{Config.playlist[0][1]}</b>\nㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
elif Config.STREAM_LINK:
title=f"<b>Stream Using [Url]({data['file']})</b> ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
title=f"<b>Streaming Startup [stream]({Config.STREAM_URL})</b> ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
if Config.msg.get('player'):
await Config.msg['player'].delete()
Config.msg['player'] = await k.edit(f"🎸{title}", reply_markup=await get_buttons(), disable_web_page_preview=True)
await delete_messages([m])
else:
await k.edit('No time specified')
await delete_messages([m, k])
@Client.on_message(filters.command(["settings", f"settings@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def settings(client, m: Message):
await m.reply(f"Configure Your VCPlayer Settings Here. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ", reply_markup=await settings_panel(), disable_web_page_preview=True)
await delete_messages([m])↪️export_import.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
import json
import os
from pyrogram.types import Message
from contextlib import suppress
from config import Config
from utils import (
get_buttons,
is_admin,
get_playlist_str,
shuffle_playlist,
import_play_list,
delete_messages,
chat_filter
)
from pyrogram import (
Client,
filters
)
from pyrogram.errors import (
MessageNotModified,
MessageIdInvalid
)admin_filter=filters.create(is_admin)
@Client.on_message(filters.command(["export", f"export@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def export_play_list(client, message: Message):
if not Config.playlist:
k=await message.reply_text("Playlist is Empty")
await delete_messages([message, k])
return
file=f"{message.chat.id}_{message.message_id}.json"
with open(file, 'w+') as outfile:
json.dump(Config.playlist, outfile, indent=4)
await client.send_document(chat_id=message.chat.id, document=file, file_name="PlayList.json", caption=f"Playlist\n\nNumber Of Songs: <code>{len(Config.playlist)}</code>\n\nJoin [XTZ Bots](https://t.me/subin_works)")
try:
os.remove(file)
except:
pass
await delete_messages([message])@Client.on_message(filters.command(["import", f"import@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def import_playlist(client, m: Message):
with suppress(MessageIdInvalid, MessageNotModified):
if m.reply_to_message is not None and m.reply_to_message.document:
if m.reply_to_message.document.file_name != "PlayList.json":
k=await m.reply("Invalid PlayList file given. Export your current Playlist using /export.")
await delete_messages([m, k])
return
myplaylist=await m.reply_to_message.download()
status=await m.reply("Trying to get details from playlist.")
n=await import_play_list(myplaylist)
if not n:
await status.edit("Errors Occured while importing playlist.")
await delete_messages([m, status])
return
if Config.SHUFFLE:
await shuffle_playlist()
pl=await get_playlist_str()
if m.chat.type == "private":
await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
elif not Config.LOG_GROUP and m.chat.type == "supergroup":
if Config.msg.get('playlist'):
await Config.msg['playlist'].delete()
Config.msg['playlist'] = await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
await delete_messages([m])
else:
await delete_messages([m, status])
else:
k = await m.reply("No playList file given.")
await delete_messages([m, k])↪️inline.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from pyrogram.handlers import InlineQueryHandler
from youtubesearchpython import VideosSearch
from config import Config
from utils import LOGGER
from pyrogram.types import (
InlineQueryResultArticle,
InputTextMessageContent,
InlineKeyboardButton,
InlineKeyboardMarkup
)
from pyrogram import (
Client,
errors
)
buttons = [
[
InlineKeyboardButton('⚡️Make Own Bot', url='https://github.com/subinps/VCPlayerBot'),
InlineKeyboardButton('🧩 Join Here', url='https://t.me/subin_works'),
]
]
def get_cmd(dur):
if dur:
return "/play"
else:
return "/stream"
@Client.on_inline_query()
async def search(client, query):
answers = []
if query.query == "ETHO_ORUTHAN_PM_VANNU":
answers.append(
InlineQueryResultArticle(
title="Deploy",
input_message_content=InputTextMessageContent(f"{Config.REPLY_MESSAGE}\n\n<b>You can't use this bot in your group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.</b>", disable_web_page_preview=True),
reply_markup=InlineKeyboardMarkup(buttons)
)
)
await query.answer(results=answers, cache_time=0)
return
string = query.query.lower().strip().rstrip()
if string == "":
await client.answer_inline_query(
query.id,
results=answers,
switch_pm_text=("Search a youtube video"),
switch_pm_parameter="help",
cache_time=0
)
else:
videosSearch = VideosSearch(string.lower(), limit=50)
for v in videosSearch.result()["result"]:
answers.append(
InlineQueryResultArticle(
title=v["title"],
description=("Duration: {} Views: {}").format(
v["duration"],
v["viewCount"]["short"]
),
input_message_content=InputTextMessageContent(
"{} https://www.youtube.com/watch?v={}".format(get_cmd(v["duration"]), v["id"])
),
thumb_url=v["thumbnails"][0]["url"]
)
)
try:
await query.answer(
results=answers,
cache_time=0
)
except errors.QueryIdInvalid:
await query.answer(
results=answers,
cache_time=0,
switch_pm_text=("Nothing found"),
switch_pm_parameter="",
)
__handlers__ = [
[
InlineQueryHandler(
search
)
]
]↪️файл manage_admins.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from config import Config
from pyrogram import (
Client,
filters
)
from utils import (
get_admins,
sync_to_db,
delete_messages,
sudo_filter
)
@Client.on_message(filters.command(['vcpromote', f"vcpromote@{Config.BOT_USERNAME}"]) & sudo_filter)
async def add_admin(client, message):
if message.reply_to_message:
if message.reply_to_message.from_user.id is None:
k = await message.reply("You are an anonymous admin, you can't do this.")
await delete_messages([message, k])
return
user_id=message.reply_to_message.from_user.id
user=message.reply_to_message.from_user elif ' ' in message.text:
c, user = message.text.split(" ", 1)
if user.startswith("@"):
user=user.replace("@", "")
try:
user=await client.get_users(user)
except Exception as e:
k=await message.reply(f"I was unable to locate that user.\nError: {e}")
LOGGER.error(f"Unable to find the user - {e}", exc_info=True)
await delete_messages([message, k])
return
user_id=user.id
else:
try:
user_id=int(user)
user=await client.get_users(user_id)
except:
k=await message.reply(f"You should give a user id or his username with @.")
await delete_messages([message, k])
return
else:
k=await message.reply("No user specified, reply to a user with /vcpromote or pass a users user id or username.")
await delete_messages([message, k])
return
if user_id in Config.ADMINS:
k = await message.reply("This user is already an admin.")
await delete_messages([message, k])
return
Config.ADMINS.append(user_id)
k=await message.reply(f"Succesfully promoted {user.mention} as VC admin")
await sync_to_db()
await delete_messages([message, k])
@Client.on_message(filters.command(['vcdemote', f"vcdemote@{Config.BOT_USERNAME}"]) & sudo_filter)
async def remove_admin(client, message):
if message.reply_to_message:
if message.reply_to_message.from_user.id is None:
k = await message.reply("You are an anonymous admin, you can't do this.")
await delete_messages([message, k])
return
user_id=message.reply_to_message.from_user.id
user=message.reply_to_message.from_user
elif ' ' in message.text:
c, user = message.text.split(" ", 1)
if user.startswith("@"):
user=user.replace("@", "")
try:
user=await client.get_users(user)
except Exception as e:
k = await message.reply(f"I was unable to locate that user.\nError: {e}")
LOGGER.error(f"Unable to Locate user, {e}", exc_info=True)
await delete_messages([message, k])
return
user_id=user.id
else:
try:
user_id=int(user)
user=await client.get_users(user_id)
except:
k = await message.reply(f"You should give a user id or his username with @.")
await delete_messages([message, k])
return
else:
k = await message.reply("No user specified, reply to a user with /vcdemote or pass a users user id or username.")
await delete_messages([message, k])
return
if not user_id in Config.ADMINS:
k = await message.reply("This user is not an admin yet.")
await delete_messages([message, k])
return
Config.ADMINS.remove(user_id)
k = await message.reply(f"Succesfully Demoted {user.mention}")
await sync_to_db()
await delete_messages([message, k])
@Client.on_message(filters.command(['refresh', f"refresh@{Config.BOT_USERNAME}"]) & filters.user(Config.SUDO))
async def refresh_admins(client, message):
Config.ADMIN_CACHE=False
await get_admins(Config.CHAT)
k = await message.reply("Admin list has been refreshed")
await sync_to_db()
await delete_messages([message, k])↪️player.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from youtube_search import YoutubeSearch
from contextlib import suppress
from pyrogram.types import Message
from yt_dlp import YoutubeDL
from datetime import datetime
from pyrogram import filters
from config import Config
from PTN import parse
import re
from utils import (
add_to_db_playlist,
clear_db_playlist,
delete_messages,
download,
get_admins,
get_duration,
is_admin,
get_buttons,
get_link,
import_play_list,
is_audio,
leave_call,
play,
get_playlist_str,
send_playlist,
shuffle_playlist,
start_stream,
stream_from_link,
chat_filter,
c_play,
is_ytdl_supported
)
from pyrogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton
)
from pyrogram.errors import (
MessageIdInvalid,
MessageNotModified,
UserNotParticipant,
PeerIdInvalid,
ChannelInvalid
)
from pyrogram import (
Client,
filters
)admin_filter=filters.create(is_admin)
@Client.on_message(filters.command(["play", "fplay", f"play@{Config.BOT_USERNAME}", f"fplay@{Config.BOT_USERNAME}"]) & chat_filter)
async def add_to_playlist(_, message: Message):
with suppress(MessageIdInvalid, MessageNotModified):
admins = await get_admins(Config.CHAT)
if Config.ADMIN_ONLY:
if not (message.from_user is None and message.sender_chat or message.from_user.id in admins):
k=await message.reply_sticker("CAADBQADsQIAAtILIVYld1n74e3JuQI")
await delete_messages([message, k])
return
type=""
yturl=""
ysearch=""
url=""
if message.command[0] == "fplay":
if not (message.from_user is None and message.sender_chat or message.from_user.id in admins):
k=await message.reply("This command is only for admins.")
await delete_messages([message, k])
return
msg = await message.reply_text("⚡️ **Checking recived input..**")
if message.reply_to_message and message.reply_to_message.video:
await msg.edit("⚡️ **Checking Telegram Media...**")
type='video'
m_video = message.reply_to_message.video
elif message.reply_to_message and message.reply_to_message.document:
await msg.edit("⚡️ **Checking Telegram Media...**")
m_video = message.reply_to_message.document
type='video'
if not "video" in m_video.mime_type:
return await msg.edit("The given file is invalid")
elif message.reply_to_message and message.reply_to_message.audio:
#if not Config.IS_VIDEO:
#return await message.reply("Play from audio file is available only if Video Mode if turned off.\nUse /settings to configure ypur player.")
await msg.edit("⚡️ **Checking Telegram Media...**")
type='audio'
m_video = message.reply_to_message.audio
else:
if message.reply_to_message and message.reply_to_message.text:
query=message.reply_to_message.text
elif " " in message.text:
text = message.text.split(" ", 1)
query = text[1]
else:
await msg.edit("You Didn't gave me anything to play.Reply to a video or a youtube link or a direct link.")
await delete_messages([message, msg])
return
regex = r"^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&?"
match = re.match(regex,query)
if match:
type="youtube"
yturl=query
elif query.startswith("http"):
try:
has_audio_ = await is_audio(query)
except:
has_audio_ = False
LOGGER.error("Unable to get Audio properties within time.")
if has_audio_:
try:
dur=await get_duration(query)
except:
dur=0
if dur == 0:
await msg.edit("This is a live stream, Use /stream command.")
await delete_messages([message, msg])
return
type="direct"
url=query
else:
if is_ytdl_supported(query):
type="ytdl_s"
url=query
else:
await msg.edit("This is an invalid link, provide me a direct link or a youtube link.")
await delete_messages([message, msg])
return
else:
type="query"
ysearch=query
if not message.from_user is None:
user=f"[{message.from_user.first_name}](tg://user?id={message.from_user.id})"
user_id = message.from_user.id
else:
user="Anonymous"
user_id = "anonymous_admin"
now = datetime.now()
nyav = now.strftime("%d-%m-%Y-%H:%M:%S")
if type in ["video", "audio"]:
if type == "audio":
if m_video.title is None:
if m_video.file_name is None:
title_ = "Music"
else:
title_ = m_video.file_name
else:
title_ = m_video.title
if m_video.performer is not None:
title = f"{m_video.performer} - {title_}"
else:
title=title_
unique = f"{nyav}_{m_video.file_size}_audio"
else:
title=m_video.file_name
unique = f"{nyav}_{m_video.file_size}_video"
if Config.PTN:
ny = parse(title)
title_ = ny.get("title")
if title_:
title = title_
file_id=m_video.file_id
if title is None:
title = 'Music'
data={1:title, 2:file_id, 3:"telegram", 4:user, 5:unique}
if message.command[0] == "fplay":
pla = [data] + Config.playlist
Config.playlist = pla
else:
Config.playlist.append(data)
await add_to_db_playlist(data)
await msg.edit("Media added to playlist")
elif type in ["youtube", "query", "ytdl_s"]:
if type=="youtube":
await msg.edit("⚡️ **Fetching Video From YouTube...**")
url=yturl
elif type=="query":
try:
await msg.edit("⚡️ **Fetching Video From YouTube...**")
ytquery=ysearch
results = YoutubeSearch(ytquery, max_results=1).to_dict()
url = f"https://youtube.com{results[0]['url_suffix']}"
title = results[0]["title"][:40]
except Exception as e:
await msg.edit(
"Song not found.\nTry inline mode.."
)
LOGGER.error(str(e), exc_info=True)
await delete_messages([message, msg])
return
elif type == "ytdl_s":
url=url
else:
return
ydl_opts = {
"quite": True,
"geo-bypass": True,
"nocheckcertificate": True
}
ydl = YoutubeDL(ydl_opts)
try:
info = ydl.extract_info(url, False)
except Exception as e:
LOGGER.error(e, exc_info=True)
await msg.edit(
f"YouTube Download Error ❌\nError:- {e}"
)
LOGGER.error(str(e))
await delete_messages([message, msg])
return
if type == "ytdl_s":
title = "Music"
try:
title = info['title']
except:
pass
else:
title = info["title"]
if info['duration'] is None:
await msg.edit("This is a live stream, Use /stream command.")
await delete_messages([message, msg])
return
data={1:title, 2:url, 3:"youtube", 4:user, 5:f"{nyav}_{user_id}"}
if message.command[0] == "fplay":
pla = [data] + Config.playlist
Config.playlist = pla
else:
Config.playlist.append(data)
await add_to_db_playlist(data)
await msg.edit(f"[{title}]({url}) added to playist", disable_web_page_preview=True)
elif type == "direct":
data={1:"Music", 2:url, 3:"url", 4:user, 5:f"{nyav}_{user_id}"}
if message.command[0] == "fplay":
pla = [data] + Config.playlist
Config.playlist = pla
else:
Config.playlist.append(data)
await add_to_db_playlist(data)
await msg.edit("Link added to playlist")
if not Config.CALL_STATUS \
and len(Config.playlist) >= 1:
await msg.edit("Downloading and Processing...")
await download(Config.playlist[0], msg)
await play()
elif (len(Config.playlist) == 1 and Config.CALL_STATUS):
await msg.edit("Downloading and Processing...")
await download(Config.playlist[0], msg)
await play()
elif message.command[0] == "fplay":
await msg.edit("Downloading and Processing...")
await download(Config.playlist[0], msg)
await play()
else:
await send_playlist()
await msg.delete()
pl=await get_playlist_str()
if message.chat.type == "private":
await message.reply(pl, reply_markup=await get_buttons() ,disable_web_page_preview=True)
elif not Config.LOG_GROUP and message.chat.type == "supergroup":
if Config.msg.get('playlist') is not None:
await Config.msg['playlist'].delete()
Config.msg['playlist']=await message.reply(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
await delete_messages([message])
for track in Config.playlist[:2]:
await download(track)
@Client.on_message(filters.command(["leave", f"leave@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def leave_voice_chat(_, m: Message):
if not Config.CALL_STATUS:
k=await m.reply("Not joined any voicechat.")
await delete_messages([m, k])
return
await leave_call()
k=await m.reply("Succesfully left videochat.")
await delete_messages([m, k])@Client.on_message(filters.command(["shuffle", f"shuffle@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def shuffle_play_list(client, m: Message):
if not Config.CALL_STATUS:
k = await m.reply("Not joined any voicechat.")
await delete_messages([m, k])
return
else:
if len(Config.playlist) > 2:
k=await m.reply_text(f"Playlist Shuffled.")
await shuffle_playlist()
await delete_messages([m, k])
else:
k=await m.reply_text(f"You cant shuffle playlist with less than 3 songs.")
await delete_messages([m, k])
@Client.on_message(filters.command(["clearplaylist", f"clearplaylist@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def clear_play_list(client, m: Message):
if not Config.playlist:
k = await m.reply("Playlist is empty.")
await delete_messages([m, k])
return
Config.playlist.clear()
k=await m.reply_text(f"Playlist Cleared.")
await clear_db_playlist(all=True)
if Config.IS_LOOP \
and not (Config.YPLAY or Config.CPLAY):
await start_stream()
else:
await leave_call()
await delete_messages([m, k])@Client.on_message(filters.command(["cplay", f"cplay@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def channel_play_list(client, m: Message):
with suppress(MessageIdInvalid, MessageNotModified):
k=await m.reply("Setting up for channel play..")
if " " in m.text:
you, me = m.text.split(" ", 1)
if me.startswith("-100"):
try:
me=int(me)
except:
await k.edit("Invalid chat id given")
await delete_messages([m, k])
return
try:
await client.get_chat_member(int(me), Config.USER_ID)
except (ValueError, PeerIdInvalid, ChannelInvalid):
LOGGER.error(f"Given channel is private and @{Config.BOT_USERNAME} is not an admin over there.", exc_info=True)
await k.edit(f"Given channel is private and @{Config.BOT_USERNAME} is not an admin over there. If channel is not private , please provide username of channel.")
await delete_messages([m, k])
return
except UserNotParticipant:
LOGGER.error("Given channel is private and USER account is not a member of channel.")
await k.edit("Given channel is private and USER account is not a member of channel.")
await delete_messages([m, k])
return
except Exception as e:
LOGGER.error(f"Errors occured while getting data abount channel - {e}", exc_info=True)
await k.edit(f"Something went wrong- {e}")
await delete_messages([m, k])
return
await k.edit("Searching files from channel, this may take some time, depending on number of files in the channel.")
st, msg = await c_play(me)
if st == False:
await m.edit(msg)
else:
await k.edit(f"Succesfully added {msg} files to playlist.")
elif me.startswith("@"):
me = me.replace("@", "")
try:
chat=await client.get_chat(me)
except Exception as e:
LOGGER.error(f"Errors occured while fetching info about channel - {e}", exc_info=True)
await k.edit(f"Errors occured while getting data about channel - {e}")
await delete_messages([m, k])
return
await k.edit("Searching files from channel, this may take some time, depending on number of files in the channel.")
st, msg=await c_play(me)
if st == False:
await k.edit(msg)
await delete_messages([m, k])
else:
await k.edit(f"Succesfully Added {msg} files from {chat.title} to playlist")
await delete_messages([m, k])
else:
await k.edit("The given channel is invalid. For private channels it should start with -100 and for public channels it should start with @\nExamples - `/cplay @VCPlayerFiles or /cplay -100125369865\n\nFor private channel, both bot and the USER account should be members of channel.")
await delete_messages([m, k])
else:
await k.edit("You didn't gave me any channel. Give me a channel id or username from which i should play files . \nFor private channels it should start with -100 and for public channels it should start with @\nExamples - `/cplay @VCPlayerFiles or /cplay -100125369865\n\nFor private channel, both bot and the USER account should be members of channel.")
await delete_messages([m, k])@Client.on_message(filters.command(["yplay", f"yplay@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def yt_play_list(client, m: Message):
with suppress(MessageIdInvalid, MessageNotModified):
if m.reply_to_message is not None and m.reply_to_message.document:
if m.reply_to_message.document.file_name != "YouTube_PlayList.json":
k=await m.reply("Invalid PlayList file given. Use @GetPlayListBot or search for a playlist in @DumpPlaylist to get a playlist file.")
await delete_messages([m, k])
return
ytplaylist=await m.reply_to_message.download()
status=await m.reply("Trying to get details from playlist.")
n=await import_play_list(ytplaylist)
if not n:
await status.edit("Errors Occured while importing playlist.")
await delete_messages([m, status])
return
if Config.SHUFFLE:
await shuffle_playlist()
pl=await get_playlist_str()
if m.chat.type == "private":
await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
elif not Config.LOG_GROUP and m.chat.type == "supergroup":
if Config.msg.get("playlist") is not None:
await Config.msg['playlist'].delete()
Config.msg['playlist']=await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
await delete_messages([m])
else:
await delete_messages([m, status])
else:
k=await m.reply("No playList file given. Use @GetPlayListBot or search for a playlist in @DumpPlaylist to get a playlist file.")
await delete_messages([m, k])
@Client.on_message(filters.command(["stream", f"stream@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def stream(client, m: Message):
with suppress(MessageIdInvalid, MessageNotModified):
msg=await m.reply("Checking the recived input.")
if m.reply_to_message and m.reply_to_message.text:
link=m.reply_to_message.text
elif " " in m.text:
text = m.text.split(" ", 1)
link = text[1]
else:
k = await msg.edit("Provide a link to stream!")
await delete_messages([m, k])
return
regex = r"^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&?"
match = re.match(regex,link)
if match:
stream_link=await get_link(link)
if not stream_link:
k = await msg.edit("This is an invalid link.")
await delete_messages([m, k])
return
else:
stream_link=link
try:
is_audio_ = await is_audio(stream_link)
except:
is_audio_ = False
LOGGER.error("Unable to get Audio properties within time.")
if not is_audio_:
k = await msg.edit("This is an invalid link, provide me a direct link or a youtube link.")
await delete_messages([m, k])
return
try:
dur=await get_duration(stream_link)
except:
dur=0
if dur != 0:
k = await msg.edit("This is not a live stream, Use /play command.")
await delete_messages([m, k])
return
k, msg_=await stream_from_link(stream_link)
if k == False:
k = await msg.edit(msg_)
await delete_messages([m, k])
return
if Config.msg.get('player'):
await Config.msg['player'].delete()
Config.msg['player']=await msg.edit(f"[Streaming]({stream_link}) Started. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ", disable_web_page_preview=True, reply_markup=await get_buttons())
await delete_messages([m])
admincmds=["yplay", "leave", "pause", "resume", "skip", "restart", "volume", "shuffle", "clearplaylist", "export", "import", "update", 'replay', 'logs', 'stream', 'fplay', 'schedule', 'record', 'slist', 'cancel', 'cancelall', 'vcpromote', 'vcdemote', 'refresh', 'rtitle', 'seek', 'vcmute', 'unmute',
f'stream@{Config.BOT_USERNAME}', f'logs@{Config.BOT_USERNAME}', f"replay@{Config.BOT_USERNAME}", f"yplay@{Config.BOT_USERNAME}", f"leave@{Config.BOT_USERNAME}", f"pause@{Config.BOT_USERNAME}", f"resume@{Config.BOT_USERNAME}", f"skip@{Config.BOT_USERNAME}",
f"restart@{Config.BOT_USERNAME}", f"volume@{Config.BOT_USERNAME}", f"shuffle@{Config.BOT_USERNAME}", f"clearplaylist@{Config.BOT_USERNAME}", f"export@{Config.BOT_USERNAME}", f"import@{Config.BOT_USERNAME}", f"update@{Config.BOT_USERNAME}",
f'play@{Config.BOT_USERNAME}', f'schedule@{Config.BOT_USERNAME}', f'record@{Config.BOT_USERNAME}', f'slist@{Config.BOT_USERNAME}', f'cancel@{Config.BOT_USERNAME}', f'cancelall@{Config.BOT_USERNAME}', f'vcpromote@{Config.BOT_USERNAME}',
f'vcdemote@{Config.BOT_USERNAME}', f'refresh@{Config.BOT_USERNAME}', f'rtitle@{Config.BOT_USERNAME}', f'seek@{Config.BOT_USERNAME}', f'mute@{Config.BOT_USERNAME}', f'vcunmute@{Config.BOT_USERNAME}'
]allcmd = ["play", "player", f"play@{Config.BOT_USERNAME}", f"player@{Config.BOT_USERNAME}"] + admincmds@Client.on_message(filters.command(admincmds) & ~admin_filter & chat_filter)
async def notforu(_, m: Message):
k = await _.send_cached_media(chat_id=m.chat.id, file_id="CAADBQADEgQAAtMJyFVJOe6-VqYVzAI", caption="You Are Not Authorized", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton('⚡️Join Here', url='https://t.me/subin_works')]]))
await delete_messages([m, k])@Client.on_message(filters.command(allcmd) & ~chat_filter & filters.group)
async def not_chat(_, m: Message):
if m.from_user is not None and m.from_user.id in Config.SUDO:
buttons = [
[
InlineKeyboardButton('⚡️Change CHAT', callback_data='set_new_chat'),
],
[
InlineKeyboardButton('No', callback_data='closesudo'),
]
]
await m.reply("This is not the group which i have been configured to play, Do you want to set this group as default CHAT?", reply_markup=InlineKeyboardMarkup(buttons))
await delete_messages([m])
else:
buttons = [
[
InlineKeyboardButton('⚡️Make Own Bot', url='https://github.com/subinps/VCPlayerBot'),
InlineKeyboardButton('🧩 Join Here', url='https://t.me/subin_works'),
]
]
await m.reply("<b>You can't use this bot in this group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.</b>", disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(buttons))↪️recorder.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from config import Config
from pyrogram import (
Client,
filters
)
from utils import (
chat_filter,
is_admin,
is_admin,
delete_messages,
recorder_settings,
sync_to_db
)
from pyrogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton
)admin_filter=filters.create(is_admin)
@Client.on_message(filters.command(["record", f"record@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def record_vc(bot, message):
await message.reply("Configure you VCPlayer Recording settings from hereㅤㅤ ㅤ", reply_markup=(await recorder_settings()))
await delete_messages([message])@Client.on_message(filters.command(["rtitle", f"rtitle@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def recording_title(bot, message):
m=await message.reply("Checking..")
if " " in message.text:
cmd, title = message.text.split(" ", 1)
else:
await m.edit("Give me a new title. Use /rtitle < Custom Title >\nUse <code>False</code> to revert to default title")
await delete_messages([message, m])
return if Config.DATABASE_URI:
await m.edit("Mongo DB Found, Setting up recording title...")
if title == "False":
await m.edit(f"Sucessfully removed custom recording title.")
Config.RECORDING_TITLE=False
await sync_to_db()
await delete_messages([message, m])
return
else:
Config.RECORDING_TITLE=title
await sync_to_db()
await m.edit(f"Succesfully changed recording title to {title}")
await delete_messages([message, m])
return
else:
if not Config.HEROKU_APP:
buttons = [[InlineKeyboardButton('Heroku API_KEY', url='https://dashboard.heroku.com/account/applications/authorizations/new'), InlineKeyboardButton('🗑 Close', callback_data='close'),]]
await m.edit(
text="No heroku app found, this command needs the following heroku vars to be set.\n\n1. <code>HEROKU_API_KEY</code>: Your heroku account api key.\n2. <code>HEROKU_APP_NAME</code>: Your heroku app name.",
reply_markup=InlineKeyboardMarkup(buttons))
await delete_messages([message])
return
config = Config.HEROKU_APP.config()
if title == "False":
if "RECORDING_TITLE" in config:
await m.edit(f"Sucessfully removed custom recording title. Now restarting..")
await delete_messages([message])
del config["RECORDING_TITLE"]
config["RECORDING_TITLE"] = None
else:
await m.edit(f"Its already default title, nothing was changed")
Config.RECORDING_TITLE=False
await delete_messages([message, m])
else:
await m.edit(f"Succesfully changed recording title to {title}, Now restarting")
await delete_messages([message])
config["RECORDING_TITLE"] = title↪️scheduler.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from utils import LOGGER import re import calendar from datetime import datetime from contextlib import suppress import pytz from config import Config from PTN import parse from youtube_search import YoutubeSearch from yt_dlp import YoutubeDL
from pyrogram import(
Client,
filters
)
from pyrogram.types import (
InlineKeyboardButton,
InlineKeyboardMarkup
)
from utils import (
delete_messages,
is_admin,
sync_to_db,
is_audio,
chat_filter,
scheduler,
is_ytdl_supported
)from pyrogram.types import (
InlineKeyboardButton,
InlineKeyboardMarkup
)
from pyrogram.errors import (
MessageIdInvalid,
MessageNotModified
)IST = pytz.timezone(Config.TIME_ZONE)
admin_filter=filters.create(is_admin)
@Client.on_message(filters.command(["schedule", f"schedule@{Config.BOT_USERNAME}"]) & chat_filter & admin_filter)
async def schedule_vc(bot, message):
with suppress(MessageIdInvalid, MessageNotModified):
type=""
yturl=""
ysearch=""
msg = await message.reply_text("⚡️ **Checking recived input..**")
if message.reply_to_message and message.reply_to_message.video:
await msg.edit("⚡️ **Checking Telegram Media...**")
type='video'
m_video = message.reply_to_message.video
elif message.reply_to_message and message.reply_to_message.document:
await msg.edit("⚡️ **Checking Telegram Media...**")
m_video = message.reply_to_message.document
type='video'
if not "video" in m_video.mime_type:
return await msg.edit("The given file is invalid")
elif message.reply_to_message and message.reply_to_message.audio:
#if not Config.IS_VIDEO:
#return await message.reply("Play from audio file is available only if Video Mode if turned off.\nUse /settings to configure ypur player.")
await msg.edit("⚡️ **Checking Telegram Media...**")
type='audio'
m_video = message.reply_to_message.audio
else:
if message.reply_to_message and message.reply_to_message.text:
query=message.reply_to_message.text
elif " " in message.text:
text = message.text.split(" ", 1)
query = text[1]
else:
await msg.edit("You Didn't gave me anything to schedule. Reply to a video or a youtube link or a direct link.")
await delete_messages([message, msg])
return
regex = r"^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&?"
match = re.match(regex,query)
if match:
type="youtube"
yturl=query
elif query.startswith("http"):
has_audio_ = await is_audio(query)
if not has_audio_:
if is_ytdl_supported(query):
type="ytdl_s"
url=query
else:
await msg.edit("This is an invalid link, provide me a direct link or a youtube link.")
await delete_messages([message, msg])
return
type="direct"
url=query
else:
type="query"
ysearch=query
if not message.from_user is None:
user=f"[{message.from_user.first_name}](tg://user?id={message.from_user.id}) - (Scheduled)"
user_id = message.from_user.id
else:
user="Anonymous - (Scheduled)"
user_id = "anonymous_admin"
now = datetime.now()
nyav = now.strftime("%d-%m-%Y-%H:%M:%S")
if type in ["video", "audio"]:
if type == "audio":
if m_video.title is None:
if m_video.file_name is None:
title_ = "Music"
else:
title_ = m_video.file_name
else:
title_ = m_video.title
if m_video.performer is not None:
title = f"{m_video.performer} - {title_}"
else:
title=title_
unique = f"{nyav}_{m_video.file_size}_audio"
else:
title=m_video.file_name
unique = f"{nyav}_{m_video.file_size}_video"
if Config.PTN:
ny = parse(title)
title_ = ny.get("title")
if title_:
title = title_
if title is None:
title = 'Music'
data={'1':title, '2':m_video.file_id, '3':"telegram", '4':user, '5':unique}
sid=f"{message.chat.id}_{msg.message_id}"
Config.SCHEDULED_STREAM[sid] = data
await sync_to_db()
elif type in ["youtube", "query", "ytdl_s"]:
if type=="youtube":
await msg.edit("⚡️ **Fetching Video From YouTube...**")
url=yturl
elif type=="query":
try:
await msg.edit("⚡️ **Fetching Video From YouTube...**")
ytquery=ysearch
results = YoutubeSearch(ytquery, max_results=1).to_dict()
url = f"https://youtube.com{results[0]['url_suffix']}"
title = results[0]["title"][:40]
except Exception as e:
await msg.edit(
"Song not found.\nTry inline mode.."
)
LOGGER.error(str(e), exc_info=True)
await delete_messages([message, msg])
return
elif type == "ytdl_s":
url=url
else:
return
ydl_opts = {
"quite": True,
"geo-bypass": True,
"nocheckcertificate": True
}
ydl = YoutubeDL(ydl_opts)
try:
info = ydl.extract_info(url, False)
except Exception as e:
LOGGER.error(e, exc_info=True)
await msg.edit(
f"YouTube Download Error ❌\nError:- {e}"
)
LOGGER.error(str(e))
await delete_messages([message, msg])
return
if type == "ytdl_s":
title = "Music"
try:
title=info['title']
except:
pass
else:
title = info["title"]
data={'1':title, '2':url, '3':"youtube", '4':user, '5':f"{nyav}_{user_id}"}
sid=f"{message.chat.id}_{msg.message_id}"
Config.SCHEDULED_STREAM[sid] = data
await sync_to_db()
elif type == "direct":
data={"1":"Music", '2':url, '3':"url", '4':user, '5':f"{nyav}_{user_id}"}
sid=f"{message.chat.id}_{msg.message_id}"
Config.SCHEDULED_STREAM[sid] = data
await sync_to_db()
if message.chat.type!='private' and message.from_user is None:
await msg.edit(
text="You cant schedule from here since you are an anonymous admin. Click the schedule button to schedule through private chat.",
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"Schedule", url=f"https://telegram.dog/{Config.BOT_USERNAME}?start=sch_{sid}"),
]
]
),)
await delete_messages([message, msg])
return
today = datetime.now(IST)
smonth=today.strftime("%B")
obj = calendar.Calendar()
thisday = today.day
year = today.year
month = today.month
m=obj.monthdayscalendar(year, month)
button=[]
button.append([InlineKeyboardButton(text=f"{str(smonth)} {str(year)}",callback_data=f"sch_month_choose_none_none")])
days=["Mon", "Tues", "Wed", "Thu", "Fri", "Sat", "Sun"]
f=[]
for day in days:
f.append(InlineKeyboardButton(text=f"{day}",callback_data=f"day_info_none"))
button.append(f)
for one in m:
f=[]
for d in one:
year_=year
if d < int(today.day):
year_ += 1
if d == 0:
k="\u2063"
d="none"
else:
k=d
f.append(InlineKeyboardButton(text=f"{k}",callback_data=f"sch_month_{year_}_{month}_{d}"))
button.append(f)
button.append([InlineKeyboardButton("Close", callback_data="schclose")])
await msg.edit(f"Choose the day of the month you want to schedule the voicechat.\nToday is {thisday} {smonth} {year}. Chooosing a date preceeding today will be considered as next year {year+1}", reply_markup=InlineKeyboardMarkup(button))
@Client.on_message(filters.command(["slist", f"slist@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def list_schedule(bot, message):
k=await message.reply("Checking schedules...")
if not Config.SCHEDULE_LIST:
await k.edit("Nothing scheduled to play.")
await delete_messages([k, message])
return
text="Current Schedules:\n\n"
s=Config.SCHEDULE_LIST
f=1
for sch in s:
details=Config.SCHEDULED_STREAM.get(sch['job_id'])
if not details['3']=="telegram":
text+=f"<b>{f}.</b> Title: [{details['1']}]({details['2']}) By {details['4']}\n"
else:
text+=f"<b>{f}.</b> Title: {details['1']} By {details['4']}\n"
date = sch['date']
f+=1
date_=((pytz.utc.localize(date, is_dst=None).astimezone(IST)).replace(tzinfo=None)).strftime("%b %d %Y, %I:%M %p")
text+=f"Shedule ID : <code>{sch['job_id']}</code>\nSchedule Date : <code>{date_}</code>\n\n" await k.edit(text, disable_web_page_preview=True)
await delete_messages([message])
@Client.on_message(filters.command(["cancel", f"cancel@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def delete_sch(bot, message):
with suppress(MessageIdInvalid, MessageNotModified):
m = await message.reply("Finding the scheduled stream..")
if " " in message.text:
cmd, job_id = message.text.split(" ", 1)
else:
buttons = [
[
InlineKeyboardButton('Cancel All Schedules', callback_data='schcancel'),
InlineKeyboardButton('No', callback_data='schclose'),
]
]
reply_markup = InlineKeyboardMarkup(buttons)
await m.edit("No Schedule ID specified!! Do you want to Cancel all scheduled streams? or you can find schedul id using /slist command.", reply_markup=reply_markup)
await delete_messages([message])
return
data=Config.SCHEDULED_STREAM.get(job_id)
if not data:
await m.edit("You gave me an invalid schedule ID, check again and send.")
await delete_messages([message, m])
return
del Config.SCHEDULED_STREAM[job_id]
k=scheduler.get_job(job_id, jobstore=None)
if k:
scheduler.remove_job(job_id, jobstore=None)
old=list(filter(lambda k: k['job_id'] == job_id, Config.SCHEDULE_LIST))
if old:
for old_ in old:
Config.SCHEDULE_LIST.remove(old_)
await sync_to_db()
await m.edit(f"Succesfully deleted {data['1']} from scheduled list.")
await delete_messages([message, m])
@Client.on_message(filters.command(["cancelall", f"cancelall@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter)
async def delete_all_sch(bot, message):
buttons = [
[
InlineKeyboardButton('Cancel All Schedules', callback_data='schcancel'),
InlineKeyboardButton('No', callback_data='schclose'),
]
]
reply_markup = InlineKeyboardMarkup(buttons)
await message.reply("Do you want to cancel all the scheduled streams?ㅤㅤㅤㅤ ㅤ", reply_markup=reply_markup)
await delete_messages([message])✉подключаемые модули пользователя
↪️group_call.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import LOGGER
from pyrogram.errors import BotInlineDisabled
from pyrogram import Client, filters
from config import Config
from user import group_call
import time
from asyncio import sleep
from pyrogram.raw.base import Update
from pyrogram.raw.functions.channels import GetFullChannel
from pytgcalls import PyTgCalls
from pytgcalls.types import Update
from pyrogram.raw.types import (
UpdateGroupCall,
GroupCallDiscarded,
UpdateGroupCallParticipants
)
from pytgcalls.types.groups import (
JoinedVoiceChat,
LeftVoiceChat
)
from pytgcalls.types.stream import (
PausedStream,
ResumedStream,
MutedStream,
UnMutedStream,
StreamAudioEnded,
StreamVideoEnded
)
from utils import (
start_record_stream,
stop_recording,
edit_title,
stream_from_link,
leave_call,
start_stream,
skip,
sync_to_db,
scheduler
)async def is_reply(_, client, message):
if Config.REPLY_PM:
return True
else:
return False
reply_filter=filters.create(is_reply)DUMBED=[]
async def dumb_it(_, client, message):
if Config.RECORDING_DUMP and Config.LISTEN:
return True
else:
return False
rec_filter=filters.create(dumb_it)@Client.on_message(reply_filter & filters.private & ~filters.bot & filters.incoming & ~filters.service & ~filters.me & ~filters.chat([777000, 454000]))
async def reply(client, message):
try:
inline = await client.get_inline_bot_results(Config.BOT_USERNAME, "ETHO_ORUTHAN_PM_VANNU")
m=await client.send_inline_bot_result(
message.chat.id,
query_id=inline.query_id,
result_id=inline.results[0].id,
hide_via=True
)
old=Config.msg.get(message.chat.id)
if old:
await client.delete_messages(message.chat.id, [old["msg"], old["s"]])
Config.msg[message.chat.id]={"msg":m.updates[1].message.id, "s":message.message_id}
except BotInlineDisabled:
LOGGER.error(f"Error: Inline Mode for @{Config.BOT_USERNAME} is not enabled. Enable from @Botfather to enable PM Permit.")
await message.reply(f"{Config.REPLY_MESSAGE}\n\n<b>You can't use this bot in your group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.</b>", disable_web_page_preview=True)
except Exception as e:
LOGGER.error(e, exc_info=True)
pass
@Client.on_message(filters.private & filters.media & filters.me & rec_filter)
async def dumb_to_log(client, message):
if message.video and message.video.file_name == "record.mp4":
await message.copy(int(Config.RECORDING_DUMP))
DUMBED.append("video")
if message.audio and message.audio.file_name == "record.ogg":
await message.copy(int(Config.RECORDING_DUMP))
DUMBED.append("audio")
if Config.IS_VIDEO_RECORD:
if len(DUMBED) == 2:
DUMBED.clear()
Config.LISTEN=False
else:
if len(DUMBED) == 1:
DUMBED.clear()
Config.LISTEN=False
@Client.on_message(filters.service & filters.chat(Config.CHAT))
async def service_msg(client, message):
if message.service == 'voice_chat_started':
Config.IS_ACTIVE=True
k=scheduler.get_job(str(Config.CHAT), jobstore=None) #scheduled records
if k:
await start_record_stream()
LOGGER.info("Resuming recording..")
elif Config.WAS_RECORDING:
LOGGER.info("Previous recording was ended unexpectedly, Now resuming recordings.")
await start_record_stream()#for unscheduled
a = await client.send(
GetFullChannel(
channel=(
await client.resolve_peer(
Config.CHAT
)
)
)
)
if a.full_chat.call is not None:
Config.CURRENT_CALL=a.full_chat.call.id
LOGGER.info("Voice chat started.")
await sync_to_db()
elif message.service == 'voice_chat_scheduled':
LOGGER.info("VoiceChat Scheduled")
Config.IS_ACTIVE=False
Config.HAS_SCHEDULE=True
await sync_to_db()
elif message.service == 'voice_chat_ended':
Config.IS_ACTIVE=False
LOGGER.info("Voicechat ended")
Config.CURRENT_CALL=None
if Config.IS_RECORDING:
Config.WAS_RECORDING=True
await stop_recording()
await sync_to_db()
else:
pass@Client.on_raw_update()
async def handle_raw_updates(client: Client, update: Update, user: dict, chat: dict):
if isinstance(update, UpdateGroupCallParticipants):
if not Config.CURRENT_CALL:
a = await client.send(
GetFullChannel(
channel=(
await client.resolve_peer(
Config.CHAT
)
)
)
)
if a.full_chat.call is not None:
Config.CURRENT_CALL=a.full_chat.call.id
if Config.CURRENT_CALL and update.call.id == Config.CURRENT_CALL:
all=update.participants
old=list(filter(lambda k:k.peer.user_id if hasattr(k.peer,'user_id') else k.peer.channel_id == Config.USER_ID, all))
if old:
for me in old:
if me.volume:
Config.VOLUME=round(int(me.volume)/100)
if isinstance(update, UpdateGroupCall) and (update.chat_id == int(-1000000000000-Config.CHAT)):
if update.call is None:
Config.IS_ACTIVE = False
Config.CURRENT_CALL=None
LOGGER.warning("No Active Group Calls Found.")
if Config.IS_RECORDING:
Config.WAS_RECORDING=True
await stop_recording()
LOGGER.warning("Group call was ended and hence stoping recording.")
Config.HAS_SCHEDULE = False
await sync_to_db()
return else:
call=update.call
if isinstance(call, GroupCallDiscarded):
Config.CURRENT_CALL=None
Config.IS_ACTIVE=False
if Config.IS_RECORDING:
Config.WAS_RECORDING=True
await stop_recording()
LOGGER.warning("Group Call Was ended")
Config.CALL_STATUS = False
await sync_to_db()
return
Config.IS_ACTIVE=True
Config.CURRENT_CALL=call.id
if Config.IS_RECORDING and not call.record_video_active:
Config.LISTEN=True
await stop_recording()
LOGGER.warning("Recording was ended by user, hence stopping the schedules.")
return
if call.schedule_date:
Config.HAS_SCHEDULE=True
else:
Config.HAS_SCHEDULE=False
await sync_to_db()
@group_call.on_raw_update()
async def handler(client: PyTgCalls, update: Update):
if isinstance(update, JoinedVoiceChat):
Config.CALL_STATUS = True
if Config.EDIT_TITLE:
await edit_title()
who=await group_call.get_participants(Config.CHAT)
you=list(filter(lambda k:k.user_id == Config.USER_ID, who))
if you:
for me in you:
if me.volume:
Config.VOLUME=round(int(me.volume))
elif isinstance(update, LeftVoiceChat):
Config.CALL_STATUS = False
elif isinstance(update, PausedStream):
Config.DUR['PAUSE'] = time.time()
Config.PAUSE=True
elif isinstance(update, ResumedStream):
pause=Config.DUR.get('PAUSE')
if pause:
diff = time.time() - pause
start=Config.DUR.get('TIME')
if start:
Config.DUR['TIME']=start+diff
Config.PAUSE=False
elif isinstance(update, MutedStream):
Config.MUTED = True
elif isinstance(update, UnMutedStream):
Config.MUTED = False@group_call.on_stream_end()
async def handler(client: PyTgCalls, update: Update):
if isinstance(update, StreamAudioEnded) or isinstance(update, StreamVideoEnded):
if not Config.STREAM_END.get("STATUS"):
Config.STREAM_END["STATUS"]=str(update)
if Config.STREAM_LINK and len(Config.playlist) == 0:
if Config.IS_LOOP:
await stream_from_link(Config.STREAM_LINK)
else:
await leave_call()
elif not Config.playlist:
if Config.IS_LOOP:
await start_stream()
else:
await leave_call()
else:
await skip()
await sleep(15) #wait for max 15 sec
try:
del Config.STREAM_END["STATUS"]
except:
pass
else:
try:
del Config.STREAM_END["STATUS"]
except:
pass✉утилиты
↪️__init__.py
from .logger import LOGGER from .debug import debug from .database import db from .utils import * from .pyro_dl import Downloader
↪️database.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from .logger import LOGGER import motor.motor_asyncio from config import Config
class Database:
def __init__(self):
self._client = motor.motor_asyncio.AsyncIOMotorClient(Config.DATABASE_URI)
self.db = self._client[Config.DATABASE_NAME]
self.col = self.db.config
self.playlist = self.db.playlist
def new_config(self, name, value, dvalue):
return dict(
name = name,
dvalue = dvalue,
value = value,
)
def add_config(self, name, value, dvalue=None):
config = self.new_config(name, value, dvalue)
self.col.insert_one(config)
def new_song(self, id_, song):
return dict(
id = id_,
song = song,
) def add_to_playlist(self, id_, song):
song_ = self.new_song(id_, song)
self.playlist.insert_one(song_)
async def is_saved(self, name):
config = await self.col.find_one({'name':name})
return True if config else False
async def edit_config(self, name, value):
await self.col.update_one({'name': name}, {'$set': {'value': value}})
async def edit_default(self, name, dvalue):
await self.col.update_one({'name': name}, {'$set': {'dvalue': dvalue}})
async def get_default(self, name):
config = await self.col.find_one({'name':name})
return config.get('dvalue') async def get_config(self, name):
config = await self.col.find_one({'name':name})
return config.get('value') async def del_config(self, name):
await self.col.delete_one({'name':name})
return
async def is_in_playlist(self, id_):
song = await self.playlist.find_one({'id':id_})
return True if song else False
async def get_song(self, id_):
song_ = await self.playlist.find_one({'id':id_})
return song_.get('song') async def del_song(self, id_):
await self.playlist.delete_one({'id':id_})
return async def clear_playlist(self):
await self.playlist.drop()
return
async def get_playlist(self):
k = self.playlist.find({})
l=[]
async for song in k:
song_ = {int(k):v for k,v in song['song'].items()}
l.append(song_)
return ldb=Database()
↪️debug.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from .logger import LOGGER
from config import Config
import os
import time
from threading import Thread
import sys
if Config.DATABASE_URI:
from .database import db
from pyrogram import (
Client,
filters
)
from pyrogram.errors import (
MessageIdInvalid,
MessageNotModified
)
from pyrogram.types import (
InlineKeyboardButton,
InlineKeyboardMarkup,
Message
)
from contextlib import suppressdebug = Client(
"Debug",
Config.API_ID,
Config.API_HASH,
bot_token=Config.BOT_TOKEN,
)
@debug.on_message(filters.command(['env', f"env@{Config.BOT_USERNAME}", "config", f"config@{Config.BOT_USERNAME}"]) & filters.private & filters.user(Config.ADMINS))
async def set_heroku_var(client, message):
if message.from_user.id not in Config.SUDO:
return await message.reply(f"/env command can only be used by creator of the bot, ({str(Config.SUDO)})")
with suppress(MessageIdInvalid, MessageNotModified):
m = await message.reply("Checking config vars..")
if " " in message.text:
cmd, env = message.text.split(" ", 1)
if not "=" in env:
await m.edit("You should specify the value for env.\nExample: /env CHAT=-100213658211")
return
var, value = env.split("=", 1)
else:
await m.edit("You haven't provided any value for env, you should follow the correct format.\nExample: <code>/env CHAT=-1020202020202</code> to change or set CHAT var.\n<code>/env REPLY_MESSAGE= <code>To delete REPLY_MESSAGE.")
return if Config.DATABASE_URI and var in ["STARTUP_STREAM", "CHAT", "LOG_GROUP", "REPLY_MESSAGE", "DELAY", "RECORDING_DUMP"]:
await m.edit("Mongo DB Found, Setting up config vars...")
if not value:
await m.edit(f"No value for env specified. Trying to delete env {var}.")
if var in ["STARTUP_STREAM", "CHAT", "DELAY"]:
await m.edit("This is a mandatory var and cannot be deleted.")
return
await edit_config(var, False)
await m.edit(f"Sucessfully deleted {var}")
return
else:
if var in ["CHAT", "LOG_GROUP", "RECORDING_DUMP"]:
try:
value=int(value)
except:
await m.edit("You should give me a chat id . It should be an interger.")
return
if var == "CHAT":
Config.ADMIN_CACHE=False
Config.CHAT=int(value)
await edit_config(var, int(value))
await m.edit(f"Succesfully changed {var} to {value}")
return
else:
if var == "STARTUP_STREAM":
Config.STREAM_SETUP=False
await edit_config(var, value)
await m.edit(f"Succesfully changed {var} to {value}")
return
else:
if not Config.HEROKU_APP:
buttons = [[InlineKeyboardButton('Heroku API_KEY', url='https://dashboard.heroku.com/account/applications/authorizations/new'), InlineKeyboardButton('🗑 Close', callback_data='close'),]]
await m.edit(
text="No heroku app found, this command needs the following heroku vars to be set.\n\n1. <code>HEROKU_API_KEY</code>: Your heroku account api key.\n2. <code>HEROKU_APP_NAME</code>: Your heroku app name.",
reply_markup=InlineKeyboardMarkup(buttons))
return
config = Config.HEROKU_APP.config()
if not value:
await m.edit(f"No value for env specified. Trying to delete env {var}.")
if var in ["STARTUP_STREAM", "CHAT", "DELAY", "API_ID", "API_HASH", "BOT_TOKEN", "SESSION_STRING", "ADMINS"]:
await m.edit("These are mandatory vars and cannot be deleted.")
return
if var in config:
await m.edit(f"Sucessfully deleted {var}")
await m.edit("Now restarting the app to make changes.")
if Config.DATABASE_URI:
msg = {"msg_id":m.message_id, "chat_id":m.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
del config[var]
config[var] = None
else:
k = await m.edit(f"No env named {var} found. Nothing was changed.")
return
if var in config:
await m.edit(f"Variable already found. Now edited to {value}")
else:
await m.edit(f"Variable not found, Now setting as new var.")
await m.edit(f"Succesfully set {var} with value {value}, Now Restarting to take effect of changes...")
if Config.DATABASE_URI:
msg = {"msg_id":m.message_id, "chat_id":m.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
config[var] = str(value)@debug.on_message(filters.command(["restart", f"restart@{Config.BOT_USERNAME}"]) & filters.private & filters.user(Config.ADMINS))
async def update(bot, message):
m=await message.reply("Restarting with new changes..")
if Config.DATABASE_URI:
msg = {"msg_id":m.message_id, "chat_id":m.chat.id}
if not await db.is_saved("RESTART"):
db.add_config("RESTART", msg)
else:
await db.edit_config("RESTART", msg)
if Config.HEROKU_APP:
Config.HEROKU_APP.restart()
else:
Thread(
target=stop_and_restart()
).start()@debug.on_message(filters.command(["clearplaylist", f"clearplaylist@{Config.BOT_USERNAME}"]) & filters.private & filters.user(Config.ADMINS))
async def clear_play_list(client, m: Message):
if not Config.playlist:
k = await m.reply("Playlist is empty.")
return
Config.playlist.clear()
k=await m.reply_text(f"Playlist Cleared.")
await clear_db_playlist(all=True)
@debug.on_message(filters.command(["skip", f"skip@{Config.BOT_USERNAME}"]) & filters.private & filters.user(Config.ADMINS))
async def skip_track(_, m: Message):
msg=await m.reply('trying to skip from queue..')
if not Config.playlist:
await msg.edit("Playlist is Empty.")
return
if len(m.command) == 1:
old_track = Config.playlist.pop(0)
await clear_db_playlist(song=old_track)
else:
#https://github.com/callsmusic/tgvc-userbot/blob/dev/plugins/vc/player.py#L268-L288
try:
items = list(dict.fromkeys(m.command[1:]))
items = [int(x) for x in items if x.isdigit()]
items.sort(reverse=True)
for i in items:
if 2 <= i <= (len(Config.playlist) - 1):
await msg.edit(f"Succesfully Removed from Playlist- {i}. **{Config.playlist[i][1]}**")
await clear_db_playlist(song=Config.playlist[i])
Config.playlist.pop(i)
else:
await msg.edit(f"You cant skip first two songs- {i}")
except (ValueError, TypeError):
await msg.edit("Invalid input")
pl=await get_playlist_str()
await msg.edit(pl, disable_web_page_preview=True)
@debug.on_message(filters.command(['logs', f"logs@{Config.BOT_USERNAME}"]) & filters.private & filters.user(Config.ADMINS))
async def get_logs(client, message):
m=await message.reply("Checking logs..")
if os.path.exists("botlog.txt"):
await message.reply_document('botlog.txt', caption="Bot Logs")
await m.delete()
else:
k = await m.edit("No log files found.")@debug.on_message(filters.text & filters.private)
async def reply_else(bot, message):
await message.reply(f"Development mode is activated.\nThis occures when there are some errors in startup of the bot.\nOnly Configuration commands works in development mode.\nAvailabe commands are /env, /skip, /clearplaylist and /restart and /logs\n\n**The cause for activation of development mode was**\n\n`{str(Config.STARTUP_ERROR)}`")def stop_and_restart():
os.system("git pull")
time.sleep(10)
os.execl(sys.executable, sys.executable, *sys.argv)async def get_playlist_str():
if not Config.playlist:
pl = f"🔈 Playlist is empty.)ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
if len(Config.playlist)>=25:
tplaylist=Config.playlist[:25]
pl=f"Listing first 25 songs of total {len(Config.playlist)} songs.\n"
pl += f"▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n" + "\n".join([
f"**{i}**. **🎸{x[1]}**\n 👤**Requested by:** {x[4]}"
for i, x in enumerate(tplaylist)
])
tplaylist.clear()
else:
pl = f"▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n" + "\n".join([
f"**{i}**. **🎸{x[1]}**\n 👤**Requested by:** {x[4]}\n"
for i, x in enumerate(Config.playlist)
])
return plasync def sync_to_db():
if Config.DATABASE_URI:
await check_db()
await db.edit_config("ADMINS", Config.ADMINS)
await db.edit_config("IS_VIDEO", Config.IS_VIDEO)
await db.edit_config("IS_LOOP", Config.IS_LOOP)
await db.edit_config("REPLY_PM", Config.REPLY_PM)
await db.edit_config("ADMIN_ONLY", Config.ADMIN_ONLY)
await db.edit_config("SHUFFLE", Config.SHUFFLE)
await db.edit_config("EDIT_TITLE", Config.EDIT_TITLE)
await db.edit_config("CHAT", Config.CHAT)
await db.edit_config("SUDO", Config.SUDO)
await db.edit_config("REPLY_MESSAGE", Config.REPLY_MESSAGE)
await db.edit_config("LOG_GROUP", Config.LOG_GROUP)
await db.edit_config("STREAM_URL", Config.STREAM_URL)
await db.edit_config("DELAY", Config.DELAY)
await db.edit_config("SCHEDULED_STREAM", Config.SCHEDULED_STREAM)
await db.edit_config("SCHEDULE_LIST", Config.SCHEDULE_LIST)
await db.edit_config("IS_VIDEO_RECORD", Config.IS_VIDEO_RECORD)
await db.edit_config("IS_RECORDING", Config.IS_RECORDING)
await db.edit_config("WAS_RECORDING", Config.WAS_RECORDING)
await db.edit_config("PORTRAIT", Config.PORTRAIT)
await db.edit_config("RECORDING_DUMP", Config.RECORDING_DUMP)
await db.edit_config("RECORDING_TITLE", Config.RECORDING_TITLE)
await db.edit_config("HAS_SCHEDULE", Config.HAS_SCHEDULE)async def sync_from_db():
if Config.DATABASE_URI:
await check_db()
Config.ADMINS = await db.get_config("ADMINS")
Config.IS_VIDEO = await db.get_config("IS_VIDEO")
Config.IS_LOOP = await db.get_config("IS_LOOP")
Config.REPLY_PM = await db.get_config("REPLY_PM")
Config.ADMIN_ONLY = await db.get_config("ADMIN_ONLY")
Config.SHUFFLE = await db.get_config("SHUFFLE")
Config.EDIT_TITLE = await db.get_config("EDIT_TITLE")
Config.CHAT = int(await db.get_config("CHAT"))
Config.playlist = await db.get_playlist()
Config.LOG_GROUP = await db.get_config("LOG_GROUP")
Config.SUDO = await db.get_config("SUDO")
Config.REPLY_MESSAGE = await db.get_config("REPLY_MESSAGE")
Config.DELAY = await db.get_config("DELAY")
Config.STREAM_URL = await db.get_config("STREAM_URL")
Config.SCHEDULED_STREAM = await db.get_config("SCHEDULED_STREAM")
Config.SCHEDULE_LIST = await db.get_config("SCHEDULE_LIST")
Config.IS_VIDEO_RECORD = await db.get_config('IS_VIDEO_RECORD')
Config.IS_RECORDING = await db.get_config("IS_RECORDING")
Config.WAS_RECORDING = await db.get_config('WAS_RECORDING')
Config.PORTRAIT = await db.get_config("PORTRAIT")
Config.RECORDING_DUMP = await db.get_config("RECORDING_DUMP")
Config.RECORDING_TITLE = await db.get_config("RECORDING_TITLE")
Config.HAS_SCHEDULE = await db.get_config("HAS_SCHEDULE")async def add_to_db_playlist(song):
if Config.DATABASE_URI:
song_={str(k):v for k,v in song.items()}
db.add_to_playlist(song[5], song_)async def clear_db_playlist(song=None, all=False):
if Config.DATABASE_URI:
if all:
await db.clear_playlist()
else:
await db.del_song(song[5])async def check_db():
if not await db.is_saved("ADMINS"):
db.add_config("ADMINS", Config.ADMINS)
if not await db.is_saved("IS_VIDEO"):
db.add_config("IS_VIDEO", Config.IS_VIDEO)
if not await db.is_saved("IS_LOOP"):
db.add_config("IS_LOOP", Config.IS_LOOP)
if not await db.is_saved("REPLY_PM"):
db.add_config("REPLY_PM", Config.REPLY_PM)
if not await db.is_saved("ADMIN_ONLY"):
db.add_config("ADMIN_ONLY", Config.ADMIN_ONLY)
if not await db.is_saved("SHUFFLE"):
db.add_config("SHUFFLE", Config.SHUFFLE)
if not await db.is_saved("EDIT_TITLE"):
db.add_config("EDIT_TITLE", Config.EDIT_TITLE)
if not await db.is_saved("CHAT"):
db.add_config("CHAT", Config.CHAT)
if not await db.is_saved("SUDO"):
db.add_config("SUDO", Config.SUDO)
if not await db.is_saved("REPLY_MESSAGE"):
db.add_config("REPLY_MESSAGE", Config.REPLY_MESSAGE)
if not await db.is_saved("STREAM_URL"):
db.add_config("STREAM_URL", Config.STREAM_URL)
if not await db.is_saved("DELAY"):
db.add_config("DELAY", Config.DELAY)
if not await db.is_saved("LOG_GROUP"):
db.add_config("LOG_GROUP", Config.LOG_GROUP)
if not await db.is_saved("SCHEDULED_STREAM"):
db.add_config("SCHEDULED_STREAM", Config.SCHEDULED_STREAM)
if not await db.is_saved("SCHEDULE_LIST"):
db.add_config("SCHEDULE_LIST", Config.SCHEDULE_LIST)
if not await db.is_saved("IS_VIDEO_RECORD"):
db.add_config("IS_VIDEO_RECORD", Config.IS_VIDEO_RECORD)
if not await db.is_saved("PORTRAIT"):
db.add_config("PORTRAIT", Config.PORTRAIT)
if not await db.is_saved("IS_RECORDING"):
db.add_config("IS_RECORDING", Config.IS_RECORDING)
if not await db.is_saved('WAS_RECORDING'):
db.add_config('WAS_RECORDING', Config.WAS_RECORDING)
if not await db.is_saved("RECORDING_DUMP"):
db.add_config("RECORDING_DUMP", Config.RECORDING_DUMP)
if not await db.is_saved("RECORDING_TITLE"):
db.add_config("RECORDING_TITLE", Config.RECORDING_TITLE)
if not await db.is_saved('HAS_SCHEDULE'):
db.add_config("HAS_SCHEDULE", Config.HAS_SCHEDULE)async def edit_config(var, value):
if var == "STARTUP_STREAM":
Config.STREAM_URL = value
elif var == "CHAT":
Config.CHAT = int(value)
elif var == "LOG_GROUP":
Config.LOG_GROUP = int(value)
elif var == "DELAY":
Config.DELAY = int(value)
elif var == "REPLY_MESSAGE":
Config.REPLY_MESSAGE = value
elif var == "RECORDING_DUMP":
Config.RECORDING_DUMP = value
await sync_to_db()↪️logger.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from logging.handlers import RotatingFileHandler import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s - %(levelname)s] - %(name)s - %(message)s",
datefmt='%d-%b-%y %H:%M:%S',
handlers=[
RotatingFileHandler(
"botlog.txt",
maxBytes=50000000,
backupCount=10
),
logging.StreamHandler()
]
)logging.getLogger("pyrogram").setLevel(logging.ERROR)
logging.getLogger("pytgcalls").setLevel(logging.ERROR)
logging.getLogger("apscheduler").setLevel(logging.ERROR)LOGGER=logging.getLogger(__name__)
↪️pyro_dl.py
# Pyrogram - Telegram MTProto API Client Library for Python # Copyright (C) 2017-2021 Dan <https://github.com/delivrance> # # This file is part of Pyrogram. # # Pyrogram is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Pyrogram is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
#https://github.com/pyrogram/pyrogram/blob/4f585c156c1a2c6707793a8ad7f2f111515ca23b/pyrogram/client.py#L492-L518 #https://github.com/pyrogram/pyrogram/blob/4f585c156c1a2c6707793a8ad7f2f111515ca23b/pyrogram/client.py#L806-1044
#Pyrogram downloader modified to suit my needs. #Downloads the file from telegram servers and retures the path of the file without waiting for the whole download to finish. #Copyright (C) @subinps
from .logger import LOGGER
import asyncio
import os
import re
import asyncio
import os
import time
from datetime import datetime
from hashlib import sha256
from bot import bot
import pyrogram
from pyrogram import raw
from pyrogram import utils
from pyrogram.crypto import aes
from pyrogram.errors import (
VolumeLocNotFound,
AuthBytesInvalid
)
from pyrogram.session import(
Auth,
Session
)
from pyrogram.file_id import(
FileId,
FileType,
ThumbnailSource
)
from pyrogram.file_id import (
FileId,
FileType,
PHOTO_TYPES
)DEFAULT_DOWNLOAD_DIR = "downloads/"
class Downloader():
def __init__(
self,
):
super().__init__()
self.client = bot async def pyro_dl(self, file_id):
file_id_obj = FileId.decode(file_id)
file_type = file_id_obj.file_type
mime_type = ""
date = 0
file_name = "" directory, file_name = os.path.split(file_name)
if not os.path.isabs(file_name):
directory = self.client.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)
if not file_name:
guessed_extension = self.client.guess_extension(mime_type) if file_type in PHOTO_TYPES:
extension = ".jpg"
elif file_type == FileType.VOICE:
extension = guessed_extension or ".ogg"
elif file_type in (FileType.VIDEO, FileType.ANIMATION, FileType.VIDEO_NOTE):
extension = guessed_extension or ".mp4"
elif file_type == FileType.DOCUMENT:
extension = guessed_extension or ".zip"
elif file_type == FileType.STICKER:
extension = guessed_extension or ".webp"
elif file_type == FileType.AUDIO:
extension = guessed_extension or ".mp3"
else:
extension = ".unknown" file_name = "{}_{}_{}{}".format(
FileType(file_id_obj.file_type).name.lower(),
datetime.fromtimestamp(date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"),
self.client.rnd_id(),
extension
)
final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name)))
os.makedirs(directory, exist_ok=True)
downloaderr = self.handle_download(file_id_obj, final_file_path)
asyncio.get_event_loop().create_task(downloaderr)
return final_file_path
async def handle_download(self, file_id_obj, final_file_path):
try:
await self.get_file(
file_id=file_id_obj,
filename=final_file_path
)
except Exception as e:
LOGGER.error(str(e), exc_info=True) try:
os.remove(final_file_path)
except OSError:
pass
else:
return final_file_path or None async def get_file(
self,
file_id: FileId,
filename: str,
) -> str:
dc_id = file_id.dc_id async with self.client.media_sessions_lock:
session = self.client.media_sessions.get(dc_id, None) if session is None:
if dc_id != await self.client.storage.dc_id():
session = Session(
self.client, dc_id, await Auth(self.client, dc_id, await self.client.storage.test_mode()).create(),
await self.client.storage.test_mode(), is_media=True
)
await session.start() for _ in range(3):
exported_auth = await self.client.send(
raw.functions.auth.ExportAuthorization(
dc_id=dc_id
)
) try:
await session.send(
raw.functions.auth.ImportAuthorization(
id=exported_auth.id,
bytes=exported_auth.bytes
)
)
except AuthBytesInvalid:
continue
else:
break
else:
await session.stop()
raise AuthBytesInvalid
else:
session = Session(
self.client, dc_id, await self.client.storage.auth_key(),
await self.client.storage.test_mode(), is_media=True
)
await session.start()self.client.media_sessions[dc_id] = session
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
) limit = 1024 * 1024
offset = 0
file_name = "" try:
r = await session.send(
raw.functions.upload.GetFile(
location=location,
offset=offset,
limit=limit
),
sleep_threshold=30
) if isinstance(r, raw.types.upload.File):
#with tempfile.NamedTemporaryFile("wb", delete=False) as f:
with open(filename, 'wb') as f:
file_name = filename
while True:
chunk = r.bytes if not chunk:
breakf.write(chunk)
offset += limit
r = await session.send(
raw.functions.upload.GetFile(
location=location,
offset=offset,
limit=limit
),
sleep_threshold=30
) elif isinstance(r, raw.types.upload.FileCdnRedirect):
async with self.client.media_sessions_lock:
cdn_session = self.client.media_sessions.get(r.dc_id, None) if cdn_session is None:
cdn_session = Session(
self.client, r.dc_id, await Auth(self.client, r.dc_id, await self.client.storage.test_mode()).create(),
await self.client.storage.test_mode(), is_media=True, is_cdn=True
)await cdn_session.start()
self.client.media_sessions[r.dc_id] = cdn_session
try:
with open(filename, 'wb') as f:
file_name = f
while True:
r2 = await cdn_session.send(
raw.functions.upload.GetCdnFile(
file_token=r.file_token,
offset=offset,
limit=limit
)
) if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded):
try:
await session.send(
raw.functions.upload.ReuploadCdnFile(
file_token=r.file_token,
request_token=r2.request_token
)
)
except VolumeLocNotFound:
break
else:
continuechunk = r2.bytes
# https://core.telegram.org/cdn#decrypting-files
decrypted_chunk = aes.ctr256_decrypt(
chunk,
r.encryption_key,
bytearray(
r.encryption_iv[:-4]
+ (offset // 16).to_bytes(4, "big")
)
) hashes = await session.send(
raw.functions.upload.GetCdnFileHashes(
file_token=r.file_token,
offset=offset
)
) # https://core.telegram.org/cdn#verifying-files
for i, h in enumerate(hashes):
cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)]
assert h.hash == sha256(cdn_chunk).digest(), f"Invalid CDN hash part {i}"f.write(decrypted_chunk)
offset += limit
if len(chunk) < limit:
break
except Exception as e:
LOGGER.error(e, exc_info=True)
raise e
except Exception as e:
if not isinstance(e, pyrogram.StopTransmission):
LOGGER.error(str(e), exc_info=True)
try:
os.remove(file_name)
except OSError:
pass return ""
else:
return file_name↪️utils.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from .logger import LOGGER
try:
from pyrogram.raw.types import InputChannel
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.jobstores.mongodb import MongoDBJobStore
from apscheduler.jobstores.base import ConflictingIdError
from pyrogram.raw.functions.channels import GetFullChannel
from pytgcalls import StreamType
import yt_dlp
from pyrogram import filters
from pymongo import MongoClient
from datetime import datetime
from threading import Thread
from math import gcd
from .pyro_dl import Downloader
from config import Config
from asyncio import sleep
from bot import bot
from PTN import parse
import subprocess
import asyncio
import json
import random
import time
import sys
import os
import math
from pyrogram.errors.exceptions.bad_request_400 import (
BadRequest,
ScheduleDateInvalid,
PeerIdInvalid,
ChannelInvalid
)
from pytgcalls.types.input_stream import (
AudioVideoPiped,
AudioPiped,
AudioImagePiped
)
from pytgcalls.types.input_stream import (
AudioParameters,
VideoParameters
)
from pyrogram.types import (
InlineKeyboardButton,
InlineKeyboardMarkup,
Message
)
from pyrogram.raw.functions.phone import (
EditGroupCallTitle,
CreateGroupCall,
ToggleGroupCallRecord,
StartScheduledGroupCall
)
from pytgcalls.exceptions import (
GroupCallNotFound,
NoActiveGroupCall,
InvalidVideoProportion
)
from PIL import (
Image,
ImageFont,
ImageDraw
) from user import (
group_call,
USER
)
except ModuleNotFoundError:
import os
import sys
import subprocess
file=os.path.abspath("requirements.txt")
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', file, '--upgrade'])
os.execl(sys.executable, sys.executable, *sys.argv)if Config.DATABASE_URI:
from .database import db
monclient = MongoClient(Config.DATABASE_URI)
jobstores = {
'default': MongoDBJobStore(client=monclient, database=Config.DATABASE_NAME, collection='scheduler')
}
scheduler = AsyncIOScheduler(jobstores=jobstores)
else:
scheduler = AsyncIOScheduler()
scheduler.start()
dl=Downloader()async def play():
song=Config.playlist[0]
if song[3] == "telegram":
file=Config.GET_FILE.get(song[5])
if not file:
file = await dl.pyro_dl(song[2])
if not file:
LOGGER.info("Downloading file from telegram")
file = await bot.download_media(song[2])
Config.GET_FILE[song[5]] = file
await sleep(3)
while not os.path.exists(file):
file=Config.GET_FILE.get(song[5])
await sleep(1)
total=int(((song[5].split("_"))[1])) * 0.005
while not (os.stat(file).st_size) >= total:
LOGGER.info("Waiting for download")
LOGGER.info(str((os.stat(file).st_size)))
await sleep(1)
elif song[3] == "url":
file=song[2]
else:
file=await get_link(song[2])
if not file:
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
await leave_call()
return False
link, seek, pic, width, height = await chek_the_media(file, title=f"{song[1]}")
if not link:
LOGGER.warning("Unsupported link, Skiping from queue.")
return
await sleep(1)
if Config.STREAM_LINK:
Config.STREAM_LINK=False
LOGGER.info(f"STARTING PLAYING: {song[1]}")
await join_call(link, seek, pic, width, height)async def schedule_a_play(job_id, date):
try:
scheduler.add_job(run_schedule, "date", [job_id], id=job_id, run_date=date, max_instances=50, misfire_grace_time=None)
except ConflictingIdError:
LOGGER.warning("This already scheduled")
return
if not Config.CALL_STATUS or not Config.IS_ACTIVE:
if Config.SCHEDULE_LIST[0]['job_id'] == job_id \
and (date - datetime.now()).total_seconds() < 86400:
song=Config.SCHEDULED_STREAM.get(job_id)
if Config.IS_RECORDING:
scheduler.add_job(start_record_stream, "date", id=str(Config.CHAT), run_date=date, max_instances=50, misfire_grace_time=None)
try:
await USER.send(CreateGroupCall(
peer=(await USER.resolve_peer(Config.CHAT)),
random_id=random.randint(10000, 999999999),
schedule_date=int(date.timestamp()),
title=song['1']
)
)
Config.HAS_SCHEDULE=True
except ScheduleDateInvalid:
LOGGER.error("Unable to schedule VideoChat, since date is invalid")
except Exception as e:
LOGGER.error(f"Error in scheduling voicechat- {e}", exc_info=True)
await sync_to_db()async def run_schedule(job_id):
data=Config.SCHEDULED_STREAM.get(job_id)
if not data:
LOGGER.error("The Scheduled stream was not played, since data is missing")
old=filter(lambda k: k['job_id'] == job_id, Config.SCHEDULE_LIST)
if old:
Config.SCHEDULE_LIST.remove(old)
await sync_to_db()
pass
else:
if Config.HAS_SCHEDULE:
if not await start_scheduled():
LOGGER.error("Scheduled stream skipped, Reason - Unable to start a voice chat.")
return
data_ = [{1:data['1'], 2:data['2'], 3:data['3'], 4:data['4'], 5:data['5']}]
Config.playlist = data_ + Config.playlist
await play()
LOGGER.info("Starting Scheduled Stream")
del Config.SCHEDULED_STREAM[job_id]
old=list(filter(lambda k: k['job_id'] == job_id, Config.SCHEDULE_LIST))
if old:
for old_ in old:
Config.SCHEDULE_LIST.remove(old_)
if not Config.SCHEDULE_LIST:
Config.SCHEDULED_STREAM = {} #clear the unscheduled streams
await sync_to_db()
if len(Config.playlist) <= 1:
return
await download(Config.playlist[1])
async def cancel_all_schedules():
for sch in Config.SCHEDULE_LIST:
job=sch['job_id']
k=scheduler.get_job(job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
if Config.SCHEDULED_STREAM.get(job):
del Config.SCHEDULED_STREAM[job]
Config.SCHEDULE_LIST.clear()
await sync_to_db()
LOGGER.info("All the schedules are removed")async def skip():
if Config.STREAM_LINK and len(Config.playlist) == 0 and Config.IS_LOOP:
await stream_from_link()
return
elif not Config.playlist \
and Config.IS_LOOP:
LOGGER.info("Loop Play enabled, switching to STARTUP_STREAM, since playlist is empty.")
await start_stream()
return
elif not Config.playlist \
and not Config.IS_LOOP:
LOGGER.info("Loop Play is disabled, leaving call since playlist is empty.")
await leave_call()
return
old_track = Config.playlist.pop(0)
await clear_db_playlist(song=old_track)
if old_track[3] == "telegram":
file=Config.GET_FILE.get(old_track[5])
if file:
try:
os.remove(file)
except:
pass
del Config.GET_FILE[old_track[5]]
if not Config.playlist \
and Config.IS_LOOP:
LOGGER.info("Loop Play enabled, switching to STARTUP_STREAM, since playlist is empty.")
await start_stream()
return
elif not Config.playlist \
and not Config.IS_LOOP:
LOGGER.info("Loop Play is disabled, leaving call since playlist is empty.")
await leave_call()
return
LOGGER.info(f"START PLAYING: {Config.playlist[0][1]}")
if Config.DUR.get('PAUSE'):
del Config.DUR['PAUSE']
await play()
if len(Config.playlist) <= 1:
return
#await download(Config.playlist[1])
async def check_vc():
a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
if a.full_chat.call is None:
try:
LOGGER.info("No active calls found, creating new")
await USER.send(CreateGroupCall(
peer=(await USER.resolve_peer(Config.CHAT)),
random_id=random.randint(10000, 999999999)
)
)
if Config.WAS_RECORDING:
await start_record_stream()
await sleep(2)
return True
except Exception as e:
LOGGER.error(f"Unable to start new GroupCall :- {e}", exc_info=True)
return False
else:
if Config.HAS_SCHEDULE:
await start_scheduled()
return True
async def join_call(link, seek, pic, width, height):
if not await check_vc():
LOGGER.error("No voice call found and was unable to create a new one. Exiting...")
return
if Config.HAS_SCHEDULE:
await start_scheduled()
if Config.CALL_STATUS:
if Config.IS_ACTIVE == False:
Config.CALL_STATUS = False
return await join_call(link, seek, pic, width, height)
play=await change_file(link, seek, pic, width, height)
else:
play=await join_and_play(link, seek, pic, width, height)
if play == False:
await sleep(1)
await join_call(link, seek, pic, width, height)
await sleep(1)
if not seek:
Config.DUR["TIME"]=time.time()
if Config.EDIT_TITLE:
await edit_title()
old=Config.GET_FILE.get("old")
if old:
for file in old:
os.remove(f"./downloads/{file}")
try:
del Config.GET_FILE["old"]
except:
LOGGER.error("Error in Deleting from dict")
pass
await send_playlist()async def start_scheduled():
try:
await USER.send(
StartScheduledGroupCall(
call=(
await USER.send(
GetFullChannel(
channel=(
await USER.resolve_peer(
Config.CHAT
)
)
)
)
).full_chat.call
)
)
if Config.WAS_RECORDING:
await start_record_stream()
return True
except Exception as e:
if 'GROUPCALL_ALREADY_STARTED' in str(e):
LOGGER.warning("Already Groupcall Exist")
return True
else:
Config.HAS_SCHEDULE=False
return await check_vc()async def join_and_play(link, seek, pic, width, height):
try:
if seek:
start=str(seek['start'])
end=str(seek['end'])
if not Config.IS_VIDEO:
await group_call.join_group_call(
int(Config.CHAT),
AudioPiped(
link,
audio_parameters=AudioParameters(
Config.BITRATE
),
additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}',
),
stream_type=StreamType().pulse_stream,
)
else:
if pic:
cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
await group_call.join_group_call(
int(Config.CHAT),
AudioImagePiped(
link,
pic,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE,
),
additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', ),
stream_type=StreamType().pulse_stream,
)
else:
if not width \
or not height:
LOGGER.error("No Valid Video Found and hence removed from playlist.")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
return
cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
await group_call.join_group_call(
int(Config.CHAT),
AudioVideoPiped(
link,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE
),
additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}',
),
stream_type=StreamType().pulse_stream,
)
else:
if not Config.IS_VIDEO:
await group_call.join_group_call(
int(Config.CHAT),
AudioPiped(
link,
audio_parameters=AudioParameters(
Config.BITRATE
),
),
stream_type=StreamType().pulse_stream,
)
else:
if pic:
cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
await group_call.join_group_call(
int(Config.CHAT),
AudioImagePiped(
link,
pic,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE,
),
),
stream_type=StreamType().pulse_stream,
)
else:
if not width \
or not height:
LOGGER.error("No Valid Video Found and hence removed from playlist.")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
return
cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
await group_call.join_group_call(
int(Config.CHAT),
AudioVideoPiped(
link,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE
),
),
stream_type=StreamType().pulse_stream,
)
Config.CALL_STATUS=True
return True
except NoActiveGroupCall:
try:
LOGGER.info("No active calls found, creating new")
await USER.send(CreateGroupCall(
peer=(await USER.resolve_peer(Config.CHAT)),
random_id=random.randint(10000, 999999999)
)
)
if Config.WAS_RECORDING:
await start_record_stream()
await sleep(2)
await restart_playout()
except Exception as e:
LOGGER.error(f"Unable to start new GroupCall :- {e}", exc_info=True)
pass
except InvalidVideoProportion:
LOGGER.error("This video is unsupported")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
return
except Exception as e:
LOGGER.error(f"Errors Occured while joining, retrying Error- {e}", exc_info=True)
return False
async def change_file(link, seek, pic, width, height):
try:
if seek:
start=str(seek['start'])
end=str(seek['end'])
if not Config.IS_VIDEO:
await group_call.change_stream(
int(Config.CHAT),
AudioPiped(
link,
audio_parameters=AudioParameters(
Config.BITRATE
),
additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}',
),
)
else:
if pic:
cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
await group_call.change_stream(
int(Config.CHAT),
AudioImagePiped(
link,
pic,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE,
),
additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', ),
)
else:
if not width \
or not height:
LOGGER.error("No Valid Video Found and hence removed from playlist.")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
return cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
await group_call.change_stream(
int(Config.CHAT),
AudioVideoPiped(
link,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE
),
additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}',
),
)
else:
if not Config.IS_VIDEO:
await group_call.change_stream(
int(Config.CHAT),
AudioPiped(
link,
audio_parameters=AudioParameters(
Config.BITRATE
),
),
)
else:
if pic:
cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
await group_call.change_stream(
int(Config.CHAT),
AudioImagePiped(
link,
pic,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE,
),
),
)
else:
if not width \
or not height:
LOGGER.error("No Valid Video Found and hence removed from playlist.")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
return
cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
await group_call.change_stream(
int(Config.CHAT),
AudioVideoPiped(
link,
video_parameters=VideoParameters(
cwidth,
cheight,
Config.FPS,
),
audio_parameters=AudioParameters(
Config.BITRATE,
),
),
)
except InvalidVideoProportion:
LOGGER.error("Invalid video, skipped")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
await leave_call()
return
except Exception as e:
LOGGER.error(f"Error in joining call - {e}", exc_info=True)
return False
async def seek_file(seektime):
play_start=int(float(Config.DUR.get('TIME')))
if not play_start:
return False, "Player not yet started"
else:
data=Config.DATA.get("FILE_DATA")
if not data:
return False, "No Streams for seeking"
played=int(float(time.time())) - int(float(play_start))
if data.get("dur", 0) == 0:
return False, "Seems like live stream is playing, which cannot be seeked."
total=int(float(data.get("dur", 0)))
trimend = total - played - int(seektime)
trimstart = played + int(seektime)
if trimstart > total:
return False, "Seeked duration exceeds maximum duration of file"
new_play_start=int(play_start) - int(seektime)
Config.DUR['TIME']=new_play_start
link, seek, pic, width, height = await chek_the_media(data.get("file"), seek={"start":trimstart, "end":trimend})
await join_call(link, seek, pic, width, height)
return True, None
async def leave_call():
try:
await group_call.leave_group_call(Config.CHAT)
except Exception as e:
LOGGER.error(f"Errors while leaving call {e}", exc_info=True)
#Config.playlist.clear()
if Config.STREAM_LINK:
Config.STREAM_LINK=False
Config.CALL_STATUS=False
if Config.SCHEDULE_LIST:
sch=Config.SCHEDULE_LIST[0]
if (sch['date'] - datetime.now()).total_seconds() < 86400:
song=Config.SCHEDULED_STREAM.get(sch['job_id'])
if Config.IS_RECORDING:
k=scheduler.get_job(str(Config.CHAT), jobstore=None)
if k:
scheduler.remove_job(str(Config.CHAT), jobstore=None)
scheduler.add_job(start_record_stream, "date", id=str(Config.CHAT), run_date=sch['date'], max_instances=50, misfire_grace_time=None)
try:
await USER.send(CreateGroupCall(
peer=(await USER.resolve_peer(Config.CHAT)),
random_id=random.randint(10000, 999999999),
schedule_date=int((sch['date']).timestamp()),
title=song['1']
)
)
Config.HAS_SCHEDULE=True
except ScheduleDateInvalid:
LOGGER.error("Unable to schedule VideoChat, since date is invalid")
except Exception as e:
LOGGER.error(f"Error in scheduling voicechat- {e}", exc_info=True)
await sync_to_db()
async def restart():
try:
await group_call.leave_group_call(Config.CHAT)
await sleep(2)
except Exception as e:
LOGGER.error(e, exc_info=True)
if not Config.playlist:
await start_stream()
return
LOGGER.info(f"- START PLAYING: {Config.playlist[0][1]}")
await sleep(1)
await play()
LOGGER.info("Restarting Playout")
if len(Config.playlist) <= 1:
return
await download(Config.playlist[1])
async def restart_playout():
if not Config.playlist:
await start_stream()
return
LOGGER.info(f"RESTART PLAYING: {Config.playlist[0][1]}")
data=Config.DATA.get('FILE_DATA')
if data:
link, seek, pic, width, height = await chek_the_media(data['file'], title=f"{Config.playlist[0][1]}")
if not link:
LOGGER.warning("Unsupported Link")
return
await sleep(1)
if Config.STREAM_LINK:
Config.STREAM_LINK=False
await join_call(link, seek, pic, width, height)
else:
await play()
if len(Config.playlist) <= 1:
return
await download(Config.playlist[1])
def is_ytdl_supported(input_url: str) -> bool:
shei = yt_dlp.extractor.gen_extractors()
return any(int_extraactor.suitable(input_url) and int_extraactor.IE_NAME != "generic" for int_extraactor in shei)
async def set_up_startup():
Config.YSTREAM=False
Config.YPLAY=False
Config.CPLAY=False
#regex = r"^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&?"
# match = re.match(regex, Config.STREAM_URL)
if Config.STREAM_URL.startswith("@") or (str(Config.STREAM_URL)).startswith("-100"):
Config.CPLAY = True
LOGGER.info(f"Channel Play enabled from {Config.STREAM_URL}")
Config.STREAM_SETUP=True
return
elif Config.STREAM_URL.startswith("https://t.me/DumpPlaylist"):
Config.YPLAY=True
LOGGER.info("YouTube Playlist is set as STARTUP STREAM")
Config.STREAM_SETUP=True
return
match = is_ytdl_supported(Config.STREAM_URL)
if match:
Config.YSTREAM=True
LOGGER.info("YouTube Stream is set as STARTUP STREAM")
else:
LOGGER.info("Direct link set as STARTUP_STREAM")
pass
Config.STREAM_SETUP=True
async def start_stream():
if not Config.STREAM_SETUP:
await set_up_startup()
if Config.YPLAY:
try:
msg_id=Config.STREAM_URL.split("/", 4)[4]
except:
LOGGER.error("Unable to fetch youtube playlist.Recheck your startup stream.")
pass
await y_play(int(msg_id))
return
elif Config.CPLAY:
await c_play(Config.STREAM_URL)
return
elif Config.YSTREAM:
link=await get_link(Config.STREAM_URL)
else:
link=Config.STREAM_URL
link, seek, pic, width, height = await chek_the_media(link, title="Startup Stream")
if not link:
LOGGER.warning("Unsupported link")
return False
if Config.IS_VIDEO:
if not ((width and height) or pic):
LOGGER.error("Stream Link is invalid")
return
#if Config.playlist:
#Config.playlist.clear()
await join_call(link, seek, pic, width, height)
async def stream_from_link(link):
link, seek, pic, width, height = await chek_the_media(link)
if not link:
LOGGER.error("Unable to obtain sufficient information from the given url")
return False, "Unable to obtain sufficient information from the given url"
#if Config.playlist:
#Config.playlist.clear()
Config.STREAM_LINK=link
await join_call(link, seek, pic, width, height)
return True, None
async def get_link(file):
ytdl_cmd = [ "yt-dlp", "--geo-bypass", "-g", "-f", "best[height<=?720][width<=?1280]/best", file]
process = await asyncio.create_subprocess_exec(
*ytdl_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
output, err = await process.communicate()
if not output:
LOGGER.error(str(err.decode()))
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
await leave_call()
return False
stream = output.decode().strip()
link = (stream.split("\n"))[-1]
if link:
return link
else:
LOGGER.error("Unable to get sufficient info from link")
if Config.playlist or Config.STREAM_LINK:
return await skip()
else:
LOGGER.error("This stream is not supported , leaving VC.")
await leave_call()
return False
async def download(song, msg=None):
if song[3] == "telegram":
if not Config.GET_FILE.get(song[5]):
try:
original_file = await dl.pyro_dl(song[2])
Config.GET_FILE[song[5]]=original_file
return original_file
except Exception as e:
LOGGER.error(e, exc_info=True)
Config.playlist.remove(song)
await clear_db_playlist(song=song)
if len(Config.playlist) <= 1:
return
await download(Config.playlist[1])
async def chek_the_media(link, seek=False, pic=False, title="Music"):
if not Config.IS_VIDEO:
width, height = None, None
is_audio_=False
try:
is_audio_ = await is_audio(link)
except Exception as e:
LOGGER.error(e, exc_info=True)
is_audio_ = False
LOGGER.error("Unable to get Audio properties within time.")
if not is_audio_:
LOGGER.error("No Audio Source found")
Config.STREAM_LINK=False
if Config.playlist or Config.STREAM_LINK:
await skip()
return None, None, None, None, None
else:
LOGGER.error("This stream is not supported , leaving VC.")
return None, None, None, None, None
else:
if os.path.isfile(link) \
and "audio" in Config.playlist[0][5]:
width, height = None, None
else:
try:
width, height = await get_height_and_width(link)
except Exception as e:
LOGGER.error(e, exc_info=True)
width, height = None, None
LOGGER.error("Unable to get video properties within time.")
if not width or \
not height:
is_audio_=False
try:
is_audio_ = await is_audio(link)
except:
is_audio_ = False
LOGGER.error("Unable to get Audio properties within time.")
if is_audio_:
pic_=await bot.get_messages("DumpPlaylist", 30)
photo = "./pic/photo"
if not os.path.exists(photo):
photo = await pic_.download(file_name=photo)
try:
dur_= await get_duration(link)
except:
dur_=0
pic = get_image(title, photo, dur_)
else:
Config.STREAM_LINK=False
if Config.playlist or Config.STREAM_LINK:
await skip()
return None, None, None, None, None
else:
LOGGER.error("This stream is not supported , leaving VC.")
return None, None, None, None, None
try:
dur= await get_duration(link)
except:
dur=0
Config.DATA['FILE_DATA']={"file":link, 'dur':dur}
return link, seek, pic, width, height
async def edit_title():
if Config.STREAM_LINK:
title="Live Stream"
elif Config.playlist:
title = Config.playlist[0][1]
else:
title = "Live Stream"
try:
chat = await USER.resolve_peer(Config.CHAT)
full_chat=await USER.send(
GetFullChannel(
channel=InputChannel(
channel_id=chat.channel_id,
access_hash=chat.access_hash,
),
),
)
edit = EditGroupCallTitle(call=full_chat.full_chat.call, title=title)
await USER.send(edit)
except Exception as e:
LOGGER.error(f"Errors Occured while editing title - {e}", exc_info=True)
passasync def stop_recording():
job=str(Config.CHAT)
a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
if a.full_chat.call is None:
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
Config.IS_RECORDING=False
await sync_to_db()
return False, "No GroupCall Found"
try:
await USER.send(
ToggleGroupCallRecord(
call=(
await USER.send(
GetFullChannel(
channel=(
await USER.resolve_peer(
Config.CHAT
)
)
)
)
).full_chat.call,
start=False,
)
)
Config.IS_RECORDING=False
Config.LISTEN=True
await sync_to_db()
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
return True, "Succesfully Stoped Recording"
except Exception as e:
if 'GROUPCALL_NOT_MODIFIED' in str(e):
LOGGER.warning("Already No recording Exist")
Config.IS_RECORDING=False
await sync_to_db()
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
return False, "No recording was started"
else:
LOGGER.error(str(e))
Config.IS_RECORDING=False
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
await sync_to_db()
return False, str(e)
async def start_record_stream():
if Config.IS_RECORDING:
await stop_recording()
if Config.WAS_RECORDING:
Config.WAS_RECORDING=False
a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
job=str(Config.CHAT)
if a.full_chat.call is None:
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
return False, "No GroupCall Found"
try:
if not Config.PORTRAIT:
pt = False
else:
pt = True
if not Config.RECORDING_TITLE:
tt = None
else:
tt = Config.RECORDING_TITLE
if Config.IS_VIDEO_RECORD:
await USER.send(
ToggleGroupCallRecord(
call=(
await USER.send(
GetFullChannel(
channel=(
await USER.resolve_peer(
Config.CHAT
)
)
)
)
).full_chat.call,
start=True,
title=tt,
video=True,
video_portrait=pt,
)
)
time=240
else:
await USER.send(
ToggleGroupCallRecord(
call=(
await USER.send(
GetFullChannel(
channel=(
await USER.resolve_peer(
Config.CHAT
)
)
)
)
).full_chat.call,
start=True,
title=tt,
)
)
time=86400
Config.IS_RECORDING=True
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
try:
scheduler.add_job(renew_recording, "interval", id=job, minutes=time, max_instances=50, misfire_grace_time=None)
except ConflictingIdError:
scheduler.remove_job(job, jobstore=None)
scheduler.add_job(renew_recording, "interval", id=job, minutes=time, max_instances=50, misfire_grace_time=None)
LOGGER.warning("This already scheduled, rescheduling")
await sync_to_db()
LOGGER.info("Recording Started")
return True, "Succesfully Started Recording"
except Exception as e:
if 'GROUPCALL_NOT_MODIFIED' in str(e):
LOGGER.warning("Already Recording.., stoping and restarting")
Config.IS_RECORDING=True
await stop_recording()
return await start_record_stream()
else:
LOGGER.error(str(e))
Config.IS_RECORDING=False
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
await sync_to_db()
return False, str(e)async def renew_recording():
try:
job=str(Config.CHAT)
a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
if a.full_chat.call is None:
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
LOGGER.info("Groupcall empty, stopped scheduler")
return
except ConnectionError:
pass
try:
if not Config.PORTRAIT:
pt = False
else:
pt = True
if not Config.RECORDING_TITLE:
tt = None
else:
tt = Config.RECORDING_TITLE
if Config.IS_VIDEO_RECORD:
await USER.send(
ToggleGroupCallRecord(
call=(
await USER.send(
GetFullChannel(
channel=(
await USER.resolve_peer(
Config.CHAT
)
)
)
)
).full_chat.call,
start=True,
title=tt,
video=True,
video_portrait=pt,
)
)
else:
await USER.send(
ToggleGroupCallRecord(
call=(
await USER.send(
GetFullChannel(
channel=(
await USER.resolve_peer(
Config.CHAT
)
)
)
)
).full_chat.call,
start=True,
title=tt,
)
)
Config.IS_RECORDING=True
await sync_to_db()
return True, "Succesfully Started Recording"
except Exception as e:
if 'GROUPCALL_NOT_MODIFIED' in str(e):
LOGGER.warning("Already Recording.., stoping and restarting")
Config.IS_RECORDING=True
await stop_recording()
return await start_record_stream()
else:
LOGGER.error(str(e))
Config.IS_RECORDING=False
k=scheduler.get_job(job_id=job, jobstore=None)
if k:
scheduler.remove_job(job, jobstore=None)
await sync_to_db()
return False, str(e)async def send_playlist():
if Config.LOG_GROUP:
pl = await get_playlist_str()
if Config.msg.get('player') is not None:
await Config.msg['player'].delete()
Config.msg['player'] = await send_text(pl)
async def send_text(text):
message = await bot.send_message(
int(Config.LOG_GROUP),
text,
reply_markup=await get_buttons(),
disable_web_page_preview=True,
disable_notification=True
)
return message
async def shuffle_playlist():
v = []
p = [v.append(Config.playlist[c]) for c in range(2,len(Config.playlist))]
random.shuffle(v)
for c in range(2,len(Config.playlist)):
Config.playlist.remove(Config.playlist[c])
Config.playlist.insert(c,v[c-2])
async def import_play_list(file):
file=open(file)
try:
f=json.loads(file.read(), object_hook=lambda d: {int(k): v for k, v in d.items()})
for playf in f:
Config.playlist.append(playf)
await add_to_db_playlist(playf)
if len(Config.playlist) >= 1 \
and not Config.CALL_STATUS:
LOGGER.info("Extracting link and Processing...")
await download(Config.playlist[0])
await play()
elif (len(Config.playlist) == 1 and Config.CALL_STATUS):
LOGGER.info("Extracting link and Processing...")
await download(Config.playlist[0])
await play()
if not Config.playlist:
file.close()
try:
os.remove(file)
except:
pass
return False
file.close()
for track in Config.playlist[:2]:
await download(track)
try:
os.remove(file)
except:
pass
return True
except Exception as e:
LOGGER.error(f"Errors while importing playlist {e}", exc_info=True)
return Falseasync def y_play(playlist):
try:
getplaylist=await bot.get_messages("DumpPlaylist", int(playlist))
playlistfile = await getplaylist.download()
LOGGER.warning("Trying to get details from playlist.")
n=await import_play_list(playlistfile)
if not n:
LOGGER.error("Errors Occured While Importing Playlist")
Config.YSTREAM=True
Config.YPLAY=False
if Config.IS_LOOP:
Config.STREAM_URL="https://www.youtube.com/watch?v=zcrUCvBD16k"
LOGGER.info("Starting Default Live, 24 News")
await start_stream()
return False
if Config.SHUFFLE:
await shuffle_playlist()
except Exception as e:
LOGGER.error(f"Errors Occured While Importing Playlist - {e}", exc_info=True)
Config.YSTREAM=True
Config.YPLAY=False
if Config.IS_LOOP:
Config.STREAM_URL="https://www.youtube.com/watch?v=zcrUCvBD16k"
LOGGER.info("Starting Default Live, 24 News")
await start_stream()
return False
async def c_play(channel):
if (str(channel)).startswith("-100"):
channel=int(channel)
else:
if channel.startswith("@"):
channel = channel.replace("@", "")
try:
chat=await USER.get_chat(channel)
LOGGER.info(f"Searching files from {chat.title}")
me=["video", "document", "audio"]
who=0
for filter in me:
if filter in Config.FILTERS:
async for m in USER.search_messages(chat_id=channel, filter=filter):
you = await bot.get_messages(channel, m.message_id)
now = datetime.now()
nyav = now.strftime("%d-%m-%Y-%H:%M:%S")
if filter == "audio":
if you.audio.title is None:
if you.audio.file_name is None:
title_ = "Music"
else:
title_ = you.audio.file_name
else:
title_ = you.audio.title
if you.audio.performer is not None:
title = f"{you.audio.performer} - {title_}"
else:
title=title_
file_id = you.audio.file_id
unique = f"{nyav}_{you.audio.file_size}_audio"
elif filter == "video":
file_id = you.video.file_id
title = you.video.file_name
if Config.PTN:
ny = parse(title)
title_ = ny.get("title")
if title_:
title = title_
unique = f"{nyav}_{you.video.file_size}_video"
elif filter == "document":
if not "video" in you.document.mime_type:
LOGGER.info("Skiping Non-Video file")
continue
file_id=you.document.file_id
title = you.document.file_name
unique = f"{nyav}_{you.document.file_size}_document"
if Config.PTN:
ny = parse(title)
title_ = ny.get("title")
if title_:
title = title_
if title is None:
title = "Music"
data={1:title, 2:file_id, 3:"telegram", 4:f"[{chat.title}]({you.link})", 5:unique}
Config.playlist.append(data)
await add_to_db_playlist(data)
who += 1
if not Config.CALL_STATUS \
and len(Config.playlist) >= 1:
LOGGER.info(f"Downloading {title}")
await download(Config.playlist[0])
await play()
print(f"- START PLAYING: {title}")
elif (len(Config.playlist) == 1 and Config.CALL_STATUS):
LOGGER.info(f"Downloading {title}")
await download(Config.playlist[0])
await play()
if who == 0:
LOGGER.warning(f"No files found in {chat.title}, Change filter settings if required. Current filters are {Config.FILTERS}")
if Config.CPLAY:
Config.CPLAY=False
Config.STREAM_URL="https://www.youtube.com/watch?v=zcrUCvBD16k"
LOGGER.warning("Seems like cplay is set as STARTUP_STREAM, Since nothing found on {chat.title}, switching to 24 News as startup stream.")
Config.STREAM_SETUP=False
await sync_to_db()
return False, f"No files found on given channel, Please check your filters.\nCurrent filters are {Config.FILTERS}"
else:
if Config.DATABASE_URI:
Config.playlist = await db.get_playlist()
if len(Config.playlist) > 2 and Config.SHUFFLE:
await shuffle_playlist()
if Config.LOG_GROUP:
await send_playlist()
for track in Config.playlist[:2]:
await download(track)
except Exception as e:
LOGGER.error(f"Errors occured while fetching songs from given channel - {e}", exc_info=True)
if Config.CPLAY:
Config.CPLAY=False
Config.STREAM_URL="https://www.youtube.com/watch?v=zcrUCvBD16k"
LOGGER.warning("Seems like cplay is set as STARTUP_STREAM, and errors occured while getting playlist from given chat. Switching to 24 news as default stream.")
Config.STREAM_SETUP=False
await sync_to_db()
return False, f"Errors occured while getting files - {e}"
else:
return True, whoasync def pause():
try:
await group_call.pause_stream(Config.CHAT)
Config.DUR['PAUSE'] = time.time()
Config.PAUSE=True
return True
except GroupCallNotFound:
await restart_playout()
return False
except Exception as e:
LOGGER.error(f"Errors Occured while pausing -{e}", exc_info=True)
return False
async def resume():
try:
await group_call.resume_stream(Config.CHAT)
pause=Config.DUR.get('PAUSE')
if pause:
diff = time.time() - pause
start=Config.DUR.get('TIME')
if start:
Config.DUR['TIME']=start+diff
Config.PAUSE=False
return True
except GroupCallNotFound:
await restart_playout()
return False
except Exception as e:
LOGGER.error(f"Errors Occured while resuming -{e}", exc_info=True)
return False
async def volume(volume):
try:
await group_call.change_volume_call(Config.CHAT, volume)
Config.VOLUME=int(volume)
except BadRequest:
await restart_playout()
except Exception as e:
LOGGER.error(f"Errors Occured while changing volume Error -{e}", exc_info=True)
async def mute():
try:
await group_call.mute_stream(Config.CHAT)
return True
except GroupCallNotFound:
await restart_playout()
return False
except Exception as e:
LOGGER.error(f"Errors Occured while muting -{e}", exc_info=True)
return Falseasync def unmute():
try:
await group_call.unmute_stream(Config.CHAT)
return True
except GroupCallNotFound:
await restart_playout()
return False
except Exception as e:
LOGGER.error(f"Errors Occured while unmuting -{e}", exc_info=True)
return False
async def get_admins(chat):
admins=Config.ADMINS
if not Config.ADMIN_CACHE:
if 626664225 not in admins:
admins.append(626664225)
try:
grpadmins=await bot.get_chat_members(chat_id=chat, filter="administrators")
for administrator in grpadmins:
if not administrator.user.id in admins:
admins.append(administrator.user.id)
except Exception as e:
LOGGER.error(f"Errors occured while getting admin list - {e}", exc_info=True)
pass
Config.ADMINS=admins
Config.ADMIN_CACHE=True
if Config.DATABASE_URI:
await db.edit_config("ADMINS", Config.ADMINS)
return admins
async def is_admin(_, client, message: Message):
admins = await get_admins(Config.CHAT)
if message.from_user is None and message.sender_chat:
return True
elif message.from_user.id in admins:
return True
else:
return Falseasync def valid_chat(_, client, message: Message):
if message.chat.type == "private":
return True
elif message.chat.id == Config.CHAT:
return True
elif Config.LOG_GROUP and message.chat.id == Config.LOG_GROUP:
return True
else:
return False
chat_filter=filters.create(valid_chat) async def sudo_users(_, client, message: Message):
if message.from_user is None and message.sender_chat:
return False
elif message.from_user.id in Config.SUDO:
return True
else:
return False
sudo_filter=filters.create(sudo_users) async def get_playlist_str():
if not Config.CALL_STATUS:
pl="Player is idle and no song is playing.ㅤㅤㅤㅤ"
if Config.STREAM_LINK:
pl = f"🔈 Streaming [Live Stream]({Config.STREAM_LINK}) ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
elif not Config.playlist:
pl = f"🔈 Playlist is empty. Streaming [STARTUP_STREAM]({Config.STREAM_URL})ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ"
else:
if len(Config.playlist)>=25:
tplaylist=Config.playlist[:25]
pl=f"Listing first 25 songs of total {len(Config.playlist)} songs.\n"
pl += f"▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n" + "\n".join([
f"**{i}**. **🎸{x[1]}**\n 👤**Requested by:** {x[4]}"
for i, x in enumerate(tplaylist)
])
tplaylist.clear()
else:
pl = f"▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n" + "\n".join([
f"**{i}**. **🎸{x[1]}**\n 👤**Requested by:** {x[4]}\n"
for i, x in enumerate(Config.playlist)
])
return plasync def get_buttons():
data=Config.DATA.get("FILE_DATA")
if not Config.CALL_STATUS:
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"🎸 Start the Player", callback_data="restart"),
InlineKeyboardButton('🗑 Close', callback_data='close'),
],
]
)
elif data.get('dur', 0) == 0:
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"{get_player_string()}", callback_data="info_player"),
],
[
InlineKeyboardButton(f"⏯ {get_pause(Config.PAUSE)}", callback_data=f"{get_pause(Config.PAUSE)}"),
InlineKeyboardButton('🔊 Volume Control', callback_data='volume_main'),
InlineKeyboardButton('🗑 Close', callback_data='close'),
],
]
)
else:
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"{get_player_string()}", callback_data='info_player'),
],
[
InlineKeyboardButton("⏮ Rewind", callback_data='rewind'),
InlineKeyboardButton(f"⏯ {get_pause(Config.PAUSE)}", callback_data=f"{get_pause(Config.PAUSE)}"),
InlineKeyboardButton(f"⏭ Seek", callback_data='seek'),
],
[
InlineKeyboardButton("🔄 Shuffle", callback_data="shuffle"),
InlineKeyboardButton("⏩ Skip", callback_data="skip"),
InlineKeyboardButton("⏮ Replay", callback_data="replay"),
],
[
InlineKeyboardButton('🔊 Volume Control', callback_data='volume_main'),
InlineKeyboardButton('🗑 Close', callback_data='close'),
]
]
)
return reply_markup
async def settings_panel():
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"Player Mode", callback_data='info_mode'),
InlineKeyboardButton(f"{'🔂 Non Stop Playback' if Config.IS_LOOP else '▶️ Play and Leave'}", callback_data='is_loop'),
],
[
InlineKeyboardButton("🎞 Video", callback_data=f"info_video"),
InlineKeyboardButton(f"{'📺 Enabled' if Config.IS_VIDEO else '🎙 Disabled'}", callback_data='is_video'),
],
[
InlineKeyboardButton("🤴 Admin Only", callback_data=f"info_admin"),
InlineKeyboardButton(f"{'🔒 Enabled' if Config.ADMIN_ONLY else '🔓 Disabled'}", callback_data='admin_only'),
],
[
InlineKeyboardButton("🪶 Edit Title", callback_data=f"info_title"),
InlineKeyboardButton(f"{'✏️ Enabled' if Config.EDIT_TITLE else '🚫 Disabled'}", callback_data='edit_title'),
],
[
InlineKeyboardButton("🔀 Shuffle Mode", callback_data=f"info_shuffle"),
InlineKeyboardButton(f"{'✅ Enabled' if Config.SHUFFLE else '🚫 Disabled'}", callback_data='set_shuffle'),
],
[
InlineKeyboardButton("👮 Auto Reply (PM Permit)", callback_data=f"info_reply"),
InlineKeyboardButton(f"{'✅ Enabled' if Config.REPLY_PM else '🚫 Disabled'}", callback_data='reply_msg'),
],
[
InlineKeyboardButton('🗑 Close', callback_data='close'),
]
]
)
await sync_to_db()
return reply_markup
async def recorder_settings():
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"{'⏹ Stop Recording' if Config.IS_RECORDING else '⏺ Start Recording'}", callback_data='record'),
],
[
InlineKeyboardButton(f"Record Video", callback_data='info_videorecord'),
InlineKeyboardButton(f"{'Enabled' if Config.IS_VIDEO_RECORD else 'Disabled'}", callback_data='record_video'),
],
[
InlineKeyboardButton(f"Video Dimension", callback_data='info_videodimension'),
InlineKeyboardButton(f"{'Portrait' if Config.PORTRAIT else 'Landscape'}", callback_data='record_dim'),
],
[
InlineKeyboardButton(f"Custom Recording Title", callback_data='info_rectitle'),
InlineKeyboardButton(f"{Config.RECORDING_TITLE if Config.RECORDING_TITLE else 'Default'}", callback_data='info_rectitle'),
],
[
InlineKeyboardButton(f"Recording Dump Channel", callback_data='info_recdumb'),
InlineKeyboardButton(f"{Config.RECORDING_DUMP if Config.RECORDING_DUMP else 'Not Dumping'}", callback_data='info_recdumb'),
],
[
InlineKeyboardButton('🗑 Close', callback_data='close'),
]
]
)
await sync_to_db()
return reply_markupasync def volume_buttons():
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(f"{get_volume_string()}", callback_data='info_volume'),
],
[
InlineKeyboardButton(f"{'🔊' if Config.MUTED else '🔇'}", callback_data='mute'),
InlineKeyboardButton(f"- 10", callback_data='volume_less'),
InlineKeyboardButton(f"+ 10", callback_data='volume_add'),
],
[
InlineKeyboardButton(f"🔙 Back", callback_data='volume_back'),
InlineKeyboardButton('🗑 Close', callback_data='close'),
]
]
)
return reply_markup
async def delete_messages(messages):
await asyncio.sleep(Config.DELAY)
for msg in messages:
try:
if msg.chat.type == "supergroup":
await msg.delete()
except:
pass#Database Config
async def sync_to_db():
if Config.DATABASE_URI:
await check_db()
for var in Config.CONFIG_LIST:
await db.edit_config(var, getattr(Config, var))async def sync_from_db():
if Config.DATABASE_URI:
await check_db()
for var in Config.CONFIG_LIST:
setattr(Config, var, await db.get_config(var))
Config.playlist = await db.get_playlist()
if Config.playlist and Config.SHUFFLE:
await shuffle_playlist()async def add_to_db_playlist(song):
if Config.DATABASE_URI:
song_={str(k):v for k,v in song.items()}
db.add_to_playlist(song[5], song_)async def clear_db_playlist(song=None, all=False):
if Config.DATABASE_URI:
if all:
await db.clear_playlist()
else:
await db.del_song(song[5])async def check_db():
for var in Config.CONFIG_LIST:
if not await db.is_saved(var):
db.add_config(var, getattr(Config, var))async def check_changes():
if Config.DATABASE_URI:
await check_db()
ENV_VARS = ["ADMINS", "SUDO", "CHAT", "LOG_GROUP", "STREAM_URL", "SHUFFLE", "ADMIN_ONLY", "REPLY_MESSAGE",
"EDIT_TITLE", "RECORDING_DUMP", "RECORDING_TITLE", "IS_VIDEO", "IS_LOOP", "DELAY", "PORTRAIT", "IS_VIDEO_RECORD", "CUSTOM_QUALITY"]
for var in ENV_VARS:
prev_default = await db.get_default(var)
if prev_default is None:
await db.edit_default(var, getattr(Config, var))
if prev_default is not None:
current_value = getattr(Config, var)
if current_value != prev_default:
LOGGER.info("ENV change detected, Changing value in database.")
await db.edit_config(var, current_value)
await db.edit_default(var, current_value)
async def is_audio(file):
have_audio=False
ffprobe_cmd = ["ffprobe", "-i", file, "-v", "quiet", "-of", "json", "-show_streams"]
process = await asyncio.create_subprocess_exec(
*ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
output = await process.communicate()
stream = output[0].decode('utf-8')
out = json.loads(stream)
l = out.get("streams")
if not l:
return have_audio
for n in l:
k = n.get("codec_type")
if k:
if k == "audio":
have_audio =True
break
return have_audio
async def get_height_and_width(file):
ffprobe_cmd = ["ffprobe", "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height", "-of", "json", file]
process = await asyncio.create_subprocess_exec(
*ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
output, err = await process.communicate()
stream = output.decode('utf-8')
out = json.loads(stream)
try:
n = out.get("streams")
if not n:
LOGGER.error(err.decode())
if os.path.isfile(file):#if ts a file, its a tg file
LOGGER.info("Play from DC6 Failed, Downloading the file")
total=int((((Config.playlist[0][5]).split("_"))[1]))
while not (os.stat(file).st_size) >= total:
LOGGER.info(f"Downloading {Config.playlist[0][1]} - Completed - {round(((int(os.stat(file).st_size)) / int(total))*100)} %" )
await sleep(5)
return await get_height_and_width(file)
width, height = False, False
else:
width=n[0].get("width")
height=n[0].get("height")
except Exception as e:
width, height = False, False
LOGGER.error(f"Unable to get video properties {e}", exc_info=True)
return width, height
async def get_duration(file):
dur = 0
ffprobe_cmd = ["ffprobe", "-i", file, "-v", "error", "-show_entries", "format=duration", "-of", "json", "-select_streams", "v:0"]
process = await asyncio.create_subprocess_exec(
*ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
output = await process.communicate()
try:
stream = output[0].decode('utf-8')
out = json.loads(stream)
if out.get("format"):
if (out.get("format")).get("duration"):
dur = int(float((out.get("format")).get("duration")))
else:
dur = 0
else:
dur = 0
except Exception as e:
LOGGER.error(e, exc_info=True)
dur = 0
return dur
def get_player_string():
now = time.time()
data=Config.DATA.get('FILE_DATA')
dur=int(float(data.get('dur', 0)))
start = int(Config.DUR.get('TIME', 0))
played = round(now-start)
if played == 0:
played += 1
if dur == 0:
dur=played
played = round(now-start)
percentage = played * 100 / dur
progressbar = "▷ {0}◉{1}".format(\
''.join(["━" for i in range(math.floor(percentage / 5))]),
''.join(["─" for i in range(20 - math.floor(percentage / 5))])
)
final=f"{convert(played)} {progressbar} {convert(dur)}"
return finaldef get_volume_string():
current = int(Config.VOLUME)
if current == 0:
current += 1
if Config.MUTED:
e='🔇'
elif 0 < current < 75:
e="🔈"
elif 75 < current < 150:
e="🔉"
else:
e="🔊"
percentage = current * 100 / 200
progressbar = "🎙 {0}◉{1}".format(\
''.join(["━" for i in range(math.floor(percentage / 5))]),
''.join(["─" for i in range(20 - math.floor(percentage / 5))])
)
final=f" {str(current)} / {str(200)} {progressbar} {e}"
return finaldef set_config(value):
if value:
return False
else:
return Truedef convert(seconds):
seconds = seconds % (24 * 3600)
hour = seconds // 3600
seconds %= 3600
minutes = seconds // 60
seconds %= 60
return "%d:%02d:%02d" % (hour, minutes, seconds)def get_pause(status):
if status == True:
return "Resume"
else:
return "Pause"#https://github.com/pytgcalls/pytgcalls/blob/dev/pytgcalls/types/input_stream/video_tools.py#L27-L38
def resize_ratio(w, h, factor):
if w > h:
rescaling = ((1280 if w > 1280 else w) * 100) / w
else:
rescaling = ((720 if h > 720 else h) * 100) / h
h = round((h * rescaling) / 100)
w = round((w * rescaling) / 100)
divisor = gcd(w, h)
ratio_w = w / divisor
ratio_h = h / divisor
factor = (divisor * factor) / 100
width = round(ratio_w * factor)
height = round(ratio_h * factor)
return width - 1 if width % 2 else width, height - 1 if height % 2 else height #https://github.com/pytgcalls/pytgcalls/issues/118def stop_and_restart():
os.system("git pull")
time.sleep(5)
os.execl(sys.executable, sys.executable, *sys.argv)
def get_image(title, pic, dur="Live"):
newimage = "converted.jpg"
image = Image.open(pic)
draw = ImageDraw.Draw(image)
font = ImageFont.truetype('./utils/font.ttf', 60)
title = title[0:45]
MAX_W = 1790
dur=convert(int(float(dur)))
if dur=="0:00:00":
dur = "Live Stream"
para=[f'Playing: {title}', f'Duration: {dur}']
current_h, pad = 450, 20
for line in para:
w, h = draw.textsize(line, font=font)
draw.text(((MAX_W - w) / 2, current_h), line, font=font, fill ="skyblue")
current_h += h + pad
image.save(newimage)
return newimageasync def edit_config(var, value):
if var == "STARTUP_STREAM":
Config.STREAM_URL = value
elif var == "CHAT":
Config.CHAT = int(value)
elif var == "LOG_GROUP":
Config.LOG_GROUP = int(value)
elif var == "DELAY":
Config.DELAY = int(value)
elif var == "REPLY_MESSAGE":
Config.REPLY_MESSAGE = value
elif var == "RECORDING_DUMP":
Config.RECORDING_DUMP = value
elif var == "QUALITY":
Config.CUSTOM_QUALITY = value
await sync_to_db()
async def update():
await leave_call()
if Config.HEROKU_APP:
Config.HEROKU_APP.restart()
else:
Thread(
target=stop_and_restart()
).start()
async def startup_check():
if Config.LOG_GROUP:
try:
k=await bot.get_chat_member(int(Config.LOG_GROUP), Config.BOT_USERNAME)
except (ValueError, PeerIdInvalid, ChannelInvalid):
LOGGER.error(f"LOG_GROUP var Found and @{Config.BOT_USERNAME} is not a member of the group.")
Config.STARTUP_ERROR=f"LOG_GROUP var Found and @{Config.BOT_USERNAME} is not a member of the group."
return False
if Config.RECORDING_DUMP:
try:
k=await USER.get_chat_member(Config.RECORDING_DUMP, Config.USER_ID)
except (ValueError, PeerIdInvalid, ChannelInvalid):
LOGGER.error(f"RECORDING_DUMP var Found and @{Config.USER_ID} is not a member of the group./ Channel")
Config.STARTUP_ERROR=f"RECORDING_DUMP var Found and @{Config.USER_ID} is not a member of the group./ Channel"
return False
if not k.status in ["administrator", "creator"]:
LOGGER.error(f"RECORDING_DUMP var Found and @{Config.USER_ID} is not a admin of the group./ Channel")
Config.STARTUP_ERROR=f"RECORDING_DUMP var Found and @{Config.USER_ID} is not a admin of the group./ Channel"
return False
if Config.CHAT:
try:
k=await USER.get_chat_member(Config.CHAT, Config.USER_ID)
if not k.status in ["administrator", "creator"]:
LOGGER.warning(f"{Config.USER_ID} is not an admin in {Config.CHAT}, it is recommended to run the user as admin.")
elif k.status in ["administrator", "creator"] and not k.can_manage_voice_chats:
LOGGER.warning(f"{Config.USER_ID} is not having right to manage voicechat, it is recommended to promote with this right.")
except (ValueError, PeerIdInvalid, ChannelInvalid):
Config.STARTUP_ERROR=f"The user account by which you generated the SESSION_STRING is not found on CHAT ({Config.CHAT})"
LOGGER.error(f"The user account by which you generated the SESSION_STRING is not found on CHAT ({Config.CHAT})")
return False
try:
k=await bot.get_chat_member(Config.CHAT, Config.BOT_USERNAME)
if not k.status == "administrator":
LOGGER.warning(f"{Config.BOT_USERNAME}, is not an admin in {Config.CHAT}, it is recommended to run the bot as admin.")
except (ValueError, PeerIdInvalid, ChannelInvalid):
Config.STARTUP_ERROR=f"Bot Was Not Found on CHAT, it is recommended to add {Config.BOT_USERNAME} to {Config.CHAT}"
LOGGER.warning(f"Bot Was Not Found on CHAT, it is recommended to add {Config.BOT_USERNAME} to {Config.CHAT}")
pass
if not Config.DATABASE_URI:
LOGGER.warning("No DATABASE_URI , found. It is recommended to use a database.")
return True↪️.атрибуты gitattributes
# Auto detect text files and perform LF normalization * text=auto
↪️.гитиньоре
*.pyc *.log *.session *.session-journal test.py todo.txt
↪️Файл Dockerfile
FROM nikolaik/python-nodejs:python3.9-nodejs16
RUN apt update && apt upgrade -y RUN apt install ffmpeg -y
COPY requirements.txt /requirements.txt RUN cd / RUN pip3 install -U pip && pip3 install -U -r requirements.txt RUN mkdir /VCPlayerBot WORKDIR /VCPlayerBot COPY start.sh /start.sh CMD ["/bin/bash", "/start.sh"]
↪️app.json
{
"name": "VCPlayerBot",
"description": "Telegram bot to stream video in telegram VC",
"repository": "https://github.com/subinps/VCPlayerBot",
"stack": "container",
"keywords": [
"telegram",
"bot",
"voicechat",
"music",
"python",
"pyrogram",
"pytgcalls",
"tgcalls",
"voip"
],
"env": {
"API_ID": {
"description": "api_id part of your Telegram API Key from my.telegram.org/apps",
"required": true
},
"API_HASH": {
"description": "api_hash part of your Telegram API Key from my.telegram.org/apps",
"required": true
},
"BOT_TOKEN": {
"description": "Bot token of Bot, get from @Botfather",
"required": true
},
"SESSION_STRING": {
"description": "Session string, read the README to learn how to export it with Pyrogram",
"required": true
},
"CHAT": {
"description": "ID of Channel or Group where the Bot plays Music",
"required": true
},
"LOG_GROUP": {
"description": "ID of the group to send playlist If CHAT is a Group, if channel then leave blank",
"required": false
},
"QUALITY": {
"description": "Default quality of your video player, Use one of high, medium or low.",
"value": "high",
"required": false
},
"DATABASE_URI": {
"description": "Mongo DB database URI , get from https://cloud.mongodb.com, even if this is optional, many of functions may not work if this is not set.",
"required": false
},
"ADMINS": {
"description": "ID of Users who can use Admin commands(for multiple users seperated by space)",
"required": true
},
"ADMIN_ONLY": {
"description": "Change it to True if you want to make /play command available for everyone.",
"value": "False",
"required": false
},
"HEROKU_API_KEY": {
"description": "Your heroku api key, get it from https://dashboard.heroku.com/account/applications/authorizations/new.",
"required": false
},
"HEROKU_APP_NAME": {
"description": "Heroku App Name.",
"required": false
},
"STARTUP_STREAM": {
"description": "YouTube live / Direct link to a video / Telegram link to a YouTube playlist.(Read the README for more info.) ",
"value": "https://youtu.be/zcrUCvBD16k",
"required": false
},
"REPLY_MESSAGE": {
"description": "A reply message to those who message the USER account in PM. Make it blank if you do not need this feature.",
"value": "Hey, Iam a bot to play music, not having time to chat with you.",
"required": false
}
},
"formation": {
"worker": {
"quantity": 1,
"size": "free"
}
}
}↪️bot.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>.
from pyrogram import Client
from config import Config
bot = Client(
"VCPlayer",
Config.API_ID,
Config.API_HASH,
bot_token=Config.BOT_TOKEN,
plugins=dict(root="plugins")
)↪️config.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from utils import LOGGER try: import os import heroku3 from dotenv import load_dotenv from ast import literal_eval as is_enabled
except ModuleNotFoundError:
import os
import sys
import subprocess
file=os.path.abspath("requirements.txt")
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', file, '--upgrade'])
os.execl(sys.executable, sys.executable, *sys.argv)
class Config:
#Telegram API Stuffs
load_dotenv() # load enviroment variables from .env file
ADMIN = os.environ.get("ADMINS", '')
SUDO = [int(admin) for admin in (ADMIN).split()] # Exclusive for heroku vars configuration.
ADMINS = [int(admin) for admin in (ADMIN).split()] #group admins will be appended to this list.
API_ID = int(os.environ.get("API_ID", ''))
API_HASH = os.environ.get("API_HASH", "")
BOT_TOKEN = os.environ.get("BOT_TOKEN", "")
SESSION = os.environ.get("SESSION_STRING", "") #Stream Chat and Log Group
CHAT = int(os.environ.get("CHAT", ""))
LOG_GROUP=os.environ.get("LOG_GROUP", "") #Stream
STREAM_URL=os.environ.get("STARTUP_STREAM", "https://www.youtube.com/watch?v=zcrUCvBD16k")
#Database
DATABASE_URI=os.environ.get("DATABASE_URI", None)
DATABASE_NAME=os.environ.get("DATABASE_NAME", "VCPlayerBot")
#heroku
API_KEY=os.environ.get("HEROKU_API_KEY", None)
APP_NAME=os.environ.get("HEROKU_APP_NAME", None)
#Optional Configuration
SHUFFLE=is_enabled(os.environ.get("SHUFFLE", 'True'))
ADMIN_ONLY=is_enabled(os.environ.get("ADMIN_ONLY", "False"))
REPLY_MESSAGE=os.environ.get("REPLY_MESSAGE", False)
EDIT_TITLE = os.environ.get("EDIT_TITLE", True)
#others
RECORDING_DUMP=os.environ.get("RECORDING_DUMP", False)
RECORDING_TITLE=os.environ.get("RECORDING_TITLE", False)
TIME_ZONE = os.environ.get("TIME_ZONE", "Asia/Kolkata")
IS_VIDEO=is_enabled(os.environ.get("IS_VIDEO", 'True'))
IS_LOOP=is_enabled(os.environ.get("IS_LOOP", 'True'))
DELAY=int(os.environ.get("DELAY", '10'))
PORTRAIT=is_enabled(os.environ.get("PORTRAIT", 'False'))
IS_VIDEO_RECORD=is_enabled(os.environ.get("IS_VIDEO_RECORD", 'True'))
DEBUG=is_enabled(os.environ.get("DEBUG", 'False'))
PTN=is_enabled(os.environ.get("PTN", "False")) #Quality vars
E_BITRATE=os.environ.get("BITRATE", False)
E_FPS=os.environ.get("FPS", False)
CUSTOM_QUALITY=os.environ.get("QUALITY", "100") #Search filters for cplay
FILTERS = [filter.lower() for filter in (os.environ.get("FILTERS", "video document")).split(" ")]
#Dont touch these, these are not for configuring player
GET_FILE={}
DATA={}
STREAM_END={}
SCHEDULED_STREAM={}
DUR={}
msg = {} SCHEDULE_LIST=[]
playlist=[]
CONFIG_LIST = ["ADMINS", "IS_VIDEO", "IS_LOOP", "REPLY_PM", "ADMIN_ONLY", "SHUFFLE", "EDIT_TITLE", "CHAT",
"SUDO", "REPLY_MESSAGE", "STREAM_URL", "DELAY", "LOG_GROUP", "SCHEDULED_STREAM", "SCHEDULE_LIST",
"IS_VIDEO_RECORD", "IS_RECORDING", "WAS_RECORDING", "RECORDING_TITLE", "PORTRAIT", "RECORDING_DUMP", "HAS_SCHEDULE",
"CUSTOM_QUALITY"]STARTUP_ERROR=None
ADMIN_CACHE=False
CALL_STATUS=False
YPLAY=False
YSTREAM=False
CPLAY=False
STREAM_SETUP=False
LISTEN=False
STREAM_LINK=False
IS_RECORDING=False
WAS_RECORDING=False
PAUSE=False
MUTED=False
HAS_SCHEDULE=None
IS_ACTIVE=None
VOLUME=100
CURRENT_CALL=None
BOT_USERNAME=None
USER_ID=None if LOG_GROUP:
LOG_GROUP=int(LOG_GROUP)
else:
LOG_GROUP=None
if not API_KEY or \
not APP_NAME:
HEROKU_APP=None
else:
HEROKU_APP=heroku3.from_key(API_KEY).apps()[APP_NAME]
if EDIT_TITLE in ["NO", 'False']:
EDIT_TITLE=False
LOGGER.info("Title Editing turned off")
if REPLY_MESSAGE:
REPLY_MESSAGE=REPLY_MESSAGE
REPLY_PM=True
LOGGER.info("Reply Message Found, Enabled PM MSG")
else:
REPLY_MESSAGE=False
REPLY_PM=False if E_BITRATE:
try:
BITRATE=int(E_BITRATE)
except:
LOGGER.error("Invalid bitrate specified.")
E_BITRATE=False
BITRATE=48000
if not BITRATE >= 48000:
BITRATE=48000
else:
BITRATE=48000
if E_FPS:
try:
FPS=int(E_FPS)
except:
LOGGER.error("Invalid FPS specified")
E_FPS=False
if not FPS >= 30:
FPS=30
else:
FPS=30
try:
CUSTOM_QUALITY=int(CUSTOM_QUALITY)
if CUSTOM_QUALITY > 100:
CUSTOM_QUALITY = 100
LOGGER.warning("maximum quality allowed is 100, invalid quality specified. Quality set to 100")
elif CUSTOM_QUALITY < 10:
LOGGER.warning("Minimum Quality allowed is 10., Qulaity set to 10")
CUSTOM_QUALITY = 10
if 66.9 < CUSTOM_QUALITY < 100:
if not E_BITRATE:
BITRATE=48000
elif 50 < CUSTOM_QUALITY < 66.9:
if not E_BITRATE:
BITRATE=36000
else:
if not E_BITRATE:
BITRATE=24000
except:
if CUSTOM_QUALITY.lower() == 'high':
CUSTOM_QUALITY=100
elif CUSTOM_QUALITY.lower() == 'medium':
CUSTOM_QUALITY=66.9
elif CUSTOM_QUALITY.lower() == 'low':
CUSTOM_QUALITY=50
else:
LOGGER.warning("Invalid QUALITY specified.Defaulting to High.")
CUSTOM_QUALITY=100 #help strings
PLAY_HELP="""
__You can play using any of these options__1. Play a video from a YouTube link. Command: **/play** __You can use this as a reply to a YouTube link or pass link along command. or as a reply to message to search that in YouTube.__
2. Play from a telegram file. Command: **/play** __Reply to a supported media(video and documents or audio file ).__ Note: __For both the cases /fplay also can be used by admins to play the song immediately without waiting for queue to end.__
3. Play from a YouTube playlist Command: **/yplay** __First get a playlist file from @GetPlaylistBot or @DumpPlaylist and reply to playlist file.__
4. Live Stream Command: **/stream** __Pass a live stream URL or any direct URL to play it as stream.__
5. Import an old playlist. Command: **/import** __Reply to a previously exported playlist file. __
6. Channel Play Command: **/cplay** __Use `/cplay channel username or channel id` to play all the files from the given channel. By default both video files and documents will be played . You can add or remove the file type using `FILTERS` var. For example , to stream audio, video and document from the channel use `/env FILTERS video document audio` . If you need only audio , you can use `/env FILTERS video audio` and so on. To set up the files from a channel as STARTUP_STREAM, so that the files will be automatically added to playlist on startup of bot. use `/env STARTUP_STREAM channel username or channel id`
Note that for public channels you should use username of channels along with '@' and for private channels you should use channel id.
For private channels , make sure both the bot and USER account is a member of channel.__
"""
SETTINGS_HELP="""
**You can easily customize you player as per you needs. The following configurations are available:**🔹Command: **/settings**
🔹AVAILABLE CONFIGURATIONS:
**Player Mode** - __This allows you to run your player as 24/7 music player or only when there is song in queue. If disabled, player will leave from the call when the playlist is empty. Otherwise STARTUP_STREAM will be streamed when playlist id empty.__
**Video Enabled** - __This allows you to switch between audio and video. if disabled, video files will be played as audio.__
**Admin Only** - __Enabling this will restrict non-admin users from using play command.__
**Edit Title** - __Enabling this will edit your VideoChat title to current playing songs name.__
**Shuffle Mode** - __Enabling this will shuffle the playlist whenever you import a playlist or using /yplay __
**Auto Reply** - __Choose whether to reply the PM messages of playing user account. You can set up a custom reply message using `REPLY_MESSAGE` confug.__
"""
SCHEDULER_HELP="""
__VCPlayer allows you to schedule a stream.
This means you can schedule a stream for a future date and on the scheduled date, stream will be played automatically.
At present you can schedule a stream for even one year!!. Make sure you have set up a databse, else you will loose your schedules whenever the player restarts. __Command: **/schedule**
__Reply to a file or a youtube video or even a text message with schedule command. The replied media or youtube video will be scheduled and will be played on the scheduled date. The scheduling time is by default in IST and you can change the timezone using `TIME_ZONE` config.__
Command: **/slist** __View your current scheduled streams.__
Command: **/cancel** __Cancel a schedule by its schedule id, You can get the schedule id using /slist command__
Command: **/cancelall**
__Cancel all the scheduled streams__
"""
RECORDER_HELP="""
__With VCPlayer you can easily record all your video chats.
By default telegram allows you to record for a maximum duration of 4 hours.
An attempt to overcome this limit has been made by automatically restarting the recording after 4 hours__Command: **/record**
AVAILABLE CONFIGURATIONS: 1. Record Video: __If enabled both the video and audio of the stream will be recorded, otherwise only audio will be recorded.__
2. Video dimension: __Choose between portrait and landscape dimensions for your recording__
3. Custom Recording Title: __Set up a custom recording title for your recordings. Use a command /rtitle to configure this. To turn off the custom title, use `/rtitle False `__
4. Recording Dumb: __You can set up forwarding all your recordings to a channel, this will be useful since otherwise recordings are sent to saved messages of streaming account. Setup using `RECORDING_DUMP` config.__
⚠️ If you start a recording with vcplayer, make sure you stop the same with vcplayer.
"""
CONTROL_HELP=""" __VCPlayer allows you to control your streams easily__ 1. Skip a song. Command: **/skip** __You can pass a number greater than 2 to skip the song in that position.__
2. Pause the player. Command: **/pause**
3. Resume the player. Command: **/resume**
4. Change Volume. Command: **/volume** __Pass the volume in between 1-200.__
5. Leave the VC. Command: **/leave**
6. Shuffle the playlist. Command: **/shuffle**
7. Clear the current playlist queue. Command: **/clearplaylist**
8. Seek the video. Command: **/seek** __You can pass number of seconds to be skipped. Example: /seek 10 to skip 10 sec. /seek -10 to rewind 10 sec.__
9. Mute the player. Command: **/vcmute**
10. Unmute the player. Command : **/vcunmute**
11. Shows the playlist. Command: **/playlist** __Use /player to show with control buttons__ """
ADMIN_HELP=""" __VCPlayer allows to control admins, that is you can add admins and remove them easily. It is recommended to use a MongoDb database for better experience, else all you admins will get reset after restart.__
Command: **/vcpromote** __You can promote a admin with their username or user id or by replying to that users message.__
Command: **/vcdemote** __Remove an admin from admin list__
Command: **/refresh** __Refresh the admin list of chat__ """
MISC_HELP=""" Command: **/export** __VCPlayer allows you to export your current playlist for future use.__ __A json file will be sent to you and the same can be used along /import command.__
Command : **/logs** __If your player went something gone wrong, you can easily check the logs using /logs__ Command : **/env** __Setup your config vars with /env command.__ __Example: To set up a__ `REPLY_MESSAGE` __use__ `/env REPLY_MESSAGE=Hey, Check out @subin_works rather than spamming in my PM`__ __You can delete a config var by ommiting a value for that, Example:__ `/env LOG_GROUP=` __this will delete the existing LOG_GROUP config.
Command: **/config** __Same as using /env**
Command: **/update** __Updates youe bot with latest changes__
Tip: __You can easily change the CHAT config by adding the user account and bot account to any other group and any command in new group__
"""
ENV_HELP="""
**These are the configurable vars available and you can set each one of them using /env command****Mandatory Vars**
1. `API_ID` : __Get From [my.telegram.org](https://my.telegram.org/)__
2. `API_HASH` : __Get from [my.telegram.org](https://my.telegram.org)__
3. `BOT_TOKEN` : __[@Botfather](https://telegram.dog/BotFather)__
4. `SESSION_STRING` : __Generate From here [GenerateStringName](https://repl.it/@subinps/getStringName)__
5. `CHAT` : __ID of Channel/Group where the bot plays Music.__
6. `STARTUP_STREAM` : __This will be streamed on startups and restarts of bot. You can use either any STREAM_URL or a direct link of any video or a Youtube Live link. You can also use YouTube Playlist.Find a Telegram Link for your playlist from [PlayList Dumb](https://telegram.dog/DumpPlaylist) or get a PlayList from [PlayList Extract](https://telegram.dog/GetAPlaylistbot). The PlayList link should in form `https://t.me/DumpPlaylist/xxx` You can also use the files from a channel as startup stream. For that just use the channel id or channel username of channel as STARTUP_STREAM value. For more info on channel play , read help from player section.__
**Recommended Optional Vars**
1. `DATABASE_URI`: __MongoDB database Url, get from [mongodb](https://cloud.mongodb.com). This is an optional var, but it is recomonded to use this to experiance the full features.__
2. `HEROKU_API_KEY`: __Your heroku api key. Get one from [here](https://dashboard.heroku.com/account/applications/authorizations/new)__
3. `HEROKU_APP_NAME`: __Your heroku app's name.__
4. `FILTERS`: __Filters for channel play file search. Read help about cplay in player section.__
**Other Optional Vars** 1. `LOG_GROUP` : __Group to send Playlist, if CHAT is a Group__
2. `ADMINS` : __ID of users who can use admin commands.__
3. `REPLY_MESSAGE` : __A reply to those who message the USER account in PM. Leave it blank if you do not need this feature. (Configurable through buttons if mongodb added. Use /settings)__
4. `ADMIN_ONLY` : __Pass `True` If you want to make /play command only for admins of `CHAT`. By default /play is available for all.(Configurable through buttons if mongodb added. Use /settings)__
5. `DATABASE_NAME`: __Database name for your mongodb database.mongodb__
6. `SHUFFLE` : __Make it `False` if you dont want to shuffle playlists. (Configurable through buttons)__
7. `EDIT_TITLE` : __Make it `False` if you do not want the bot to edit video chat title according to playing song. (Configurable through buttons if mongodb added. Use /settings)__
8. `RECORDING_DUMP` : __A Channel ID with the USER account as admin, to dump video chat recordings.__
9. `RECORDING_TITLE`: __A custom title for your videochat recordings.__
10. `TIME_ZONE` : __Time Zone of your country, by default IST__
11. `IS_VIDEO_RECORD` : __Make it `False` if you do not want to record video, and only audio will be recorded.(Configurable through buttons if mongodb added. Use /record)__
12. `IS_LOOP` ; __Make it `False` if you do not want 24 / 7 Video Chat. (Configurable through buttons if mongodb added.Use /settings)__
13. `IS_VIDEO` : __Make it `False` if you want to use the player as a musicplayer without video. (Configurable through buttons if mongodb added. Use /settings)__
14. `PORTRAIT`: __Make it `True` if you want the video recording in portrait mode. (Configurable through buttons if mongodb added. Use /record)__
15. `DELAY` : __Choose the time limit for commands deletion. 10 sec by default.__
16. `QUALITY` : __Customize the quality of video chat, use one of `high`, `medium`, `low` . __
17. `BITRATE` : __Bitrate of audio (Not recommended to change).__
18. `FPS` : __Fps of video to be played (Not recommended to change.)__
"""
↪️env.образец
API_ID="your-app-id" API_HASH="your-api-hash" BOT_TOKEN="your-bot-token" SESSION_STRING="your-session-string" CHAT="your-chat-id" ADMINS="your-admins"
↪️heroku.yml
build:
docker:
worker: Dockerfile↪️install_node.sh
echo "deb https://deb.nodesource.com/node_16.x buster main" > /etc/apt/sources.list.d/nodesource.list && \ wget -qO- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list && \ wget -qO- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ apt-get update && \ apt-get install -yqq nodejs yarn && \ pip install -U pip && pip install pipenv && \ npm i -g npm@^7 && \ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python && ln -s /root/.poetry/bin/poetry /usr/local/bin && \ rm -rf /var/lib/apt/lists/*
↪️main.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import (
play,
start_stream,
startup_check,
sync_from_db,
check_changes
)
from user import group_call, USER
from utils import LOGGER
from config import Config
from pyrogram import idle
from bot import bot
import asyncio
import osif Config.DATABASE_URI:
from utils import db
async def main():
await bot.start()
Config.BOT_USERNAME = (await bot.get_me()).username
LOGGER.info(f"{Config.BOT_USERNAME} Started.")
if Config.DATABASE_URI:
try:
if await db.is_saved("RESTART"):
msg=await db.get_config("RESTART")
if msg:
try:
k=await bot.edit_message_text(msg['chat_id'], msg['msg_id'], text="Succesfully restarted.")
await db.del_config("RESTART")
except:
pass
await check_changes()
await sync_from_db()
except Exception as e:
LOGGER.error(f"Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}", exc_info=True)
Config.STARTUP_ERROR="Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}"
LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.")
await bot.stop()
from utils import debug
await debug.start()
await idle()
return if Config.DEBUG:
LOGGER.info("Debugging enabled by user, Now in debug mode.")
Config.STARTUP_ERROR="Debugging enabled by user, Now in debug mode."
from utils import debug
await bot.stop()
await debug.start()
await idle()
return try:
await group_call.start()
Config.USER_ID = (await USER.get_me()).id
k=await startup_check()
if k == False:
LOGGER.error("Startup checks not passed , bot is quiting")
await bot.stop()
LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.")
from utils import debug
await debug.start()
await idle()
return if Config.IS_LOOP:
if Config.playlist:
await play()
LOGGER.info("Loop play enabled and playlist is not empty, resuming playlist.")
else:
LOGGER.info("Loop play enabled , starting playing startup stream.")
await start_stream()
except Exception as e:
if "unpack requires" in str(e):
LOGGER.error("You Have to generate a new session string from the link given in README of the repo and replace the existing one with the new.")
LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.")
Config.STARTUP_ERROR=f"You Have to generate a new session string from the link given in README of the repo and replace the existing one with the new. \nGenerate string session from https://repl.it/@subinps/getStringName"
else:
LOGGER.error(f"Startup was unsuccesfull, Errors - {e}", exc_info=True)
LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.")
Config.STARTUP_ERROR=f"Startup was unsuccesfull, Errors - {e}"
from utils import debug
await bot.stop()
await debug.start()
await idle()
return await idle()
await bot.stop()if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())↪️requirements.txt
pyrogram==1.3.6 py-tgcalls==0.8.2 parse-torrent-name tgcrypto yt-dlp youtube-search-python httpx==0.23.0 youtube-search heroku3 pillow motor dnspython pytz apscheduler python-dotenv
↪️start.sh
echo "Cloning Repo...." if [ -z $BRANCH ] then echo "Cloning main branch...." git clone https://github.com/subinps/VCPlayerBot /VCPlayerBot else echo "Cloning $BRANCH branch...." git clone https://github.com/subinps/VCPlayerBot -b $BRANCH /VCPlayerBot fi cd /VCPlayerBot pip3 install -U -r requirements.txt echo "Starting Bot...." python3 main.py
↪️user.py
#!/usr/bin/env python3 # Copyright (C) @subinps # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version.
# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from pytgcalls import PyTgCalls from pyrogram import Client from config import Config from utils import LOGGER
USER = Client(
Config.SESSION,
Config.API_ID,
Config.API_HASH,
plugins=dict(root="userplugins")
)
group_call = PyTgCalls(USER, cache_duration=180)