diff --git a/README.md b/README.md index b25b2f88..3712d639 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -

Music Play Bot 🎵

+

Telegram Group Music Player Bot 🎵

-### Here the advanced branch with more features 🙂 +### A bot that can play music on telegram group's voice call

- +

Requirements 📝

@@ -16,7 +16,7 @@ ### Commands 🛠 #### For all in group - `/play` - reply to youtube url or song file to play song -- `/ytp ` - play song without youtube url or song file (best method) +- `/play ` - play song you requested - `/song ` - download songs you want quickly - `/search ` - search videos on youtube with details @@ -28,14 +28,13 @@ ### Deploy To Heroku -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/InukaAsith/GroupMusicBot) +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Infinity-Bots/GroupMusicPlayerBot) -Get pyrogram STRING_NAME from here ⬇️ - -[![GenerateString](https://img.shields.io/badge/repl.it-generateString-yellowgreen)](https://replit.com/@SpEcHiDe/GenerateStringSession) +Use [Repl Link](https://replit.com/@SpEcHiDe/GenerateStringSession) to get pyrogram string session ### Credits -- [ImJanindu](https://github.com/Imjanindu): Dev +- [ImJanindu](https://github.com/ImJanindu): Dev +- [InukaASiTH](https://github.com/InukaAsith): Dev - [Laky](https://github.com/Laky-64) & [Andrew](https://github.com/AndrewLaneX): PyTgCalls - [Original Repo](https://github.com/suprojects/CallsMusic) - [Infinity BOTs](https://t.me/Infinity_BOTs) diff --git a/etc/font.otf b/etc/font.otf index 442742e0..3c356311 100644 Binary files a/etc/font.otf and b/etc/font.otf differ diff --git a/etc/foreground.png b/etc/foreground.png index 09aa8666..612d22dc 100644 Binary files a/etc/foreground.png and b/etc/foreground.png differ diff --git a/etc/foreground_square.png b/etc/foreground_square.png index 3ef6c94e..bfe66210 100644 Binary files a/etc/foreground_square.png and b/etc/foreground_square.png differ diff --git a/etc/tg_vc_bot.png b/etc/tg_vc_bot.png index e703a3f2..850fa088 100644 Binary files a/etc/tg_vc_bot.png and b/etc/tg_vc_bot.png differ diff --git a/etc/thumb.jpg b/etc/thumb.jpg new file mode 100644 index 00000000..46d1ca67 Binary files /dev/null and b/etc/thumb.jpg differ diff --git a/handlers/inline.py b/handlers/inline.py deleted file mode 100644 index efe9b275..00000000 --- a/handlers/inline.py +++ /dev/null @@ -1,51 +0,0 @@ -from pyrogram import Client, errors -from pyrogram.types import InlineQuery, InlineQueryResultArticle, InputTextMessageContent - -from youtubesearchpython import VideosSearch - - -@Client.on_inline_query() -async def inline(client: Client, query: InlineQuery): - answers = [] - search_query = query.query.lower().strip().rstrip() - - if search_query == "": - await client.answer_inline_query( - query.id, - results=answers, - switch_pm_text="Type a YouTube video name...", - switch_pm_parameter="help", - cache_time=0 - ) - else: - search = VideosSearch(search_query, limit=50) - - for result in search.result()["result"]: - answers.append( - InlineQueryResultArticle( - title=result["title"], - description="{}, {} views.".format( - result["duration"], - result["viewCount"]["short"] - ), - input_message_content=InputTextMessageContent( - "https://www.youtube.com/watch?v={}".format( - result["id"] - ) - ), - thumb_url=result["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="Error: Search timed out", - switch_pm_parameter="", - ) diff --git a/handlers/play.py b/handlers/play.py index 9f26efe9..01522027 100644 --- a/handlers/play.py +++ b/handlers/play.py @@ -1,34 +1,25 @@ +import os from os import path - -from pyrogram import Client -from pyrogram.types import Message, Voice - +from pyrogram import Client, filters +from pyrogram.types import Message, Voice, InlineKeyboardButton, InlineKeyboardMarkup +from pyrogram.errors import UserAlreadyParticipant from callsmusic import callsmusic, queues - -from os import path +from callsmusic.callsmusic import client as USER +from helpers.admins import get_administrators import requests import aiohttp import youtube_dl from youtube_search import YoutubeSearch - - import converter from downloaders import youtube - -from config import BOT_NAME as bn, DURATION_LIMIT -from helpers.filters import command, other_filters +from config import DURATION_LIMIT +from helpers.filters import command from helpers.decorators import errors from helpers.errors import DurationLimitError from helpers.gets import get_url, get_file_name -from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup - -import os -import aiohttp import aiofiles import ffmpeg -from PIL import Image -from PIL import ImageFont -from PIL import ImageDraw +from PIL import Image, ImageFont, ImageDraw def transcode(filename): @@ -83,7 +74,7 @@ async def generate_cover(requested_by, title, views, duration, thumbnail): ) draw.text((190, 630), f"Views: {views}", (255, 255, 255), font=font) draw.text((190, 670), - f"Played By: {requested_by}", + f"Added By: {requested_by}", (255, 255, 255), font=font, ) @@ -94,62 +85,92 @@ async def generate_cover(requested_by, title, views, duration, thumbnail): -@Client.on_message(command("play") & other_filters) -@errors +@Client.on_message(command("play") + & filters.group + & ~filters.edited + & ~filters.forwarded + & ~filters.via_bot) async def play(_, message: Message): - lel = await message.reply("🔄 **Processing** sounds...") - sender_id = message.from_user.id - sender_name = message.from_user.first_name - - keyboard = InlineKeyboardMarkup( - [ - [ - InlineKeyboardButton( - text="🔊 Channel", - url="https://t.me/Infinity_BOTs") - - ] - ] - ) - + lel = await message.reply("🔄 **Processing...**") + + administrators = await get_administrators(message.chat) + chid = message.chat.id + + try: + user = await USER.get_me() + except: + user.first_name = "Mizuki" + usar = user + wew = usar.id + try: + await _.get_chat_member(chid, wew) + except: + for administrator in administrators: + if administrator == message.from_user.id: + try: + invitelink = await _.export_chat_invite_link(chid) + except: + await lel.edit( + "Add me as admin of yor group first!") + return + + try: + await USER.join_chat(invitelink) + await USER.send_message( + message.chat.id, "**Mizuki Music assistant joined this group for play music 🎵**") + + except UserAlreadyParticipant: + pass + except Exception: + await lel.edit( + f"🛑 Flood Wait Error 🛑 \n\Hey {user.first_name}, assistant userbot couldn't join your group due to heavy join requests. Make sure userbot is not banned in group and try again later!") + try: + await USER.get_chat(chid) + except: + await lel.edit( + f"Hey {user.first_name}, assistant userbot is not in this chat, ask admin to send /play command for first time to add it.") + return + audio = (message.reply_to_message.audio or message.reply_to_message.voice) if message.reply_to_message else None url = get_url(message) if audio: if round(audio.duration / 60) > DURATION_LIMIT: raise DurationLimitError( - f"❌ Videos longer than {DURATION_LIMIT} minute(s) aren't allowed to play!" + f"❌ Videos longer than {DURATION_LIMIT} minutes aren't allowed to play!" ) file_name = get_file_name(audio) title = file_name - thumb_name = "https://telegra.ph/file/a4fa687ed647cfef52402.jpg" + thumb_name = "https://telegra.ph/file/caeb50039026a746e7252.jpg" thumbnail = thumb_name duration = round(audio.duration / 60) views = "Locally added" + keyboard = InlineKeyboardMarkup( + [ [ - [ - InlineKeyboardButton( - text="🔊 Channel", - url=f"https://t.me/Daisyxupdates") - - ] + InlineKeyboardButton( + text="Channel 🔊", + url="https://t.me/Infinity_BOTs") + ] - ) - requested_by = message.from_user.mention() + ] + ) + + requested_by = message.from_user.first_name await generate_cover(requested_by, title, views, duration, thumbnail) file_path = await converter.convert( (await message.reply_to_message.download(file_name)) if not path.isfile(path.join("downloads", file_name)) else file_name ) + elif url: try: results = YoutubeSearch(url, max_results=1).to_dict() - # url = f"https://youtube.com{results[0]['url_suffix']}" - #print(results) - title = results[0]["title"][:40] + # print results + title = results[0]["title"] thumbnail = results[0]["thumbnails"][0] thumb_name = f'thumb{title}.jpg' thumb = requests.get(thumbnail, allow_redirects=True) @@ -157,53 +178,60 @@ async def play(_, message: Message): duration = results[0]["duration"] url_suffix = results[0]["url_suffix"] views = results[0]["views"] + durl = url + durl = durl.replace("youtube", "youtubepp") + + secmul, dur, dur_arr = 1, 0, duration.split(':') + for i in range(len(dur_arr)-1, -1, -1): + dur += (int(dur_arr[i]) * secmul) + secmul *= 60 + keyboard = InlineKeyboardMarkup( + [ [ - [ - InlineKeyboardButton( - text="Watch On YouTube 🎬", - url=f"{url}") + InlineKeyboardButton( + text="YouTube 🎬", + url=f"{url}"), + InlineKeyboardButton( + text="Download 📥", + url=f"{durl}") - ] ] - ) + ] + ) except Exception as e: title = "NaN" - thumb_name = "https://telegra.ph/file/a4fa687ed647cfef52402.jpg" + thumb_name = "https://telegra.ph/file/638c20c44ca418c8b2178.jpg" duration = "NaN" views = "NaN" keyboard = InlineKeyboardMarkup( [ [ InlineKeyboardButton( - text="Watch On YouTube 🎬", + text="YouTube 🎬", url=f"https://youtube.com") ] ] ) - requested_by = message.from_user.mention() + if (dur / 60) > DURATION_LIMIT: + await lel.edit(f"❌ Videos longer than {DURATION_LIMIT} minutes aren't allowed to play!") + return + requested_by = message.from_user.first_name await generate_cover(requested_by, title, views, duration, thumbnail) file_path = await converter.convert(youtube.download(url)) else: - await lel.edit("🔎 **Finding** the song...") - sender_id = message.from_user.id - user_id = message.from_user.id - sender_name = message.from_user.first_name - user_name = message.from_user.first_name - rpk = "["+user_name+"](tg://user?id="+str(user_id)+")" - - query = '' - for i in message.command[1:]: - query += ' ' + str(i) - print(query) - await lel.edit("🎵 **Processing** sounds...") - ydl_opts = {"format": "bestaudio[ext=m4a]"} + if len(message.command) < 2: + return await lel.edit("🧐 **What's the song you want to play?**") + await lel.edit("🔎 **Finding the song...**") + query = message.text.split(None, 1)[1] + # print(query) + await lel.edit("🎵 **Processing sounds...**") try: results = YoutubeSearch(query, max_results=1).to_dict() url = f"https://youtube.com{results[0]['url_suffix']}" - #print(results) - title = results[0]["title"][:40] + # print results + title = results[0]["title"] thumbnail = results[0]["thumbnails"][0] thumb_name = f'thumb{title}.jpg' thumb = requests.get(thumbnail, allow_redirects=True) @@ -211,9 +239,16 @@ async def play(_, message: Message): duration = results[0]["duration"] url_suffix = results[0]["url_suffix"] views = results[0]["views"] - + durl = url + durl = durl.replace("youtube", "youtubepp") + + secmul, dur, dur_arr = 1, 0, duration.split(':') + for i in range(len(dur_arr)-1, -1, -1): + dur += (int(dur_arr[i]) * secmul) + secmul *= 60 + except Exception as e: - lel.edit( + await lel.edit( "❌ Song not found.\n\nTry another song or maybe spell it properly." ) print(str(e)) @@ -223,13 +258,20 @@ async def play(_, message: Message): [ [ InlineKeyboardButton( - text="Watch On YouTube 🎬", - url=f"{url}") + text="YouTube 🎬", + url=f"{url}"), + InlineKeyboardButton( + text="Download 📥", + url=f"{durl}") ] ] ) - requested_by = message.from_user.mention() + + if (dur / 60) > DURATION_LIMIT: + await lel.edit(f"❌ Videos longer than {DURATION_LIMIT} minutes aren't allowed to play!") + return + requested_by = message.from_user.first_name await generate_cover(requested_by, title, views, duration, thumbnail) file_path = await converter.convert(youtube.download(url)) @@ -237,7 +279,9 @@ async def play(_, message: Message): position = await queues.put(message.chat.id, file=file_path) await message.reply_photo( photo="final.png", - caption=f"#⃣ Your requested song **queued** at position {position}!", + caption="**🎵 Song:** {}\n**🕒 Duration:** {} min\n**👤 Added By:** {}\n\n**#⃣ Queued Position:** {}".format( + title, duration, message.from_user.mention(), position + ), reply_markup=keyboard) os.remove("final.png") return await lel.delete() @@ -246,9 +290,8 @@ async def play(_, message: Message): await message.reply_photo( photo="final.png", reply_markup=keyboard, - caption="▶️ **Playing** here the song requested by {} via DaisyX Music 😜".format( - message.from_user.mention() - ), - ) + caption="**🎵 Song:** {}\n**🕒 Duration:** {} min\n**👤 Added By:** {}\n\n**▶️ Now Playing at `{}`...**".format( + title, duration, message.from_user.mention(), message.chat.title + ), ) os.remove("final.png") return await lel.delete() diff --git a/handlers/private.py b/handlers/private.py index 3b82c87f..18d5089d 100644 --- a/handlers/private.py +++ b/handlers/private.py @@ -19,7 +19,7 @@ async def start(_, message: Message): [ [ InlineKeyboardButton( - "🛠 Source Code 🛠", url="https://github.com/ImJanindu/GroupMusicBot") + "🛠 Source Code 🛠", url="https://github.com/Infinity-Bots/GroupMusicPlayerBot") ],[ InlineKeyboardButton( "💬 Group", url="https://t.me/InfinityBOTs_Support" diff --git a/handlers/songs.py b/handlers/songs.py index 8c8d1885..1ec1781c 100644 --- a/handlers/songs.py +++ b/handlers/songs.py @@ -1,69 +1,104 @@ +# Infinity Bots (https://t.me/Infinity_Bots) + import os -import requests import aiohttp -import youtube_dl - +import asyncio +import json +import sys +import time +from youtubesearchpython import SearchVideos from pyrogram import filters, Client -from youtube_search import YoutubeSearch - -def time_to_seconds(time): - stringt = str(time) - return sum(int(x) * 60 ** i for i, x in enumerate(reversed(stringt.split(':')))) - - -@Client.on_message(filters.command('song') & ~filters.private & ~filters.channel) -def song(client, message): - - user_id = message.from_user.id - user_name = message.from_user.first_name - rpk = "["+user_name+"](tg://user?id="+str(user_id)+")" +from youtube_dl import YoutubeDL +from youtube_dl.utils import ( + ContentTooShortError, + DownloadError, + ExtractorError, + GeoRestrictedError, + MaxDownloadsReached, + PostProcessingError, + UnavailableVideoError, + XAttrMetadataError, +) - query = '' - for i in message.command[1:]: - query += ' ' + str(i) - print(query) - m = message.reply('🔎 Finding the song...') - ydl_opts = {"format": "bestaudio[ext=m4a]"} +@Client.on_message(filters.command("song") & ~filters.edited) +async def song(client, message): + cap = "@JEBotZ" + url = message.text.split(None, 1)[1] + rkp = await message.reply("Processing...") + if not url: + await rkp.edit("**What's the song you want?**\nUsage`/song `") + search = SearchVideos(url, offset=1, mode="json", max_results=1) + test = search.result() + p = json.loads(test) + q = p.get("search_result") try: - results = YoutubeSearch(query, max_results=1).to_dict() - link = f"https://youtube.com{results[0]['url_suffix']}" - #print(results) - title = results[0]["title"][:40] - thumbnail = results[0]["thumbnails"][0] - thumb_name = f'thumb{title}.jpg' - thumb = requests.get(thumbnail, allow_redirects=True) - open(thumb_name, 'wb').write(thumb.content) - - - duration = results[0]["duration"] - url_suffix = results[0]["url_suffix"] - views = results[0]["views"] - - except Exception as e: - m.edit( - "❌ Found Nothing.\n\nTry another keywork or maybe spell it properly." + url = q[0]["link"] + except BaseException: + return await rkp.edit("Failed to find that song.") + type = "audio" + if type == "audio": + opts = { + "format": "bestaudio", + "addmetadata": True, + "key": "FFmpegMetadata", + "writethumbnail": True, + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "mp3", + "preferredquality": "320", + } + ], + "outtmpl": "%(id)s.mp3", + "quiet": True, + "logtostderr": False, + } + song = True + try: + await rkp.edit("Downloading...") + with YoutubeDL(opts) as rip: + rip_data = rip.extract_info(url) + except DownloadError as DE: + await rkp.edit(f"`{str(DE)}`") + return + except ContentTooShortError: + await rkp.edit("`The download content was too short.`") + return + except GeoRestrictedError: + await rkp.edit( + "`Video is not available from your geographic location due to geographic restrictions imposed by a website.`" ) - print(str(e)) return - m.edit("Downloading the song by @Infinity_BOTs...") - try: - with youtube_dl.YoutubeDL(ydl_opts) as ydl: - info_dict = ydl.extract_info(link, download=False) - audio_file = ydl.prepare_filename(info_dict) - ydl.process_info(info_dict) - rep = '**🎵 Uploaded by @Infinity_BOTs**' - secmul, dur, dur_arr = 1, 0, duration.split(':') - for i in range(len(dur_arr)-1, -1, -1): - dur += (int(dur_arr[i]) * secmul) - secmul *= 60 - message.reply_audio(audio_file, caption=rep, thumb=thumb_name, parse_mode='md', title=title, duration=dur) - m.delete() - except Exception as e: - m.edit('❌ Error') - print(e) - - try: - os.remove(audio_file) - os.remove(thumb_name) + except MaxDownloadsReached: + await rkp.edit("`Max-downloads limit has been reached.`") + return + except PostProcessingError: + await rkp.edit("`There was an error during post processing.`") + return + except UnavailableVideoError: + await rkp.edit("`Media is not available in the requested format.`") + return + except XAttrMetadataError as XAME: + await rkp.edit(f"`{XAME.code}: {XAME.msg}\n{XAME.reason}`") + return + except ExtractorError: + await rkp.edit("`There was an error during info extraction.`") + return except Exception as e: - print(e) + await rkp.edit(f"{str(type(e)): {str(e)}}") + return + time.time() + if song: + await rkp.edit("Uploading...") #ImJanindu + lol = "./etc/thumb.jpg" + lel = await message.reply_audio( + f"{rip_data['id']}.mp3", + duration=int(rip_data["duration"]), + title=str(rip_data["title"]), + performer=str(rip_data["uploader"]), + thumb=lol, + caption=cap) #JEBotZ + await rkp.delete() diff --git a/handlers/ytplay b/handlers/ytplay deleted file mode 100644 index 08201a3e..00000000 --- a/handlers/ytplay +++ /dev/null @@ -1,108 +0,0 @@ -import os -from os import path -import requests -import aiohttp -import youtube_dl -from pyrogram import Client -from pyrogram.types import Message, Voice -from youtube_search import YoutubeSearch -from callsmusic import callsmusic, queues - -import converter -from downloaders import youtube - -from config import BOT_NAME as bn, DURATION_LIMIT -from helpers.filters import command, other_filters -from helpers.decorators import errors -from helpers.errors import DurationLimitError -from helpers.gets import get_url, get_file_name -from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup - -@Client.on_message(command("ytp") & other_filters) -@errors -async def play(_, message: Message): - - lel = await message.reply("🔎 **Finding** the song...") - sender_id = message.from_user.id - user_id = message.from_user.id - sender_name = message.from_user.first_name - user_name = message.from_user.first_name - rpk = "["+user_name+"](tg://user?id="+str(user_id)+")" - - query = '' - for i in message.command[1:]: - query += ' ' + str(i) - print(query) - await lel.edit("🎵 **Processing** sounds...") - ydl_opts = {"format": "bestaudio[ext=m4a]"} - try: - results = YoutubeSearch(query, max_results=1).to_dict() - url = f"https://youtube.com{results[0]['url_suffix']}" - #print(results) - title = results[0]["title"][:40] - thumbnail = results[0]["thumbnails"][0] - thumb_name = f'thumb{title}.jpg' - thumb = requests.get(thumbnail, allow_redirects=True) - open(thumb_name, 'wb').write(thumb.content) - - - duration = results[0]["duration"] - url_suffix = results[0]["url_suffix"] - views = results[0]["views"] - - except Exception as e: - lel.edit( - "❌ Song not found.\n\nTry another song or maybe spell it properly." - ) - print(str(e)) - return - - keyboard = InlineKeyboardMarkup( - [ - [ - InlineKeyboardButton( - text="Watch On YouTube 🎬", - url=f"{url}") - - ] - ] - ) - - keyboard2 = InlineKeyboardMarkup( - [ - [ - InlineKeyboardButton( - text="Watch On YouTube 🎬", - url=f"{url}") - - ] - ] - ) - - audio = (message.reply_to_message.audio or message.reply_to_message.voice) if message.reply_to_message else None - - if audio: - await lel.edit_text("Lel") - - elif url: - file_path = await converter.convert(youtube.download(url)) - else: - return await lel.edit_text("❗ You did not give me anything to play!") - - if message.chat.id in callsmusic.pytgcalls.active_calls: - position = await queues.put(message.chat.id, file=file_path) - await message.reply_photo( - photo=thumb_name, - caption=f"#⃣ Your requested song **queued** at position {position}!", - reply_markup=keyboard2) - return await lel.delete() - else: - callsmusic.pytgcalls.join_group_call(message.chat.id, file_path) - await message.reply_photo( - photo=thumb_name, - reply_markup=keyboard, - caption="▶️ **Playing** here the song requested by {} via YouTube Music 😜".format( - message.from_user.mention() - ), - ) - return await lel.delete() diff --git a/requirements.txt b/requirements.txt index 1a6cec23..7f2a3a13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ pyrogram TgCrypto -py-tgcalls +py-tgcalls==0.5.2 python-dotenv youtube_dl youtube_search_python @@ -12,3 +12,4 @@ youtube_search search_engine_parser ffmpeg Pillow +ujson