├── etc
├── __init__.py
├── font.otf
├── tg_vc_bot.jpg
└── tg_vc_bot.jpg.png
├── SophiaMusic
├── helpers
│ ├── __init__.py
│ ├── errors.py
│ ├── channelmusic.py
│ ├── filters.py
│ ├── admins.py
│ ├── gets.py
│ └── decorators.py
├── modules
│ ├── __init__.py
│ ├── gcast.py
│ ├── ytsearch.py
│ ├── inline.py
│ ├── start.py
│ ├── pmpermit.py
│ ├── msg.py
│ ├── userbotjoin.py
│ ├── channeladmins.py
│ ├── admins.py
│ ├── song.py
│ ├── channelplay.py
│ └── play.py
├── services
│ ├── __init__.py
│ ├── downloaders
│ │ ├── __init__.py
│ │ └── youtube.py
│ ├── queues
│ │ ├── __init__.py
│ │ └── queues.py
│ ├── callsmusic
│ │ ├── __init__.py
│ │ └── callsmusic.py
│ └── converter
│ │ ├── __init__.py
│ │ └── converter.py
├── function
│ ├── __init__.py
│ └── admins.py
├── __main__.py
└── config.py
├── .dockerignore
├── heroku.yml
├── .gitignore
├── requirements.txt
├── Dockerfile
├── example.env
├── str.py
├── app.json
├── README.md
└── LICENSE
/etc/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/SophiaMusic/services/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/etc/font.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dihanofficial/SophiaMusic/HEAD/etc/font.otf
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | LICENSE
2 | README.md
3 | .env
4 | .gitignore
5 | .git/
6 | __pycache__/
7 |
--------------------------------------------------------------------------------
/etc/tg_vc_bot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dihanofficial/SophiaMusic/HEAD/etc/tg_vc_bot.jpg
--------------------------------------------------------------------------------
/etc/tg_vc_bot.jpg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dihanofficial/SophiaMusic/HEAD/etc/tg_vc_bot.jpg.png
--------------------------------------------------------------------------------
/heroku.yml:
--------------------------------------------------------------------------------
1 | build:
2 | docker:
3 | worker: Dockerfile
4 | run:
5 | worker: python3 -m SophiaMusic
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.session
2 | *.session-journal
3 | .env
4 | __pycache__
5 | .idea
6 | .vscode
7 | raw_files
8 | downloads
9 |
--------------------------------------------------------------------------------
/SophiaMusic/services/downloaders/__init__.py:
--------------------------------------------------------------------------------
1 | from SophiaMusic.services.downloaders import youtube
2 |
3 | __all__ = ["youtube"]
4 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/errors.py:
--------------------------------------------------------------------------------
1 | class DurationLimitError(Exception):
2 | pass
3 |
4 |
5 | class FFmpegReturnCodeError(Exception):
6 | pass
7 |
--------------------------------------------------------------------------------
/SophiaMusic/services/queues/__init__.py:
--------------------------------------------------------------------------------
1 | from SophiaMusic.services.queues.queues import clear, get, is_empty, put, task_done
2 |
3 | __all__ = ["clear", "get", "is_empty", "put", "task_done"]
4 |
--------------------------------------------------------------------------------
/SophiaMusic/services/callsmusic/__init__.py:
--------------------------------------------------------------------------------
1 | from SophiaMusic.services.queues import queues
2 | from SophiaMusic.services.callsmusic.callsmusic import pytgcalls, run
3 |
4 | __all__ = ["queues", "pytgcalls", "run"]
5 |
--------------------------------------------------------------------------------
/SophiaMusic/function/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from SophiaMusic.function.admins import admins
3 | from SophiaMusic.function.admins import get
4 | from SophiaMusic.function.admins import set
5 |
6 | __all__ = ["set", "get", "admins"]
7 |
--------------------------------------------------------------------------------
/SophiaMusic/services/converter/__init__.py:
--------------------------------------------------------------------------------
1 | from os import listdir, mkdir
2 |
3 | if "raw_files" not in listdir():
4 | mkdir("raw_files")
5 |
6 | from SophiaMusic.services.converter.converter import convert
7 |
8 | __all__ = ["convert"]
9 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/channelmusic.py:
--------------------------------------------------------------------------------
1 | from pyrogram.types import Chat
2 |
3 |
4 | def get_chat_id(chat: Chat):
5 | if chat.title.startswith("Channel Music: ") and chat.title[16:].isnumeric():
6 | return int(chat.title[15:])
7 | return chat.id
8 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pyrogram
2 | tgcrypto
3 | py-tgcalls==0.5.5
4 | python-dotenv
5 | youtube_dl
6 | youtube_search_python
7 | requests
8 | aiohttp
9 | aiofiles
10 | asyncio
11 | youtube_search
12 | search_engine_parser
13 | ffmpeg
14 | Pillow
15 | python-arq
16 | future
17 | psutil
18 | wget
19 |
--------------------------------------------------------------------------------
/SophiaMusic/function/admins.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List
2 | from SophiaMusic.config import admins
3 |
4 | admins: Dict[int, List[int]] = {}
5 |
6 |
7 | def set(chat_id: int, admins_: List[int]):
8 | admins[chat_id] = admins_
9 |
10 |
11 | def get(chat_id: int) -> List[int]:
12 | if chat_id in admins:
13 | return admins[chat_id]
14 | return []
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9.6-slim-buster
2 | RUN apt update && apt upgrade -y
3 | RUN apt install git curl python3-pip ffmpeg -y
4 | RUN pip3 install -U pip
5 | RUN curl -sL https://deb.nodesource.com/setup_16.x | bash -
6 | RUN apt-get install -y nodejs
7 | RUN npm i -g npm
8 | RUN mkdir /app/
9 | COPY . /app/
10 | WORKDIR /app/
11 | RUN pip3 install -U -r requirements.txt
12 | CMD python3 -m SophiaMusic
13 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | SESSION_NAME=session # If you don't deploy with docker, keep it as is and if you do so it should be a session string generated by "python str.py"
2 | BOT_TOKEN=123456:abcdefghijklmnopqrstuv
3 | BOT_NAME=HELLBOT
4 | API_ID=123456
5 | API_HASH=abcdefghijklmnopqrstuv
6 | SUDO_USERS=1111 2222 # List of user IDs separated by space
7 | DURATION_LIMIT=10 # in minutes (default: 7)
8 | ARQ_API_KEY=
9 | UPDATES_CHANNEL=
10 | BG_IMAGE=
11 |
12 |
--------------------------------------------------------------------------------
/str.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | from pyrogram import Client
4 |
5 | print("Enter your app information from my.telegram.org/apps below.")
6 |
7 |
8 | async def main():
9 | async with Client(
10 | ":memory:", api_id=int(input("API ID:")), api_hash=input("API HASH:")
11 | ) as app:
12 | print(await app.export_session_string())
13 |
14 |
15 | if __name__ == "__main__":
16 | loop = asyncio.get_event_loop()
17 | loop.run_until_complete(main())
18 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/filters.py:
--------------------------------------------------------------------------------
1 | from typing import List, Union
2 |
3 | from pyrogram import filters
4 |
5 | from SophiaMusic.config import COMMAND_PREFIXES
6 |
7 | other_filters = filters.group & ~filters.edited & ~filters.via_bot & ~filters.forwarded
8 | other_filters2 = (
9 | filters.private & ~filters.edited & ~filters.via_bot & ~filters.forwarded
10 | )
11 |
12 |
13 | def command(commands: Union[str, List[str]]):
14 | return filters.command(commands, COMMAND_PREFIXES)
15 |
16 |
--------------------------------------------------------------------------------
/SophiaMusic/services/callsmusic/callsmusic.py:
--------------------------------------------------------------------------------
1 |
2 | from pyrogram import Client
3 | from pytgcalls import PyTgCalls
4 |
5 | from SophiaMusic.config import API_HASH, API_ID, SESSION_NAME
6 | from SophiaMusic.services.queues import queues
7 |
8 | client = Client(SESSION_NAME, API_ID, API_HASH)
9 | pytgcalls = PyTgCalls(client)
10 |
11 |
12 | @pytgcalls.on_stream_end()
13 | def on_stream_end(chat_id: int) -> None:
14 | queues.task_done(chat_id)
15 |
16 | if queues.is_empty(chat_id):
17 | pytgcalls.leave_group_call(chat_id)
18 | else:
19 | pytgcalls.change_stream(chat_id, queues.get(chat_id)["file"])
20 |
21 |
22 | run = pytgcalls.run
23 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/admins.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from pyrogram.types import Chat
4 |
5 | from SophiaMusic.function.admins import get as gett
6 | from SophiaMusic.function.admins import set
7 |
8 |
9 | async def get_administrators(chat: Chat) -> List[int]:
10 | get = gett(chat.id)
11 |
12 | if get:
13 | return get
14 | else:
15 | administrators = await chat.get_members(filter="administrators")
16 | to_set = []
17 |
18 | for administrator in administrators:
19 | if administrator.can_manage_voice_chats:
20 | to_set.append(administrator.user.id)
21 |
22 | set(chat.id, to_set)
23 | return await get_administrators(chat)
24 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/gets.py:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | """"" Sophia Music v6 """""
7 |
8 |
9 |
10 |
11 | from typing import Union
12 |
13 | from pyrogram.types import Audio, Message, Voice
14 |
15 |
16 | def get_url(message_1: Message) -> Union[str, None]:
17 | messages = [message_1]
18 |
19 | if message_1.reply_to_message:
20 | messages.append(message_1.reply_to_message)
21 |
22 | text = ""
23 | offset = None
24 | length = None
25 |
26 | for message in messages:
27 | if offset:
28 | break
29 |
30 | if message.entities:
31 | for entity in message.entities:
32 | if entity.type == "url":
33 | text = message.text or message.caption
34 | offset, length = entity.offset, entity.length
35 | break
36 |
37 | if offset in (None,):
38 | return None
39 |
40 | return text[offset : offset + length]
41 |
42 |
43 | def get_file_name(audio: Union[Audio, Voice]):
44 | return f'{audio.file_unique_id}.{audio.file_name.split(".")[-1] if not isinstance(audio, Voice) else "ogg"}'
45 |
--------------------------------------------------------------------------------
/SophiaMusic/services/downloaders/youtube.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | from os import path
4 |
5 | from youtube_dl import YoutubeDL
6 |
7 | from SophiaMusic.config import DURATION_LIMIT
8 | from SophiaMusic.helpers.errors import DurationLimitError
9 |
10 | ydl_opts = {
11 | "format": "bestaudio/best",
12 | "verbose": True,
13 | "geo-bypass": True,
14 | "nocheckcertificate": True,
15 | "outtmpl": "downloads/%(id)s.%(ext)s",
16 | }
17 |
18 | ydl = YoutubeDL(ydl_opts)
19 |
20 |
21 | def download(url: str) -> str:
22 | info = ydl.extract_info(url, False)
23 | duration = round(info["duration"] / 60)
24 |
25 | if duration > DURATION_LIMIT:
26 | raise DurationLimitError(
27 | f"🛑 Videos longer than {DURATION_LIMIT} minute(s) aren't allowed, the provided video is {duration} minute(s)"
28 | )
29 | try:
30 | ydl.download([url])
31 | except:
32 | raise DurationLimitError(
33 | f"🛑 Videos longer than {DURATION_LIMIT} minute(s) aren't allowed, the provided video is {duration} minute(s)"
34 | )
35 | return path.join("downloads", f"{info['id']}.{info['ext']}")
36 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/gcast.py:
--------------------------------------------------------------------------------
1 | from pyrogram import Client, filters
2 | from pyrogram.errors import UserAlreadyParticipant
3 | import asyncio
4 | from SophiaMusic.config import SUDO_USERS
5 |
6 | @Client.on_message(filters.command(["gcast"]))
7 | async def bye(client, message):
8 | sent=0
9 | failed=0
10 | if message.from_user.id in SUDO_USERS:
11 | lol = await message.reply("Starting Gcast")
12 | if not message.reply_to_message:
13 | await lol.edit("Reply to any text message to gcast sir")
14 | return
15 | msg = message.reply_to_message.text
16 | async for dialog in client.iter_dialogs():
17 | try:
18 | await client.send_message(dialog.chat.id, msg)
19 | sent = sent+1
20 | await lol.edit(f"Gcasting.. Sent: {sent} chats. Failed: {failed} chats.")
21 | except:
22 | failed=failed+1
23 | await lol.edit(f"Gcasting.. Sent: {sent} chats. Failed: {failed} chats.")
24 | await asyncio.sleep(3)
25 | await message.reply_text(f"Gcasted message to {sent} chats. Failed {failed} chats.")
26 |
--------------------------------------------------------------------------------
/SophiaMusic/services/queues/queues.py:
--------------------------------------------------------------------------------
1 | from asyncio import Queue
2 | from asyncio import QueueEmpty as Empty
3 | from typing import Dict
4 |
5 |
6 | queues: Dict[int, Queue] = {}
7 |
8 |
9 | async def put(chat_id: int, **kwargs) -> int:
10 | if chat_id not in queues:
11 | queues[chat_id] = Queue()
12 | await queues[chat_id].put({**kwargs})
13 | return queues[chat_id].qsize()
14 |
15 |
16 | def get(chat_id: int) -> Dict[str, str]:
17 | if chat_id in queues:
18 | try:
19 | return queues[chat_id].get_nowait()
20 | except Empty:
21 | return None
22 |
23 |
24 | def is_empty(chat_id: int) -> bool:
25 | if chat_id in queues:
26 | return queues[chat_id].empty()
27 | return True
28 |
29 |
30 | def task_done(chat_id: int):
31 | if chat_id in queues:
32 | try:
33 | queues[chat_id].task_done()
34 | except ValueError:
35 | pass
36 |
37 |
38 | def clear(chat_id: int):
39 | if chat_id in queues:
40 | if queues[chat_id].empty():
41 | raise Empty
42 | else:
43 | queues[chat_id].queue = []
44 | raise Empty
45 |
--------------------------------------------------------------------------------
/SophiaMusic/services/converter/converter.py:
--------------------------------------------------------------------------------
1 |
2 | import asyncio
3 | from os import path
4 |
5 | from SophiaMusic.helpers.errors import FFmpegReturnCodeError
6 |
7 |
8 | async def convert(file_path: str) -> str:
9 | out = path.basename(file_path)
10 | out = out.split(".")
11 | out[-1] = "raw"
12 | out = ".".join(out)
13 | out = path.basename(out)
14 | out = path.join("raw_files", out)
15 |
16 | if path.isfile(out):
17 | return out
18 | try:
19 | proc = await asyncio.create_subprocess_shell(
20 | cmd=(
21 | "ffmpeg "
22 | "-y -i "
23 | f"{file_path} "
24 | "-f s16le "
25 | "-ac 1 "
26 | "-ar 48000 "
27 | "-acodec pcm_s16le "
28 | f"{out}"
29 | ),
30 | stdin=asyncio.subprocess.PIPE,
31 | )
32 |
33 | await proc.communicate()
34 |
35 | if proc.returncode != 0:
36 | raise FFmpegReturnCodeError("FFmpeg did not return 0")
37 |
38 | return out
39 | except:
40 | raise FFmpegReturnCodeError("FFmpeg did not return 0")
41 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/ytsearch.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from pyrogram import Client as app
4 | from pyrogram.types import Message
5 | from youtube_search import YoutubeSearch
6 |
7 | logging.basicConfig(
8 | level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
9 | )
10 | logger = logging.getLogger(__name__)
11 |
12 | import pyrogram
13 |
14 | logging.getLogger("pyrogram").setLevel(logging.WARNING)
15 |
16 |
17 | @app.on_message(pyrogram.filters.command(["search"]))
18 | async def ytsearch(_, message: Message):
19 | try:
20 | if len(message.command) < 2:
21 | await message.reply_text("/search needs an argument!")
22 | return
23 | query = message.text.split(None, 1)[1]
24 | m = await message.reply_text("Searching....")
25 | results = YoutubeSearch(query, max_results=4).to_dict()
26 | i = 0
27 | text = ""
28 | while i < 4:
29 | text += f"Title - {results[i]['title']}\n"
30 | text += f"Duration - {results[i]['duration']}\n"
31 | text += f"Views - {results[i]['views']}\n"
32 | text += f"Channel - {results[i]['channel']}\n"
33 | text += f"https://youtube.com{results[i]['url_suffix']}\n\n"
34 | i += 1
35 | await m.edit(text, disable_web_page_preview=True)
36 | except Exception as e:
37 | await message.reply_text(str(e))
38 |
--------------------------------------------------------------------------------
/SophiaMusic/__main__.py:
--------------------------------------------------------------------------------
1 | # SophiaMusic (Telegram bot project)
2 | # Copyright (C) 2021 Dihan Official
3 |
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as
6 | # published by the Free Software Foundation, either version 3 of the
7 | # License, or (at your option) any later version.
8 |
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 |
18 | import requests
19 | from pyrogram import Client as Bot
20 |
21 | from SophiaMusic.config import API_HASH
22 | from SophiaMusic.config import API_ID
23 | from SophiaMusic.config import BG_IMAGE
24 | from SophiaMusic.config import BOT_TOKEN
25 | from SophiaMusic.services.callsmusic import run
26 |
27 | response = requests.get(BG_IMAGE)
28 | file = open("./etc/foreground.png", "wb")
29 | file.write(response.content)
30 | file.close()
31 |
32 | bot = Bot(
33 | ":memory:",
34 | API_ID,
35 | API_HASH,
36 | bot_token=BOT_TOKEN,
37 | plugins=dict(root="SophiaMusic.modules"),
38 | )
39 |
40 | bot.start()
41 | run()
42 |
--------------------------------------------------------------------------------
/SophiaMusic/config.py:
--------------------------------------------------------------------------------
1 | import os
2 | from os import getenv
3 |
4 | from dotenv import load_dotenv
5 |
6 | if os.path.exists("local.env"):
7 | load_dotenv("local.env")
8 |
9 | que = {}
10 | SESSION_NAME = getenv("SESSION_NAME", "session")
11 | BOT_TOKEN = getenv("BOT_TOKEN")
12 | BOT_NAME = getenv("BOT_NAME")
13 | UPDATES_CHANNEL = getenv("UPDATES_CHANNEL", "DihanOfficial")
14 | BG_IMAGE = getenv("BG_IMAGE", "https://telegra.ph/file/3654bea3e5284b442f932.png")
15 | admins = {}
16 | API_ID = int(getenv("API_ID"))
17 | API_HASH = getenv("API_HASH")
18 | BOT_USERNAME = getenv("BOT_USERNAME")
19 | ASSISTANT_NAME = getenv("ASSISTANT_NAME", "NA")
20 | SUPPORT_GROUP = getenv("SUPPORT_GROUP", "dihan_official")
21 | PROJECT_NAME = getenv("PROJECT_NAME", "Sophia Music v6")
22 | SOURCE_CODE = getenv("SOURCE_CODE", "github.com/dihanofficial/SophiaMusic")
23 | DURATION_LIMIT = int(getenv("DURATION_LIMIT", "7"))
24 | ARQ_API_KEY = getenv("ARQ_API_KEY", None)
25 | PMPERMIT = getenv("PMPERMIT", None)
26 | PMMSG = getenv("PMMSG", f"Hi there, This is a music assistant service of @{BOT_USERNAME}.\n\n ❗️ Rules:\n - No chatting allowed\n - No spam allowed \n\n 👉 **SEND GROUP INVITE LINK OR USERNAME IF USERBOT CAN T JOIN YOUR GROUP.**\n\n ⚠️ Disclamer: If you are sending a message here it means admin will see your message and join chat\n - Don t add this user to secret groups.\n - Don t Share private info here\n\n")
27 | LOG_GRP = getenv("LOG_GRP", None)
28 | COMMAND_PREFIXES = list(getenv("COMMAND_PREFIXES", "/ !").split())
29 |
30 | SUDO_USERS = list(map(int, getenv("SUDO_USERS").split()))
31 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/inline.py:
--------------------------------------------------------------------------------
1 | from pyrogram import Client, errors
2 | from pyrogram.types import (
3 | InlineQuery,
4 | InlineQueryResultArticle,
5 | InputTextMessageContent,
6 | )
7 | from youtubesearchpython import VideosSearch
8 |
9 |
10 | @Client.on_inline_query()
11 | async def inline(client: Client, query: InlineQuery):
12 | answers = []
13 | search_query = query.query.lower().strip().rstrip()
14 |
15 | if search_query == "":
16 | await client.answer_inline_query(
17 | query.id,
18 | results=answers,
19 | switch_pm_text="Type a YouTube video name...",
20 | switch_pm_parameter="help",
21 | cache_time=0,
22 | )
23 | else:
24 | search = VideosSearch(search_query, limit=50)
25 |
26 | for result in search.result()["result"]:
27 | answers.append(
28 | InlineQueryResultArticle(
29 | title=result["title"],
30 | description="{}, {} views.".format(
31 | result["duration"], result["viewCount"]["short"]
32 | ),
33 | input_message_content=InputTextMessageContent(
34 | "https://www.youtube.com/watch?v={}".format(result["id"])
35 | ),
36 | thumb_url=result["thumbnails"][0]["url"],
37 | )
38 | )
39 |
40 | try:
41 | await query.answer(results=answers, cache_time=0)
42 | except errors.QueryIdInvalid:
43 | await query.answer(
44 | results=answers,
45 | cache_time=0,
46 | switch_pm_text="Error: Search timed out",
47 | switch_pm_parameter="",
48 | )
49 |
--------------------------------------------------------------------------------
/SophiaMusic/helpers/decorators.py:
--------------------------------------------------------------------------------
1 |
2 | # Copyright (C) 2021 Dihan Official
3 |
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as
6 | # published by the Free Software Foundation, either version 3 of the
7 | # License, or (at your option) any later version.
8 |
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 |
18 | from typing import Callable
19 |
20 | from pyrogram import Client
21 | from pyrogram.types import Message
22 |
23 | from SophiaMusic.config import SUDO_USERS
24 | from SophiaMusic.helpers.admins import get_administrators
25 |
26 |
27 | def errors(func: Callable) -> Callable:
28 | async def decorator(client: Client, message: Message):
29 | try:
30 | return await func(client, message)
31 | except Exception as e:
32 | await message.reply(f"{type(e).__name__}: {e}")
33 |
34 | return decorator
35 |
36 |
37 | def authorized_users_only(func: Callable) -> Callable:
38 | async def decorator(client: Client, message: Message):
39 | if message.from_user.id in SUDO_USERS:
40 | return await func(client, message)
41 |
42 | administrators = await get_administrators(message.chat)
43 |
44 | for administrator in administrators:
45 | if administrator == message.from_user.id:
46 | return await func(client, message)
47 |
48 | return decorator
49 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/start.py:
--------------------------------------------------------------------------------
1 | from pyrogram import Client, filters
2 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
3 |
4 |
5 |
6 | @Client.on_message(
7 | filters.command("start")
8 | & filters.private
9 | & ~ filters.edited
10 | )
11 | async def start_(client: Client, message: Message):
12 | await message.reply_text(
13 | f"""Hi there,👋 {message.from_user.first_name}!
14 | \nThis is Sophia Music Bot.
15 | I play music on Telegram's Voice Chats.
16 | \nFo More Help Use Buttons Below:
17 | """,
18 |
19 |
20 | reply_markup=InlineKeyboardMarkup(
21 | [
22 | [
23 | InlineKeyboardButton(
24 | "🛠 Source Code 🛠", url="https://github.com/dihanofficial/sophiamusic")
25 | ],[
26 | InlineKeyboardButton(
27 | "💬 Updates Channel", url="https://t.me/SophiaUpdates"
28 | ),
29 | InlineKeyboardButton(
30 | "💻 Support Group", url="https://t.me/SophiaSupport_Official"
31 | )
32 | ],[
33 | InlineKeyboardButton(
34 | "➕ Add Me To Your Group ➕", url="https://t.me/SophiaSLBot?startgroup=true"
35 | )]
36 | ]
37 | ),
38 | disable_web_page_preview=True
39 | )
40 |
41 | @Client.on_message(filters.command("start") & ~filters.private & ~filters.channel)
42 | async def gstart(_, message: Message):
43 | await message.reply_text("""*Sophia Music Bot is alive.*""",
44 | reply_markup=InlineKeyboardMarkup(
45 | [
46 | [
47 | InlineKeyboardButton(
48 | "💬 Updates Channel", url="https://t.me/SophiaUpdates")
49 | ]
50 | ]
51 | )
52 | )
53 |
54 |
55 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/pmpermit.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | """ Sophia Music v6 """
4 |
5 |
6 |
7 | from pyrogram import Client
8 | import asyncio
9 | from SophiaMusic.config import SUDO_USERS, PMPERMIT, PMMSG
10 | from pyrogram import filters
11 | from pyrogram.types import Message
12 | from SophiaMusic.services.callsmusic.callsmusic import client as USER
13 |
14 | PMSET =True
15 | pchats = []
16 |
17 | @USER.on_message(filters.text & filters.private & ~filters.me & ~filters.bot)
18 | async def pmPermit(client: USER, message: Message):
19 | if PMPERMIT == "ENABLE":
20 | if PMSET:
21 | chat_id = message.chat.id
22 | if chat_id in pchats:
23 | return
24 | try:
25 | await USER.send_message(
26 | message.chat.id,
27 | PMMSG
28 | )
29 | return
30 | except:
31 | return
32 |
33 |
34 | @Client.on_message(filters.command(["/pmpermit"]))
35 | async def bye(client: Client, message: Message):
36 | if message.from_user.id in SUDO_USERS:
37 | global PMSET
38 | text = message.text.split(" ", 1)
39 | queryy = text[1]
40 | if queryy == "on":
41 | PMSET = True
42 | await message.reply_text("Pmpermit turned on")
43 | return
44 | if queryy == "off":
45 | PMSET = None
46 | await message.reply_text("Pmpermit turned off")
47 | return
48 |
49 | @USER.on_message(filters.text & filters.private & filters.me)
50 | async def autopmPermiat(client: USER, message: Message):
51 | chat_id = message.chat.id
52 | if not chat_id in pchats:
53 | pchats.append(chat_id)
54 | await message.reply_text("Approoved to PM due to outgoing messages")
55 | return
56 | message.continue_propagation()
57 |
58 | @USER.on_message(filters.command("a", [".", ""]) & filters.me & filters.private)
59 | async def pmPermiat(client: USER, message: Message):
60 | chat_id = message.chat.id
61 | if not chat_id in pchats:
62 | pchats.append(chat_id)
63 | await message.reply_text("Approoved to PM")
64 | return
65 | message.continue_propagation()
66 |
67 |
68 | @USER.on_message(filters.command("da", [".", ""]) & filters.me & filters.private)
69 | async def rmpmPermiat(client: USER, message: Message):
70 | chat_id = message.chat.id
71 | if chat_id in pchats:
72 | pchats.remove(chat_id)
73 | await message.reply_text("Dispprooved to PM")
74 | return
75 | message.continue_propagation()
76 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SophiaMusic",
3 | "description": "Open-Source bot to play songs in your Telegram's Group Voice Chat. Powered by PyTgCalls.",
4 | "keywords": ["music", "voicechat", "telegram"],
5 | "repository": "https://github.com/dihanofficial/sophiamusic-v6",
6 | "stack": "container",
7 | "buildpacks": [
8 | {
9 | "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest"
10 | }
11 | ],
12 | "env": {
13 | "SESSION_NAME": {
14 | "description": "Pyrogram session string",
15 | "required": true
16 | },
17 | "BOT_TOKEN": {
18 | "description": "A bot token from @BotFather",
19 | "required": true
20 | },
21 | "BOT_USERNAME": {
22 | "description": "Your bot's username without @",
23 | "required": true
24 | },
25 | "BOT_NAME": {
26 | "description": "Your MusicPlayer Bot Name.",
27 | "required": false,
28 | "value": ""
29 | },
30 | "SUPPORT_GROUP": {
31 | "description": "Your MusicPlayer support group without @ [Leave this if you don't have one]",
32 | "required": false,
33 | "value": "dihan_official"
34 | },
35 | "PROJECT_NAME": {
36 | "description": "Your MusicPlayer project Name.",
37 | "required": false,
38 | "value": "SophiaMusic v6"
39 | },
40 | "ASSISTANT_NAME": {
41 | "description": "Your MusicPlayer's assistant Username without @.",
42 | "required": true
43 | },
44 | "ARQ_API_KEY": {
45 | "description": "Get from @ARQRobot.",
46 | "required": false,
47 | "value": "VKSSQH-SRNJYZ-MSOJAI-QALESR-ARQ"
48 | },
49 | "BG_IMAGE": {
50 | "description": "BG image of bot (Use transparent one) [IGNORE].",
51 | "required": false,
52 | "value": "https://telegra.ph/file/3654bea3e5284b442f932.png"
53 | },
54 | "UPDATES_CHANNEL": {
55 | "description": "Updates Channel without @ [Leave this if you don't have one].",
56 | "required": false,
57 | "value": "dihanofficial"
58 | },
59 | "API_ID": {
60 | "description": "App ID from my.telegram.org/apps",
61 | "required": true
62 | },
63 | "PMPERMIT": {
64 | "description": "Change to turn off",
65 | "required": false,
66 | "value": "ENABLE"
67 | },
68 | "API_HASH": {
69 | "description": "App hash from my.telegram.org/apps",
70 | "required": true
71 | },
72 | "SUDO_USERS": {
73 | "description": "List of user IDs counted as admin everywhere (separated by space).",
74 | "required": true
75 | },
76 | "DURATION_LIMIT": {
77 | "description": "Max audio duration limit for downloads (minutes).",
78 | "required": true,
79 | "value": "10"
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Sophia Music v6 🎵
2 |
3 |
4 |
5 |
6 |
7 | Requirements 📝
8 |
9 | - FFmpeg
10 | - NodeJS [nodesource.com](https://nodesource.com/)
11 | - Python 3.7+
12 | - [PyTgCalls](https://github.com/pytgcalls/pytgcalls)
13 |
14 | ### Deploy To Heroku
15 |
16 | [](https://heroku.com/deploy?template=https://github.com/dihanofficial/SophiaMusic-v6)
17 |
18 |
19 |
20 | ### Commands 🛠
21 | #### For all in group
22 | - `/play` - reply to youtube url or song file to play song
23 | - `/ytp ` - play song without youtube url or song file (best method)
24 | - `/song ` - download songs you want quickly
25 | - `/search ` - search videos on youtube with details
26 |
27 | #### Admins only
28 | - `/pause` - pause song play
29 | - `/resume` - resume song play
30 | - `/skip` - play next song
31 | - `/end` - stop music play
32 |
33 |
34 | ### Commands for Sudo Users ⚔️
35 | - `/userbotleaveall` - remove assistant from all chats
36 | - `/gcast ` - globally brodcast replied message to all chats
37 | - `/pmpermit [on/off]` - enable/disable pmpermit message
38 |
39 | #### Pmpermit
40 | - `.a` - approove someone to pm you
41 | - `.da` - disapproove someone to pm you
42 | - You can add a custom pmpermit message by adding var `PMMSG` and adding your message through env vars (for heroku, Settings/Edit vars)
43 |
44 | + Sudo Users can execute any command in any groups
45 |
46 |
47 | ## Credits
48 | - DaisyXMusic
49 | - Callmusic
50 |
51 | #### Contributors
52 | - [Dihan Randila](https://github.com/dihanofficial): Dev / Owner
53 | - [InukaAsith](https://github.com/InukaAsith): Dev
54 |
55 | #### Special Credits
56 | - [Roj Serbest](http://github.com/rojserbest): Callsmusic Developer
57 |
58 | This bot is based on the original work done by [Rojserbest](http://github.com/rojserbest). Without his hardwork SophiaMusic won t exist.
59 | SophiaXMusic is a modified version of [Callsmusic](https://github.com/callsmusic/callsmusic) for fit the needs of some users
60 |
61 | - [StarkGang](https://github.com/StarkGang/)
62 | - [SpEcHiDe](https://github.com/SpEcHiDe/)
63 | - [The Hamker Cat](https://github.com/thehamkercat)
64 | - [Laky(for PyTgCalls)](https://github.com/Laky-64)
65 | - [Dan (for pyrogram)](https://github.com/delivrance)
66 |
67 |
68 | #### Open Source codes used in this project
69 | - https://github.com/callsmusic/callsmusic : Source code used here as base
70 | - https://github.com/DevsExpo/FridayUserbot/blob/master/main_startup/helper_func/basic_helpers.py : Functioms from line 275 to 351
71 | - https://github.com/TheHamkerCat/WilliamButcherBot/blob/dev/wbb/modules/music.py : From lines 170 to 178
72 |
73 |
74 | > This project exists thanks to these awesome developers and their codes and contributions.
75 | > And credits goes to all who supported, all who helped and API & environmental requirement package devs and all projects helped in making this project.
76 | > Special thanks to you for using bot
77 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/msg.py:
--------------------------------------------------------------------------------
1 | import os
2 | from SophiaMusic.config import SOURCE_CODE,ASSISTANT_NAME,PROJECT_NAME,SUPPORT_GROUP,UPDATES_CHANNEL
3 | class Messages():
4 | START_MSG = "**Hello 👋 [{}](tg://user?id={})!**\n\n🤖 I am an advanced bot created for playing music in the voice chats of Telegram Groups & Channels.\n\n✅ Send me /help for more info."
5 | HELP_MSG = [
6 | ".",
7 | f"""
8 | **Hey 👋 Welcome back to {PROJECT_NAME}
9 | ⚪️ {PROJECT_NAME} can play music in your group's voice chat as well as channel voice chats
10 | ⚪️ Assistant name >> @{ASSISTANT_NAME}\n\nClick next for instructions**
11 | """,
12 |
13 | f"""
14 | **Setting up**
15 | 1) Make bot admin (Group and in channel if use cplay)
16 | 2) Start a voice chat
17 | 3) Try /play [song name] for the first time by an admin
18 | *) If userbot joined enjoy music, If not add @{ASSISTANT_NAME} to your group and retry
19 | **For Channel Music Play**
20 | 1) Make me admin of your channel
21 | 2) Send /userbotjoinchannel in linked group
22 | 3) Now send commands in linked group
23 | **Commands**
24 | **=>> Song Playing 🎧**
25 | - /play: Play the requestd song
26 | - /play [yt url] : Play the given yt url
27 | - /play [reply yo audio]: Play replied audio
28 | - /splay: Play song via jio saavn
29 | - /ytplay: Directly play song via Youtube Music
30 | **=>> Playback ⏯**
31 | - /player: Open Settings menu of player
32 | - /skip: Skips the current track
33 | - /pause: Pause track
34 | - /resume: Resumes the paused track
35 | - /end: Stops media playback
36 | - /current: Shows the current Playing track
37 | - /playlist: Shows playlist
38 | *Player cmd and all other cmds except /play, /current and /playlist are only for admins of the group.
39 | """,
40 |
41 | f"""
42 | **=>> Channel Music Play 🛠**
43 | ⚪️ For linked group admins only:
44 | - /cplay [song name] - play song you requested
45 | - /csplay [song name] - play song you requested via jio saavn
46 | - /cplaylist - Show now playing list
47 | - /cccurrent - Show now playing
48 | - /cplayer - open music player settings panel
49 | - /cpause - pause song play
50 | - /cresume - resume song play
51 | - /cskip - play next song
52 | - /cend - stop music play
53 | - /userbotjoinchannel - invite assistant to your chat
54 | channel is also can be used instead of c ( /cplay = /channelplay )
55 | ⚪️ If you donlt like to play in linked group:
56 | 1) Get your channel ID.
57 | 2) Create a group with tittle: Channel Music: your_channel_id
58 | 3) Add bot as Channel admin with full perms
59 | 4) Add @{ASSISTANT_NAME} to the channel as an admin.
60 | 5) Simply send commands in your group.
61 | """,
62 |
63 | f"""
64 | **=>> More tools 🧑🔧**
65 | - /musicplayer [on/off]: Enable/Disable Music player
66 | - /admincache: Updates admin info of your group. Try if bot isn't recognize admin
67 | - /userbotjoin: Invite @{ASSISTANT_NAME} Userbot to your chat
68 | - /auth [reply to user] - Authorize User
69 | - /deauth [reply to user] - DeAuthorize user
70 | Authorized users can execute admin commands in authorized group
71 | **=>> Commands for Sudo Users ⚔️**
72 | - /userbotleaveall - remove assistant from all chats
73 | - /gcast - globally brodcast replied message to all chats
74 | - /pmpermit [on/off] - enable/disable pmpermit message
75 | *Sudo Users can execute any command in any groups
76 | """
77 | ]
78 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/userbotjoin.py:
--------------------------------------------------------------------------------
1 | from pyrogram import Client, filters
2 | from pyrogram.errors import UserAlreadyParticipant
3 | import asyncio
4 | from SophiaMusic.helpers.decorators import authorized_users_only, errors
5 | from SophiaMusic.services.callsmusic.callsmusic import client as USER
6 | from SophiaMusic.config import SUDO_USERS
7 |
8 | @Client.on_message(filters.command(["userbotjoin"]) & ~filters.private & ~filters.bot)
9 | @authorized_users_only
10 | @errors
11 | async def addchannel(client, message):
12 | chid = message.chat.id
13 | try:
14 | invitelink = await client.export_chat_invite_link(chid)
15 | except:
16 | await message.reply_text(
17 | "Add me as admin of yor group first",
18 | )
19 | return
20 |
21 | try:
22 | user = await USER.get_me()
23 | except:
24 | user.first_name = "DaisyMusic"
25 |
26 | try:
27 | await USER.join_chat(invitelink)
28 | await USER.send_message(message.chat.id, "I joined here as you requested")
29 | except UserAlreadyParticipant:
30 | await message.reply_text(
31 | "helper already in your chat",
32 | )
33 | except Exception as e:
34 | print(e)
35 | await message.reply_text(
36 | f"🛑 Flood Wait Error 🛑 \n User {user.first_name} couldn't join your group due to heavy join requests for userbot! Make sure user is not banned in group."
37 | "\n\nOr manually add @dihanofficialassistant to your Group and try again",
38 | )
39 | return
40 | await message.reply_text(
41 | "helper userbot joined your chat",
42 | )
43 |
44 |
45 | @USER.on_message(filters.group & filters.command(["userbotleave"]))
46 | @authorized_users_only
47 | async def rem(USER, message):
48 | try:
49 | await USER.leave_chat(message.chat.id)
50 | except:
51 | await message.reply_text(
52 | f"User couldn't leave your group! May be floodwaits."
53 | "\n\nOr manually kick me from to your Group",
54 | )
55 | return
56 |
57 | @Client.on_message(filters.command(["userbotleaveall"]))
58 | async def bye(client, message):
59 | if message.from_user.id in SUDO_USERS:
60 | left=0
61 | failed=0
62 | lol = await message.reply("Assistant Leaving all chats")
63 | async for dialog in USER.iter_dialogs():
64 | try:
65 | await USER.leave_chat(dialog.chat.id)
66 | left = left+1
67 | await lol.edit(f"Assistant leaving... Left: {left} chats. Failed: {failed} chats.")
68 | except:
69 | failed=failed+1
70 | await lol.edit(f"Assistant leaving... Left: {left} chats. Failed: {failed} chats.")
71 | await asyncio.sleep(0.7)
72 | await client.send_message(message.chat.id, f"Left {left} chats. Failed {failed} chats.")
73 |
74 |
75 | @Client.on_message(filters.command(["userbotjoinchannel","ubjoinc"]) & ~filters.private & ~filters.bot)
76 | @authorized_users_only
77 | @errors
78 | async def addcchannel(client, message):
79 | try:
80 | conchat = await client.get_chat(message.chat.id)
81 | conid = conchat.linked_chat.id
82 | chid = conid
83 | except:
84 | await message.reply("Is chat even linked")
85 | return
86 | chat_id = chid
87 | try:
88 | invitelink = await client.export_chat_invite_link(chid)
89 | except:
90 | await message.reply_text(
91 | "Add me as admin of yor channel first",
92 | )
93 | return
94 |
95 | try:
96 | user = await USER.get_me()
97 | except:
98 | user.first_name = "SophiaMusic"
99 |
100 | try:
101 | await USER.join_chat(invitelink)
102 | await USER.send_message(message.chat.id, "I joined here as you requested")
103 | except UserAlreadyParticipant:
104 | await message.reply_text(
105 | "helper already in your channel",
106 | )
107 | return
108 | except Exception as e:
109 | print(e)
110 | await message.reply_text(
111 | f"🛑 Flood Wait Error 🛑 \n User {user.first_name} couldn't join your channel due to heavy join requests for userbot! Make sure user is not banned in channel."
112 | "\n\nOr manually add @dihanofficialassistant to your Group and try again",
113 | )
114 | return
115 | await message.reply_text(
116 | "helper userbot joined your channel",
117 | )
118 |
119 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/channeladmins.py:
--------------------------------------------------------------------------------
1 | from asyncio import QueueEmpty
2 | from SophiaMusic.config import que
3 | from pyrogram import Client, filters
4 | from pyrogram.types import Message
5 |
6 | from SophiaMusic.function.admins import set
7 | from SophiaMusic.helpers.channelmusic import get_chat_id
8 | from SophiaMusic.helpers.decorators import authorized_users_only, errors
9 | from SophiaMusic.helpers.filters import command, other_filters
10 | from SophiaMusic.services.callsmusic import callsmusic
11 | from SophiaMusic.services.queues import queues
12 |
13 |
14 | @Client.on_message(filters.command(["channelpause","cpause"]) & filters.group & ~filters.edited)
15 | @errors
16 | @authorized_users_only
17 | async def pause(_, message: Message):
18 | try:
19 | conchat = await _.get_chat(message.chat.id)
20 | conid = conchat.linked_chat.id
21 | chid = conid
22 | except:
23 | await message.reply("Is chat even linked")
24 | return
25 | chat_id = chid
26 | if (chat_id not in callsmusic.pytgcalls.active_calls) or (
27 | callsmusic.pytgcalls.active_calls[chat_id] == "paused"
28 | ):
29 | await message.reply_text(" Nothing is playing! ❕ ")
30 | else:
31 | callsmusic.pytgcalls.pause_stream(chat_id)
32 | await message.reply_text("▶️ Paused!")
33 |
34 |
35 | @Client.on_message(filters.command(["channelresume","cresume"]) & filters.group & ~filters.edited)
36 | @errors
37 | @authorized_users_only
38 | async def resume(_, message: Message):
39 | try:
40 | conchat = await _.get_chat(message.chat.id)
41 | conid = conchat.linked_chat.id
42 | chid = conid
43 | except:
44 | await message.reply("Is chat even linked")
45 | return
46 | chat_id = chid
47 | if (chat_id not in callsmusic.pytgcalls.active_calls) or (
48 | callsmusic.pytgcalls.active_calls[chat_id] == "playing"
49 | ):
50 | await message.reply_text(" Nothing is paused! ❕")
51 | else:
52 | callsmusic.pytgcalls.resume_stream(chat_id)
53 | await message.reply_text("⏸ Resumed!")
54 |
55 |
56 | @Client.on_message(filters.command(["channelend","cend"]) & filters.group & ~filters.edited)
57 | @errors
58 | @authorized_users_only
59 | async def stop(_, message: Message):
60 | try:
61 | conchat = await _.get_chat(message.chat.id)
62 | conid = conchat.linked_chat.id
63 | chid = conid
64 | except:
65 | await message.reply("Is chat even linked")
66 | return
67 | chat_id = chid
68 | if chat_id not in callsmusic.pytgcalls.active_calls:
69 | await message.reply_text("Nothing is streaming! ❕")
70 | else:
71 | try:
72 | queues.clear(chat_id)
73 | except QueueEmpty:
74 | pass
75 |
76 | callsmusic.pytgcalls.leave_group_call(chat_id)
77 | await message.reply_text("❌ Stopped streaming!")
78 |
79 |
80 | @Client.on_message(filters.command(["channelskip","cskip"]) & filters.group & ~filters.edited)
81 | @errors
82 | @authorized_users_only
83 | async def skip(_, message: Message):
84 | global que
85 | try:
86 | conchat = await _.get_chat(message.chat.id)
87 | conid = conchat.linked_chat.id
88 | chid = conid
89 | except:
90 | await message.reply("Is chat even linked")
91 | return
92 | chat_id = chid
93 | if chat_id not in callsmusic.pytgcalls.active_calls:
94 | await message.reply_text("Nothing is playing to skip! ❕")
95 | else:
96 | queues.task_done(chat_id)
97 |
98 | if queues.is_empty(chat_id):
99 | callsmusic.pytgcalls.leave_group_call(chat_id)
100 | else:
101 | callsmusic.pytgcalls.change_stream(
102 | chat_id, queues.get(chat_id)["file"]
103 | )
104 |
105 | qeue = que.get(chat_id)
106 | if qeue:
107 | skip = qeue.pop(0)
108 | if not qeue:
109 | return
110 | await message.reply_text(f"- Skipped **{skip[0]}**\n- Now Playing **{qeue[0][0]}**")
111 |
112 |
113 | @Client.on_message(filters.command("channeladmincache"))
114 | @errors
115 | async def admincache(client, message: Message):
116 | try:
117 | conchat = await client.get_chat(message.chat.id)
118 | conid = conchat.linked_chat.id
119 | chid = conid
120 | except:
121 | await message.reply("Is chat even linked")
122 | return
123 | set(
124 | chid,
125 | [
126 | member.user
127 | for member in await conchat.linked_chat.get_members(filter="administrators")
128 | ],
129 | )
130 | await message.reply_text("Admin cache refreshed! ✔")
131 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/admins.py:
--------------------------------------------------------------------------------
1 | # Calls Music 1 - Telegram bot for streaming audio in group calls
2 | # Copyright (C) 2021 Roj Serbest
3 |
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as
6 | # published by the Free Software Foundation, either version 3 of the
7 | # License, or (at your option) any later version.
8 |
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | """ Sophia Music v6 """
18 |
19 | from asyncio import QueueEmpty
20 | from SophiaMusic.config import que
21 | from pyrogram import Client, filters
22 | from pyrogram.types import Message
23 |
24 | from SophiaMusic.function.admins import set
25 | from SophiaMusic.helpers.channelmusic import get_chat_id
26 | from SophiaMusic.helpers.decorators import authorized_users_only, errors
27 | from SophiaMusic.helpers.filters import command, other_filters
28 | from SophiaMusic.services.callsmusic import callsmusic
29 | from SophiaMusic.services.queues import queues
30 |
31 |
32 | @Client.on_message(filters.command("adminreset"))
33 | async def update_admin(client, message: Message):
34 | chat_id = get_chat_id(message.chat)
35 | set(
36 | chat_id,
37 | [
38 | member.user
39 | for member in await message.chat.get_members(filter="administrators")
40 | ],
41 | )
42 | await message.reply_text(" Admin cache refreshed! ✔ ")
43 |
44 |
45 | @Client.on_message(command("pause") & other_filters)
46 | @errors
47 | @authorized_users_only
48 | async def pause(_, message: Message):
49 | chat_id = get_chat_id(message.chat)
50 | if (chat_id not in callsmusic.pytgcalls.active_calls) or (
51 | callsmusic.pytgcalls.active_calls[chat_id] == "paused"
52 | ):
53 | await message.reply_text("Nothing is playing! ❕")
54 | else:
55 | callsmusic.pytgcalls.pause_stream(chat_id)
56 | await message.reply_text("Paused!")
57 |
58 |
59 | @Client.on_message(command("resume") & other_filters)
60 | @errors
61 | @authorized_users_only
62 | async def resume(_, message: Message):
63 | chat_id = get_chat_id(message.chat)
64 | if (chat_id not in callsmusic.pytgcalls.active_calls) or (
65 | callsmusic.pytgcalls.active_calls[chat_id] == "playing"
66 | ):
67 | await message.reply_text(" Nothing is paused! ❕ ")
68 | else:
69 | callsmusic.pytgcalls.resume_stream(chat_id)
70 | await message.reply_text(" Resumed!")
71 |
72 |
73 | @Client.on_message(command("end") & other_filters)
74 | @errors
75 | @authorized_users_only
76 | async def stop(_, message: Message):
77 | chat_id = get_chat_id(message.chat)
78 | if chat_id not in callsmusic.pytgcalls.active_calls:
79 | await message.reply_text(" Nothing is streaming! ❕ ")
80 | else:
81 | try:
82 | queues.clear(chat_id)
83 | except QueueEmpty:
84 | pass
85 |
86 | callsmusic.pytgcalls.leave_group_call(chat_id)
87 | await message.reply_text("❌ Stopped streaming!")
88 |
89 |
90 | @Client.on_message(command("skip") & other_filters)
91 | @errors
92 | @authorized_users_only
93 | async def skip(_, message: Message):
94 | global que
95 | chat_id = get_chat_id(message.chat)
96 | if chat_id not in callsmusic.pytgcalls.active_calls:
97 | await message.reply_text("❗ Nothing is playing to skip!")
98 | else:
99 | queues.task_done(chat_id)
100 |
101 | if queues.is_empty(chat_id):
102 | callsmusic.pytgcalls.leave_group_call(chat_id)
103 | else:
104 | callsmusic.pytgcalls.change_stream(
105 | chat_id, queues.get(chat_id)["file"]
106 | )
107 |
108 | qeue = que.get(chat_id)
109 | if qeue:
110 | skip = qeue.pop(0)
111 | if not qeue:
112 | return
113 | await message.reply_text(f"- Skipped **{skip[0]}**\n- Now Playing **{qeue[0][0]}**")
114 |
115 |
116 | @Client.on_message(filters.command("admincache"))
117 | @errors
118 | async def admincache(client, message: Message):
119 | set(
120 | message.chat.id,
121 | [
122 | member.user
123 | for member in await message.chat.get_members(filter="administrators")
124 | ],
125 | )
126 | await message.reply_text(" Admin cache refreshed! ✔")
127 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/song.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import asyncio
4 | import math
5 | import os
6 | import time
7 | from random import randint
8 | from urllib.parse import urlparse
9 |
10 | import aiofiles
11 | import aiohttp
12 | import requests
13 | import wget
14 | import youtube_dl
15 | from pyrogram import Client, filters
16 | from pyrogram.errors import FloodWait, MessageNotModified
17 | from pyrogram.types import Message
18 | from youtube_search import YoutubeSearch
19 | from youtubesearchpython import SearchVideos
20 |
21 | from SophiaMusic.config import DURATION_LIMIT
22 | from SophiaMusic.modules.play import arq
23 |
24 |
25 | @Client.on_message(filters.command("song") & ~filters.channel)
26 | def song(client, message):
27 |
28 | user_id = message.from_user.id
29 | user_name = message.from_user.first_name
30 | rpk = "[" + user_name + "](tg://user?id=" + str(user_id) + ")"
31 |
32 | query = ""
33 | for i in message.command[1:]:
34 | query += " " + str(i)
35 | print(query)
36 | m = message.reply("🔎 Finding the song...")
37 | ydl_opts = {"format": "bestaudio/best[ext=m4a]"}
38 | try:
39 | results = YoutubeSearch(query, max_results=1).to_dict()
40 | link = f"https://youtube.com{results[0]['url_suffix']}"
41 | # print(results)
42 | title = results[0]["title"][:40]
43 | thumbnail = results[0]["thumbnails"][0]
44 | thumb_name = f"thumb{title}.jpg"
45 | thumb = requests.get(thumbnail, allow_redirects=True)
46 | open(thumb_name, "wb").write(thumb.content)
47 |
48 | duration = results[0]["duration"]
49 | results[0]["url_suffix"]
50 | results[0]["views"]
51 |
52 | except Exception as e:
53 | m.edit("❌ Found Nothing.\n\nTry another keywork or maybe spell it properly.")
54 | print(str(e))
55 | return
56 | m.edit("Downloading the song ")
57 | try:
58 | with youtube_dl.YoutubeDL(ydl_opts) as ydl:
59 | info_dict = ydl.extract_info(link, download=False)
60 | audio_file = ydl.prepare_filename(info_dict)
61 | ydl.process_info(info_dict)
62 | rep = "**🎵 Uploaded by **"
63 | secmul, dur, dur_arr = 1, 0, duration.split(":")
64 | for i in range(len(dur_arr) - 1, -1, -1):
65 | dur += int(dur_arr[i]) * secmul
66 | secmul *= 60
67 | message.reply_audio(
68 | audio_file,
69 | caption=rep,
70 | thumb=thumb_name,
71 | parse_mode="md",
72 | title=title,
73 | duration=dur,
74 | )
75 | m.delete()
76 | except Exception as e:
77 | m.edit("❌ Error")
78 | print(e)
79 |
80 | try:
81 | os.remove(audio_file)
82 | os.remove(thumb_name)
83 | except Exception as e:
84 | print(e)
85 |
86 |
87 | # Many thanks to friday userbot project github.com/devsexpo/fridayuserbot
88 | # Copied from https://github.com/StarkGang/FridayUserbot/blob/master/main_startup/helper_func/basic_helpers.py
89 |
90 |
91 | # Copyright (C) 2020-2021 by DevsExpo@Github, < https://github.com/DevsExpo >.
92 | #
93 | # This file is part of < https://github.com/DevsExpo/FridayUserBot > project,
94 | # and is released under the "GNU v3.0 License Agreement".
95 | # Please see < https://github.com/DevsExpo/blob/master/LICENSE >
96 | #
97 | # All rights reserved.
98 |
99 |
100 | def get_text(message: Message) -> [None, str]:
101 | text_to_return = message.text
102 | if message.text is None:
103 | return None
104 | if " " in text_to_return:
105 | try:
106 | return message.text.split(None, 1)[1]
107 | except IndexError:
108 | return None
109 | else:
110 | return None
111 |
112 |
113 | def humanbytes(size):
114 | if not size:
115 | return ""
116 | power = 2 ** 10
117 | raised_to_pow = 0
118 | dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"}
119 | while size > power:
120 | size /= power
121 | raised_to_pow += 1
122 | return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B"
123 |
124 |
125 | async def progress(current, total, message, start, type_of_ps, file_name=None):
126 | now = time.time()
127 | diff = now - start
128 | if round(diff % 10.00) == 0 or current == total:
129 | percentage = current * 100 / total
130 | speed = current / diff
131 | elapsed_time = round(diff) * 1000
132 | if elapsed_time == 0:
133 | return
134 | time_to_completion = round((total - current) / speed) * 1000
135 | estimated_total_time = elapsed_time + time_to_completion
136 | progress_str = "{0}{1} {2}%\n".format(
137 | "".join(["🔴" for i in range(math.floor(percentage / 10))]),
138 | "".join(["🔘" for i in range(10 - math.floor(percentage / 10))]),
139 | round(percentage, 2),
140 | )
141 | tmp = progress_str + "{0} of {1}\nETA: {2}".format(
142 | humanbytes(current), humanbytes(total), time_formatter(estimated_total_time)
143 | )
144 | if file_name:
145 | try:
146 | await message.edit(
147 | "{}\n**File Name:** `{}`\n{}".format(type_of_ps, file_name, tmp)
148 | )
149 | except FloodWait as e:
150 | await asyncio.sleep(e.x)
151 | except MessageNotModified:
152 | pass
153 | else:
154 | try:
155 | await message.edit("{}\n{}".format(type_of_ps, tmp))
156 | except FloodWait as e:
157 | await asyncio.sleep(e.x)
158 | except MessageNotModified:
159 | pass
160 |
161 |
162 | def get_user(message: Message, text: str) -> [int, str, None]:
163 | if text is None:
164 | asplit = None
165 | else:
166 | asplit = text.split(" ", 1)
167 | user_s = None
168 | reason_ = None
169 | if message.reply_to_message:
170 | user_s = message.reply_to_message.from_user.id
171 | reason_ = text if text else None
172 | elif asplit is None:
173 | return None, None
174 | elif len(asplit[0]) > 0:
175 | user_s = int(asplit[0]) if asplit[0].isdigit() else asplit[0]
176 | if len(asplit) == 2:
177 | reason_ = asplit[1]
178 | return user_s, reason_
179 |
180 |
181 | def get_readable_time(seconds: int) -> int:
182 | count = 0
183 | ping_time = ""
184 | time_list = []
185 | time_suffix_list = ["s", "m", "h", "days"]
186 |
187 | while count < 4:
188 | count += 1
189 | if count < 3:
190 | remainder, result = divmod(seconds, 60)
191 | else:
192 | remainder, result = divmod(seconds, 24)
193 | if seconds == 0 and remainder == 0:
194 | break
195 | time_list.append(int(result))
196 | seconds = int(remainder)
197 |
198 | for x in range(len(time_list)):
199 | time_list[x] = str(time_list[x]) + time_suffix_list[x]
200 | if len(time_list) == 4:
201 | ping_time += time_list.pop() + ", "
202 |
203 | time_list.reverse()
204 | ping_time += ":".join(time_list)
205 |
206 | return ping_time
207 |
208 |
209 | def time_formatter(milliseconds: int) -> str:
210 | seconds, milliseconds = divmod(int(milliseconds), 1000)
211 | minutes, seconds = divmod(seconds, 60)
212 | hours, minutes = divmod(minutes, 60)
213 | days, hours = divmod(hours, 24)
214 | tmp = (
215 | ((str(days) + " day(s), ") if days else "")
216 | + ((str(hours) + " hour(s), ") if hours else "")
217 | + ((str(minutes) + " minute(s), ") if minutes else "")
218 | + ((str(seconds) + " second(s), ") if seconds else "")
219 | + ((str(milliseconds) + " millisecond(s), ") if milliseconds else "")
220 | )
221 | return tmp[:-2]
222 |
223 | # ===================================================================================
224 |
225 |
226 | ydl_opts = {
227 | "format": "bestaudio/best",
228 | "writethumbnail": True,
229 | "postprocessors": [
230 | {
231 | "key": "FFmpegExtractAudio",
232 | "preferredcodec": "mp3",
233 | "preferredquality": "192",
234 | }
235 | ],
236 | }
237 |
238 |
239 | def get_file_extension_from_url(url):
240 | url_path = urlparse(url).path
241 | basename = os.path.basename(url_path)
242 | return basename.split(".")[-1]
243 |
244 |
245 | # Funtion To Download Song
246 | async def download_song(url):
247 | song_name = f"{randint(6969, 6999)}.mp3"
248 | async with aiohttp.ClientSession() as session:
249 | async with session.get(url) as resp:
250 | if resp.status == 200:
251 | f = await aiofiles.open(song_name, mode="wb")
252 | await f.write(await resp.read())
253 | await f.close()
254 | return song_name
255 |
256 |
257 | is_downloading = False
258 |
259 |
260 | def time_to_seconds(time):
261 | stringt = str(time)
262 | return sum(int(x) * 60 ** i for i, x in enumerate(reversed(stringt.split(":"))))
263 |
264 |
265 | @Client.on_message(filters.command("saavn") & ~filters.edited)
266 | async def jssong(_, message):
267 | global is_downloading
268 | if len(message.command) < 2:
269 | await message.reply_text("/saavn requires an argument.")
270 | return
271 | if is_downloading:
272 | await message.reply_text(
273 | "Another download is in progress, try again after sometime."
274 | )
275 | return
276 | is_downloading = True
277 | text = message.text.split(None, 1)[1]
278 | query = text.replace(" ", "%20")
279 | m = await message.reply_text("Searching...")
280 |
281 | # ======= Copied from https://github.com/TheHamkerCat/WilliamButcherBot/blob/dev/wbb/modules/music.py line 170 ========
282 |
283 | """
284 | MIT License
285 | Copyright (c) 2021 TheHamkerCat
286 | Permission is hereby granted, free of charge, to any person obtaining a copy
287 | of this software and associated documentation files (the "Software"), to deal
288 | in the Software without restriction, including without limitation the rights
289 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
290 | copies of the Software, and to permit persons to whom the Software is
291 | furnished to do so, subject to the following conditions:
292 | The above copyright notice and this permission notice shall be included in all
293 | copies or substantial portions of the Software.
294 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
295 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
296 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
297 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
298 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
299 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
300 | SOFTWARE.
301 | """
302 |
303 | try:
304 | songs = await arq.saavn(query)
305 | if not songs.ok:
306 | await message.reply_text(songs.result)
307 | return
308 | sname = songs.result[0].song
309 | slink = songs.result[0].media_url
310 | ssingers = songs.result[0].singers
311 |
312 | # ==================================================================================================
313 |
314 | await m.edit("Downloading")
315 | song = await download_song(slink)
316 | await m.edit("Uploading")
317 | await message.reply_audio(audio=song, title=sname, performer=ssingers)
318 | os.remove(song)
319 | await m.delete()
320 | except Exception as e:
321 | is_downloading = False
322 | await m.edit(str(e))
323 | return
324 | is_downloading = False
325 |
326 |
327 | @Client.on_message(filters.command(["vsong", "video"]))
328 | async def ytmusic(client, message: Message):
329 | global is_downloading
330 | if is_downloading:
331 | await message.reply_text(
332 | "Another download is in progress, try again after sometime."
333 | )
334 | return
335 |
336 | urlissed = get_text(message)
337 |
338 | pablo = await client.send_message(
339 | message.chat.id, f"`Getting {urlissed} From Youtube Servers. Please Wait.`"
340 | )
341 | if not urlissed:
342 | await pablo.edit("Invalid Command Syntax, Please Check Help Menu To Know More!")
343 | return
344 |
345 | search = SearchVideos(f"{urlissed}", offset=1, mode="dict", max_results=1)
346 | mi = search.result()
347 | mio = mi["search_result"]
348 | mo = mio[0]["link"]
349 | thum = mio[0]["title"]
350 | fridayz = mio[0]["id"]
351 | thums = mio[0]["channel"]
352 | kekme = f"https://img.youtube.com/vi/{fridayz}/hqdefault.jpg"
353 | await asyncio.sleep(0.6)
354 | url = mo
355 | sedlyf = wget.download(kekme)
356 | opts = {
357 | "format": "best",
358 | "addmetadata": True,
359 | "key": "FFmpegMetadata",
360 | "prefer_ffmpeg": True,
361 | "geo_bypass": True,
362 | "nocheckcertificate": True,
363 | "postprocessors": [{"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}],
364 | "outtmpl": "%(id)s.mp4",
365 | "logtostderr": False,
366 | "quiet": True,
367 | }
368 | try:
369 | is_downloading = True
370 | with youtube_dl.YoutubeDL(opts) as ytdl:
371 | infoo = ytdl.extract_info(url, False)
372 | duration = round(infoo["duration"] / 60)
373 |
374 | if duration > DURATION_LIMIT:
375 | await pablo.edit(
376 | f"❌ Videos longer than {DURATION_LIMIT} minute(s) aren't allowed, the provided video is {duration} minute(s)"
377 | )
378 | is_downloading = False
379 | return
380 | ytdl_data = ytdl.extract_info(url, download=True)
381 |
382 | except Exception:
383 | # await pablo.edit(event, f"**Failed To Download** \n**Error :** `{str(e)}`")
384 | is_downloading = False
385 | return
386 |
387 | c_time = time.time()
388 | file_stark = f"{ytdl_data['id']}.mp4"
389 | capy = f"**Video Name ➠** `{thum}` \n**Requested For :** `{urlissed}` \n**Channel :** `{thums}` \n**Link :** `{mo}`"
390 | await client.send_video(
391 | message.chat.id,
392 | video=open(file_stark, "rb"),
393 | duration=int(ytdl_data["duration"]),
394 | file_name=str(ytdl_data["title"]),
395 | thumb=sedlyf,
396 | caption=capy,
397 | supports_streaming=True,
398 | progress=progress,
399 | progress_args=(
400 | pablo,
401 | c_time,
402 | f"`Uploading {urlissed} Song From YouTube Music!`",
403 | file_stark,
404 | ),
405 | )
406 | await pablo.delete()
407 | is_downloading = False
408 | for files in (sedlyf, file_stark):
409 | if files and os.path.exists(files):
410 | os.remove(files)
411 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/channelplay.py:
--------------------------------------------------------------------------------
1 | """ Sophia Music v6 """
2 |
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU Affero General Public License as
5 | # published by the Free Software Foundation, either version 3 of the
6 | # License, or (at your option) any later version.
7 |
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Affero General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Affero General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | import json
18 | import os
19 | from os import path
20 | from typing import Callable
21 |
22 | import aiofiles
23 | import aiohttp
24 | import ffmpeg
25 | import requests
26 | import wget
27 | from PIL import Image, ImageDraw, ImageFont
28 | from pyrogram import Client, filters
29 | from pyrogram.errors import UserAlreadyParticipant
30 | from pyrogram.types import Voice
31 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
32 | from Python_ARQ import ARQ
33 | from youtube_search import YoutubeSearch
34 | from SophiaMusic.modules.play import generate_cover
35 | from SophiaMusic.modules.play import arq
36 | from SophiaMusic.modules.play import cb_admin_check
37 | from SophiaMusic.modules.play import transcode
38 | from SophiaMusic.modules.play import convert_seconds
39 | from SophiaMusic.modules.play import time_to_seconds
40 | from SophiaMusic.modules.play import changeImageSize
41 | from SophiaMusic.config import BOT_NAME as bn
42 | from SophiaMusic.config import DURATION_LIMIT
43 | from SophiaMusic.config import UPDATES_CHANNEL as updateschannel
44 | from SophiaMusic.config import que
45 | from SophiaMusic.function.admins import admins as a
46 | from SophiaMusic.helpers.errors import DurationLimitError
47 | from SophiaMusic.helpers.decorators import errors
48 | from SophiaMusic.helpers.admins import get_administrators
49 | from SophiaMusic.helpers.channelmusic import get_chat_id
50 | from SophiaMusic.helpers.decorators import authorized_users_only
51 | from SophiaMusic.helpers.filters import command, other_filters
52 | from SophiaMusic.helpers.gets import get_file_name
53 | from SophiaMusic.services.callsmusic import callsmusic
54 | from SophiaMusic.services.callsmusic.callsmusic import client as USER
55 | from SophiaMusic.services.converter.converter import convert
56 | from SophiaMusic.services.downloaders import youtube
57 | from SophiaMusic.services.queues import queues
58 |
59 | chat_id = None
60 |
61 |
62 |
63 | @Client.on_message(filters.command(["channelplaylist","cplaylist"]) & filters.group & ~filters.edited)
64 | async def playlist(client, message):
65 | try:
66 | lel = await client.get_chat(message.chat.id)
67 | lol = lel.linked_chat.id
68 | except:
69 | message.reply("Is this cat even linked?")
70 | return
71 | global que
72 | queue = que.get(lol)
73 | if not queue:
74 | await message.reply_text("Player is idle")
75 | temp = []
76 | for t in queue:
77 | temp.append(t)
78 | now_playing = temp[0][0]
79 | by = temp[0][1].mention(style="md")
80 | msg = "**Now Playing** in {}".format(lel.linked_chat.title)
81 | msg += "\n- " + now_playing
82 | msg += "\n- Req by " + by
83 | temp.pop(0)
84 | if temp:
85 | msg += "\n\n"
86 | msg += "**Queue**"
87 | for song in temp:
88 | name = song[0]
89 | usr = song[1].mention(style="md")
90 | msg += f"\n- {name}"
91 | msg += f"\n- Req by {usr}\n"
92 | await message.reply_text(msg)
93 |
94 |
95 | # ============================= Settings =========================================
96 |
97 |
98 | def updated_stats(chat, queue, vol=100):
99 | if chat.id in callsmusic.pytgcalls.active_calls:
100 | # if chat.id in active_chats:
101 | stats = "Settings of **{}**".format(chat.title)
102 | if len(que) > 0:
103 | stats += "\n\n"
104 | stats += "Volume : {}%\n".format(vol)
105 | stats += "Songs in queue : `{}`\n".format(len(que))
106 | stats += "Now Playing : **{}**\n".format(queue[0][0])
107 | stats += "Requested by : {}".format(queue[0][1].mention)
108 | else:
109 | stats = None
110 | return stats
111 |
112 |
113 | def r_ply(type_):
114 | if type_ == "play":
115 | pass
116 | else:
117 | pass
118 | mar = InlineKeyboardMarkup(
119 | [
120 | [
121 | InlineKeyboardButton("⏹", "cleave"),
122 | InlineKeyboardButton("⏸", "cpuse"),
123 | InlineKeyboardButton("▶️", "cresume"),
124 | InlineKeyboardButton("⏭", "cskip"),
125 | ],
126 | [
127 | InlineKeyboardButton("Playlist 📖", "cplaylist"),
128 | ],
129 | [InlineKeyboardButton("❌ Close", "ccls")],
130 | ]
131 | )
132 | return mar
133 |
134 |
135 | @Client.on_message(filters.command(["channelcurrent","ccurrent"]) & filters.group & ~filters.edited)
136 | async def ee(client, message):
137 | try:
138 | lel = await client.get_chat(message.chat.id)
139 | lol = lel.linked_chat.id
140 | conv = lel.linked_chat
141 | except:
142 | await message.reply("Is chat even linked")
143 | return
144 | queue = que.get(lol)
145 | stats = updated_stats(conv, queue)
146 | if stats:
147 | await message.reply(stats)
148 | else:
149 | await message.reply("No VC instances running in this chat")
150 |
151 |
152 | @Client.on_message(filters.command(["channelplayer","cplayer"]) & filters.group & ~filters.edited)
153 | @authorized_users_only
154 | async def settings(client, message):
155 | playing = None
156 | try:
157 | lel = await client.get_chat(message.chat.id)
158 | lol = lel.linked_chat.id
159 | conv = lel.linked_chat
160 | except:
161 | await message.reply("Is chat even linked")
162 | return
163 | queue = que.get(lol)
164 | stats = updated_stats(conv, queue)
165 | if stats:
166 | if playing:
167 | await message.reply(stats, reply_markup=r_ply("pause"))
168 |
169 | else:
170 | await message.reply(stats, reply_markup=r_ply("play"))
171 | else:
172 | await message.reply("No VC instances running in this chat")
173 |
174 |
175 | @Client.on_callback_query(filters.regex(pattern=r"^(cplaylist)$"))
176 | async def p_cb(b, cb):
177 | global que
178 | try:
179 | lel = await client.get_chat(cb.message.chat.id)
180 | lol = lel.linked_chat.id
181 | conv = lel.linked_chat
182 | except:
183 | return
184 | que.get(lol)
185 | type_ = cb.matches[0].group(1)
186 | cb.message.chat.id
187 | cb.message.chat
188 | cb.message.reply_markup.inline_keyboard[1][0].callback_data
189 | if type_ == "playlist":
190 | queue = que.get(lol)
191 | if not queue:
192 | await cb.message.edit("Player is idle")
193 | temp = []
194 | for t in queue:
195 | temp.append(t)
196 | now_playing = temp[0][0]
197 | by = temp[0][1].mention(style="md")
198 | msg = "**Now Playing** in {}".format(conv.title)
199 | msg += "\n- " + now_playing
200 | msg += "\n- Req by " + by
201 | temp.pop(0)
202 | if temp:
203 | msg += "\n\n"
204 | msg += "**Queue**"
205 | for song in temp:
206 | name = song[0]
207 | usr = song[1].mention(style="md")
208 | msg += f"\n- {name}"
209 | msg += f"\n- Req by {usr}\n"
210 | await cb.message.edit(msg)
211 |
212 |
213 | @Client.on_callback_query(
214 | filters.regex(pattern=r"^(cplay|cpause|cskip|cleave|cpuse|cresume|cmenu|ccls)$")
215 | )
216 | @cb_admin_check
217 | async def m_cb(b, cb):
218 | global que
219 | if (
220 | cb.message.chat.title.startswith("Channel Music: ")
221 | and chat.title[14:].isnumeric()
222 | ):
223 | chet_id = int(chat.title[13:])
224 | else:
225 | try:
226 | lel = await b.get_chat(cb.message.chat.id)
227 | lol = lel.linked_chat.id
228 | conv = lel.linked_chat
229 | chet_id = lol
230 | except:
231 | return
232 | qeue = que.get(chet_id)
233 | type_ = cb.matches[0].group(1)
234 | cb.message.chat.id
235 | m_chat = cb.message.chat
236 |
237 |
238 | the_data = cb.message.reply_markup.inline_keyboard[1][0].callback_data
239 | if type_ == "cpause":
240 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
241 | callsmusic.pytgcalls.active_calls[chet_id] == "paused"
242 | ):
243 | await cb.answer("Chat is not connected!", show_alert=True)
244 | else:
245 | callsmusic.pytgcalls.pause_stream(chet_id)
246 |
247 | await cb.answer("Music Paused!")
248 | await cb.message.edit(
249 | updated_stats(conv, qeue), reply_markup=r_ply("play")
250 | )
251 |
252 | elif type_ == "cplay":
253 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
254 | callsmusic.pytgcalls.active_calls[chet_id] == "playing"
255 | ):
256 | await cb.answer("Chat is not connected!", show_alert=True)
257 | else:
258 | callsmusic.pytgcalls.resume_stream(chet_id)
259 | await cb.answer("Music Resumed!")
260 | await cb.message.edit(
261 | updated_stats(conv, qeue), reply_markup=r_ply("pause")
262 | )
263 |
264 | elif type_ == "cplaylist":
265 | queue = que.get(cb.message.chat.id)
266 | if not queue:
267 | await cb.message.edit("Player is idle")
268 | temp = []
269 | for t in queue:
270 | temp.append(t)
271 | now_playing = temp[0][0]
272 | by = temp[0][1].mention(style="md")
273 | msg = "**Now Playing** in {}".format(cb.message.chat.title)
274 | msg += "\n- " + now_playing
275 | msg += "\n- Req by " + by
276 | temp.pop(0)
277 | if temp:
278 | msg += "\n\n"
279 | msg += "**Queue**"
280 | for song in temp:
281 | name = song[0]
282 | usr = song[1].mention(style="md")
283 | msg += f"\n- {name}"
284 | msg += f"\n- Req by {usr}\n"
285 | await cb.message.edit(msg)
286 |
287 | elif type_ == "cresume":
288 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
289 | callsmusic.pytgcalls.active_calls[chet_id] == "playing"
290 | ):
291 | await cb.answer("Chat is not connected or already playng", show_alert=True)
292 | else:
293 | callsmusic.pytgcalls.resume_stream(chet_id)
294 | await cb.answer("Music Resumed!")
295 | elif type_ == "cpuse":
296 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
297 | callsmusic.pytgcalls.active_calls[chet_id] == "paused"
298 | ):
299 | await cb.answer("Chat is not connected or already paused", show_alert=True)
300 | else:
301 | callsmusic.pytgcalls.pause_stream(chet_id)
302 |
303 | await cb.answer("Music Paused!")
304 | elif type_ == "ccls":
305 | await cb.answer("Closed menu")
306 | await cb.message.delete()
307 |
308 | elif type_ == "cmenu":
309 | stats = updated_stats(conv, qeue)
310 | await cb.answer("Menu opened")
311 | marr = InlineKeyboardMarkup(
312 | [
313 | [
314 | InlineKeyboardButton("⏹", "cleave"),
315 | InlineKeyboardButton("⏸", "cpuse"),
316 | InlineKeyboardButton("▶️", "cresume"),
317 | InlineKeyboardButton("⏭", "cskip"),
318 | ],
319 | [
320 | InlineKeyboardButton("Playlist 📖", "cplaylist"),
321 | ],
322 | [InlineKeyboardButton("❌ Close", "ccls")],
323 | ]
324 | )
325 | await cb.message.edit(stats, reply_markup=marr)
326 | elif type_ == "cskip":
327 | if qeue:
328 | qeue.pop(0)
329 | if chet_id not in callsmusic.pytgcalls.active_calls:
330 | await cb.answer("Chat is not connected!", show_alert=True)
331 | else:
332 | queues.task_done(chet_id)
333 |
334 | if queues.is_empty(chet_id):
335 | callsmusic.pytgcalls.leave_group_call(chet_id)
336 |
337 | await cb.message.edit("- No More Playlist..\n- Leaving VC!")
338 | else:
339 | callsmusic.pytgcalls.change_stream(
340 | chet_id, queues.get(chet_id)["file"]
341 | )
342 | await cb.answer("Skipped")
343 | await cb.message.edit((m_chat, qeue), reply_markup=r_ply(the_data))
344 | await cb.message.reply_text(
345 | f"- Skipped track\n- Now Playing **{qeue[0][0]}**"
346 | )
347 |
348 | else:
349 | if chet_id in callsmusic.pytgcalls.active_calls:
350 | try:
351 | queues.clear(chet_id)
352 | except QueueEmpty:
353 | pass
354 |
355 | callsmusic.pytgcalls.leave_group_call(chet_id)
356 | await cb.message.edit("Successfully Left the Chat!")
357 | else:
358 | await cb.answer("Chat is not connected!", show_alert=True)
359 |
360 |
361 | @Client.on_message(filters.command(["channelplay","cplay"]) & filters.group & ~filters.edited)
362 | @authorized_users_only
363 | async def play(_, message: Message):
364 | global que
365 | lel = await message.reply("🔄 **Processing**")
366 |
367 | try:
368 | conchat = await _.get_chat(message.chat.id)
369 | conv = conchat.linked_chat
370 | conid = conchat.linked_chat.id
371 | chid = conid
372 | except:
373 | await message.reply("Is chat even linked")
374 | return
375 | try:
376 | administrators = await get_administrators(conv)
377 | except:
378 | await message.reply("Am I admin of Channel")
379 | try:
380 | user = await USER.get_me()
381 | except:
382 | user.first_name = "helper"
383 | usar = user
384 | wew = usar.id
385 | try:
386 | # chatdetails = await USER.get_chat(chid)
387 | await _.get_chat_member(chid, wew)
388 | except:
389 | for administrator in administrators:
390 | if administrator == message.from_user.id:
391 | if message.chat.title.startswith("Channel Music: "):
392 | await lel.edit(
393 | "Remember to add helper to your channel",
394 | )
395 | pass
396 |
397 | try:
398 | invitelink = await _.export_chat_invite_link(chid)
399 | except:
400 | await lel.edit(
401 | "Add me as admin of yor channel first",
402 | )
403 | return
404 |
405 | try:
406 | await USER.join_chat(invitelink)
407 | await lel.edit(
408 | "helper userbot joined your channel",
409 | )
410 |
411 | except UserAlreadyParticipant:
412 | pass
413 | except Exception:
414 | # print(e)
415 | await lel.edit(
416 | f"🔴 Flood Wait Error 🔴 \nUser {user.first_name} couldn't join your channel due to heavy requests for userbot! Make sure user is not banned in group."
417 | "\n\nOr manually add assistant to your Group and try again",
418 | )
419 | try:
420 | await USER.get_chat(chid)
421 | # lmoa = await client.get_chat_member(chid,wew)
422 | except:
423 | await lel.edit(
424 | f" {user.first_name} Userbot not in this chat, Ask channel admin to send /play command for first time or add {user.first_name} manually"
425 | )
426 | return
427 | message.from_user.id
428 | text_links = None
429 | message.from_user.first_name
430 | await lel.edit("🔎 **Finding**")
431 | message.from_user.id
432 | user_id = message.from_user.id
433 | message.from_user.first_name
434 | user_name = message.from_user.first_name
435 | rpk = "[" + user_name + "](tg://user?id=" + str(user_id) + ")"
436 | if message.reply_to_message:
437 | entities = []
438 | toxt = message.reply_to_message.text or message.reply_to_message.caption
439 | if message.reply_to_message.entities:
440 | entities = message.reply_to_message.entities + entities
441 | elif message.reply_to_message.caption_entities:
442 | entities = message.reply_to_message.entities + entities
443 | urls = [entity for entity in entities if entity.type == 'url']
444 | text_links = [
445 | entity for entity in entities if entity.type == 'text_link'
446 | ]
447 | else:
448 | urls=None
449 | if text_links:
450 | urls = True
451 | audio = (
452 | (message.reply_to_message.audio or message.reply_to_message.voice)
453 | if message.reply_to_message
454 | else None
455 | )
456 | if audio:
457 | if round(audio.duration / 60) > DURATION_LIMIT:
458 | raise DurationLimitError(
459 | f"❌ Videos longer than {DURATION_LIMIT} minute(s) aren't allowed to play!"
460 | )
461 | keyboard = InlineKeyboardMarkup(
462 | [
463 | [
464 | InlineKeyboardButton("📖 Playlist", callback_data="cplaylist"),
465 | InlineKeyboardButton("Menu ⏯ ", callback_data="cmenu"),
466 | ],
467 | [InlineKeyboardButton(text="❌ Close", callback_data="ccls")],
468 | ]
469 | )
470 | file_name = get_file_name(audio)
471 | title = file_name
472 | thumb_name = "https://telegra.ph/file/f6086f8909fbfeb0844f2.png"
473 | thumbnail = thumb_name
474 | duration = round(audio.duration / 60)
475 | views = "Locally added"
476 | requested_by = message.from_user.first_name
477 | await generate_cover(requested_by, title, views, duration, thumbnail)
478 | file_path = await convert(
479 | (await message.reply_to_message.download(file_name))
480 | if not path.isfile(path.join("downloads", file_name))
481 | else file_name
482 | )
483 | elif urls:
484 | query = toxt
485 | await lel.edit("🎵 **Processing**")
486 | ydl_opts = {"format": "bestaudio/best"}
487 | try:
488 | results = YoutubeSearch(query, max_results=1).to_dict()
489 | url = f"https://youtube.com{results[0]['url_suffix']}"
490 | # print(results)
491 | title = results[0]["title"][:40]
492 | thumbnail = results[0]["thumbnails"][0]
493 | thumb_name = f"thumb{title}.jpg"
494 | thumb = requests.get(thumbnail, allow_redirects=True)
495 | open(thumb_name, "wb").write(thumb.content)
496 | duration = results[0]["duration"]
497 | results[0]["url_suffix"]
498 | views = results[0]["views"]
499 |
500 | except Exception as e:
501 | await lel.edit(
502 | "Song not found.Try another song or maybe spell it properly."
503 | )
504 | print(str(e))
505 | return
506 | dlurl = url
507 | dlurl=dlurl.replace("youtube","youtubepp")
508 | keyboard = InlineKeyboardMarkup(
509 | [
510 | [
511 | InlineKeyboardButton("📖 Playlist", callback_data="cplaylist"),
512 | InlineKeyboardButton("Menu ⏯ ", callback_data="cmenu"),
513 | ],
514 | [
515 | InlineKeyboardButton(text="🎬 YouTube", url=f"{url}"),
516 | InlineKeyboardButton(text="Download 📥", url=f"{dlurl}"),
517 | ],
518 | [InlineKeyboardButton(text="❌ Close", callback_data="ccls")],
519 | ]
520 | )
521 | requested_by = message.from_user.first_name
522 | await generate_cover(requested_by, title, views, duration, thumbnail)
523 | file_path = await convert(youtube.download(url))
524 | else:
525 | query = ""
526 | for i in message.command[1:]:
527 | query += " " + str(i)
528 | print(query)
529 | await lel.edit("🎵 **Processing**")
530 | ydl_opts = {"format": "bestaudio/best"}
531 | try:
532 | results = YoutubeSearch(query, max_results=1).to_dict()
533 | url = f"https://youtube.com{results[0]['url_suffix']}"
534 | # print(results)
535 | title = results[0]["title"][:40]
536 | thumbnail = results[0]["thumbnails"][0]
537 | thumb_name = f"thumb{title}.jpg"
538 | thumb = requests.get(thumbnail, allow_redirects=True)
539 | open(thumb_name, "wb").write(thumb.content)
540 | duration = results[0]["duration"]
541 | results[0]["url_suffix"]
542 | views = results[0]["views"]
543 |
544 | except Exception as e:
545 | await lel.edit(
546 | "Song not found.Try another song or maybe spell it properly."
547 | )
548 | print(str(e))
549 | return
550 |
551 | dlurl = url
552 | dlurl=dlurl.replace("youtube","youtubepp")
553 | keyboard = InlineKeyboardMarkup(
554 | [
555 | [
556 | InlineKeyboardButton("📖 Playlist", callback_data="cplaylist"),
557 | InlineKeyboardButton("Menu ⏯ ", callback_data="cmenu"),
558 | ],
559 | [
560 | InlineKeyboardButton(text="🎬 YouTube", url=f"{url}"),
561 | InlineKeyboardButton(text="Download 📥", url=f"{dlurl}"),
562 | ],
563 | [InlineKeyboardButton(text="❌ Close", callback_data="ccls")],
564 | ]
565 | )
566 | requested_by = message.from_user.first_name
567 | await generate_cover(requested_by, title, views, duration, thumbnail)
568 | file_path = await convert(youtube.download(url))
569 | chat_id = chid
570 | if chat_id in callsmusic.pytgcalls.active_calls:
571 | position = await queues.put(chat_id, file=file_path)
572 | qeue = que.get(chat_id)
573 | s_name = title
574 | r_by = message.from_user
575 | loc = file_path
576 | appendable = [s_name, r_by, loc]
577 | qeue.append(appendable)
578 | await message.reply_photo(
579 | photo="final.png",
580 | caption=f"#⃣ Your requested song **queued** at position {position}!",
581 | reply_markup=keyboard,
582 | )
583 | os.remove("final.png")
584 | return await lel.delete()
585 | else:
586 | chat_id = chid
587 | que[chat_id] = []
588 | qeue = que.get(chat_id)
589 | s_name = title
590 | r_by = message.from_user
591 | loc = file_path
592 | appendable = [s_name, r_by, loc]
593 | qeue.append(appendable)
594 | callsmusic.pytgcalls.join_group_call(chat_id, file_path)
595 | await message.reply_photo(
596 | photo="final.png",
597 | reply_markup=keyboard,
598 | caption="▶️ **Playing** the song requested by {} via Youtube Music 😜 in Linked Channel".format(
599 | message.from_user.mention()
600 | ),
601 | )
602 | os.remove("final.png")
603 | return await lel.delete()
604 |
605 |
606 | @Client.on_message(filters.command(["channelsplay","csplay"]) & filters.group & ~filters.edited)
607 | @authorized_users_only
608 | async def jiosaavn(client: Client, message_: Message):
609 | global que
610 | lel = await message_.reply("🔄 **Processing**")
611 | try:
612 | conchat = await client.get_chat(message_.chat.id)
613 | conid = conchat.linked_chat.id
614 | conv = conchat.linked_chat
615 | chid = conid
616 | except:
617 | await message_.reply("Is chat even linked")
618 | return
619 | try:
620 | administrators = await get_administrators(conv)
621 | except:
622 | await message.reply("Am I admin of Channel")
623 | try:
624 | user = await USER.get_me()
625 | except:
626 | user.first_name = "DaisyMusic"
627 | usar = user
628 | wew = usar.id
629 | try:
630 | # chatdetails = await USER.get_chat(chid)
631 | await client.get_chat_member(chid, wew)
632 | except:
633 | for administrator in administrators:
634 | if administrator == message_.from_user.id:
635 | if message_.chat.title.startswith("Channel Music: "):
636 | await lel.edit(
637 | "Remember to add helper to your channel",
638 | )
639 | pass
640 | try:
641 | invitelink = await client.export_chat_invite_link(chid)
642 | except:
643 | await lel.edit(
644 | "Add me as admin of yor group first",
645 | )
646 | return
647 |
648 | try:
649 | await USER.join_chat(invitelink)
650 | await lel.edit(
651 | "helper userbot joined your channel",
652 | )
653 |
654 | except UserAlreadyParticipant:
655 | pass
656 | except Exception:
657 | # print(e)
658 | await lel.edit(
659 | f"🔴 Flood Wait Error 🔴 \nUser {user.first_name} couldn't join your channel due to heavy requests for userbot! Make sure user is not banned in group."
660 | "\n\nOr manually add @DaisyXmusic to your Group and try again",
661 | )
662 | try:
663 | await USER.get_chat(chid)
664 | # lmoa = await client.get_chat_member(chid,wew)
665 | except:
666 | await lel.edit(
667 | " helper Userbot not in this channel, Ask channel admin to send /play command for first time or add assistant manually"
668 | )
669 | return
670 | requested_by = message_.from_user.first_name
671 | chat_id = message_.chat.id
672 | text = message_.text.split(" ", 1)
673 | query = text[1]
674 | res = lel
675 | await res.edit(f"Searching 👀👀👀 for `{query}` on jio saavn")
676 | try:
677 | songs = await arq.saavn(query)
678 | if not songs.ok:
679 | await message_.reply_text(songs.result)
680 | return
681 | sname = songs.result[0].song
682 | slink = songs.result[0].media_url
683 | ssingers = songs.result[0].singers
684 | sthumb = "https://telegra.ph/file/f6086f8909fbfeb0844f2.png"
685 | sduration = int(songs.result[0].duration)
686 | except Exception as e:
687 | await res.edit("Found Literally Nothing!, You Should Work On Your English.")
688 | print(str(e))
689 | return
690 | keyboard = InlineKeyboardMarkup(
691 | [
692 | [
693 | InlineKeyboardButton("📖 Playlist", callback_data="cplaylist"),
694 | InlineKeyboardButton("Menu ⏯ ", callback_data="cmenu"),
695 | ],
696 | [
697 | InlineKeyboardButton(
698 | text="Join Updates Channel", url=f"https://t.me/{updateschannel}"
699 | )
700 | ],
701 | [InlineKeyboardButton(text="❌ Close", callback_data="ccls")],
702 | ]
703 | )
704 | file_path = await convert(wget.download(slink))
705 | chat_id = chid
706 | if chat_id in callsmusic.pytgcalls.active_calls:
707 | position = await queues.put(chat_id, file=file_path)
708 | qeue = que.get(chat_id)
709 | s_name = sname
710 | r_by = message_.from_user
711 | loc = file_path
712 | appendable = [s_name, r_by, loc]
713 | qeue.append(appendable)
714 | await res.delete()
715 | m = await client.send_photo(
716 | chat_id=message_.chat.id,
717 | reply_markup=keyboard,
718 | photo="final.png",
719 | caption=f"✯{bn}✯=#️⃣ Queued at position {position}",
720 | )
721 |
722 | else:
723 | await res.edit_text(f"{bn}=▶️ Playing.....")
724 | que[chat_id] = []
725 | qeue = que.get(chat_id)
726 | s_name = sname
727 | r_by = message_.from_user
728 | loc = file_path
729 | appendable = [s_name, r_by, loc]
730 | qeue.append(appendable)
731 | callsmusic.pytgcalls.join_group_call(chat_id, file_path)
732 | await res.edit("Generating Thumbnail.")
733 | await generate_cover(requested_by, sname, ssingers, sduration, sthumb)
734 | await res.delete()
735 | m = await client.send_photo(
736 | chat_id=message_.chat.id,
737 | reply_markup=keyboard,
738 | photo="final.png",
739 | caption=f"Playing {sname} Via Jiosaavn in linked channel",
740 | )
741 | os.remove("final.png")
742 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/SophiaMusic/modules/play.py:
--------------------------------------------------------------------------------
1 |
2 | """ Sophia Music v6 """
3 |
4 | import json
5 | import os
6 | from os import path
7 | from typing import Callable
8 |
9 | import aiofiles
10 | import aiohttp
11 | import ffmpeg
12 | import requests
13 | import wget
14 | from PIL import Image, ImageDraw, ImageFont
15 | from pyrogram import Client, filters
16 | from pyrogram.types import Voice
17 | from pyrogram.errors import UserAlreadyParticipant
18 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
19 | from Python_ARQ import ARQ
20 | from youtube_search import YoutubeSearch
21 |
22 | from SophiaMusic.config import ARQ_API_KEY
23 | from SophiaMusic.config import BOT_NAME as bn
24 | from SophiaMusic.config import DURATION_LIMIT
25 | from SophiaMusic.config import UPDATES_CHANNEL as updateschannel
26 | from SophiaMusic.config import que
27 | from SophiaMusic.config import THUMB_IMG
28 | from SophiaMusic.function.admins import admins as a
29 | from SophiaMusic.helpers.admins import get_administrators
30 | from SophiaMusic.helpers.channelmusic import get_chat_id
31 | from SophiaMusic.helpers.errors import DurationLimitError
32 | from SophiaMusic.helpers.decorators import errors
33 | from SophiaMusic.helpers.decorators import authorized_users_only
34 | from SophiaMusic.helpers.filters import command, other_filters
35 | from SophiaMusic.helpers.gets import get_file_name
36 |
37 |
38 | from SophiaMusic.services.callsmusic import callsmusic
39 | from SophiaMusic.services.callsmusic.callsmusic import client as USER
40 | from SophiaMusic.services.converter.converter import convert
41 | from SophiaMusic.services.downloaders import youtube
42 | from SophiaMusic.services.queues import queues
43 |
44 | aiohttpsession = aiohttp.ClientSession()
45 | chat_id = None
46 | arq = ARQ("https://thearq.tech", ARQ_API_KEY, aiohttpsession)
47 | DISABLED_GROUPS = []
48 | useer ="NaN"
49 | def cb_admin_check(func: Callable) -> Callable:
50 | async def decorator(client, cb):
51 | admemes = a.get(cb.message.chat.id)
52 | if cb.from_user.id in admemes:
53 | return await func(client, cb)
54 | else:
55 | await cb.answer("You ain't allowed!", show_alert=True)
56 | return
57 |
58 | return decorator
59 |
60 |
61 | def transcode(filename):
62 | ffmpeg.input(filename).output(
63 | "input.raw",
64 | format="s16le",
65 | acodec="pcm_s16le",
66 | ac=2,
67 | ar="48k"
68 | ).overwrite_output().run()
69 | os.remove(filename)
70 |
71 |
72 | # Convert seconds to mm:ss
73 | def convert_seconds(seconds):
74 | seconds = seconds % (24 * 3600)
75 | seconds %= 3600
76 | minutes = seconds // 60
77 | seconds %= 60
78 | return "%02d:%02d" % (minutes, seconds)
79 |
80 |
81 | # Convert hh:mm:ss to seconds
82 | def time_to_seconds(time):
83 | stringt = str(time)
84 | return sum(int(x) * 60 ** i for i, x in enumerate(reversed(stringt.split(":"))))
85 |
86 |
87 | # Change image size
88 | def changeImageSize(maxWidth, maxHeight, image):
89 | widthRatio = maxWidth / image.size[0]
90 | heightRatio = maxHeight / image.size[1]
91 | newWidth = int(widthRatio * image.size[0])
92 | newHeight = int(heightRatio * image.size[1])
93 | newImage = image.resize((newWidth, newHeight))
94 | return newImage
95 |
96 |
97 | async def generate_cover(requested_by, title, views, duration, thumbnail):
98 | async with aiohttp.ClientSession() as session:
99 | async with session.get(thumbnail) as resp:
100 | if resp.status == 200:
101 | f = await aiofiles.open("background.png", mode="wb")
102 | await f.write(await resp.read())
103 | await f.close()
104 |
105 | image1 = Image.open("./background.png")
106 | image2 = Image.open("etc/foreground.png")
107 | image3 = changeImageSize(1280, 720, image1)
108 | image4 = changeImageSize(1280, 720, image2)
109 | image5 = image3.convert("RGBA")
110 | image6 = image4.convert("RGBA")
111 | Image.alpha_composite(image5, image6).save("temp.png")
112 | img = Image.open("temp.png")
113 | draw = ImageDraw.Draw(img)
114 | font = ImageFont.truetype("etc/font.otf", 60)
115 | draw.text((40, 550), "Playing here...", (255, 255, 255), font=font)
116 | draw.text((40, 630), f"{title}", (255, 255, 255), font=font)
117 | img.save("final.png")
118 | os.remove("temp.png")
119 | os.remove("background.png")
120 |
121 |
122 | @Client.on_message(filters.command("playlist") & filters.group & ~filters.edited)
123 | async def playlist(client, message):
124 | global que
125 | if message.chat.id in DISABLED_GROUPS:
126 | return
127 | queue = que.get(message.chat.id)
128 | if not queue:
129 | await message.reply_text("Player is idle")
130 | temp = []
131 | for t in queue:
132 | temp.append(t)
133 | now_playing = temp[0][0]
134 | by = temp[0][1].mention(style="md")
135 | msg = "**Now Playing** in {}".format(message.chat.title)
136 | msg += "\n- " + now_playing
137 | msg += "\n- Req by " + by
138 | temp.pop(0)
139 | if temp:
140 | msg += "\n\n"
141 | msg += "**Queue**"
142 | for song in temp:
143 | name = song[0]
144 | usr = song[1].mention(style="md")
145 | msg += f"\n- {name}"
146 | msg += f"\n- Req by {usr}\n"
147 | await message.reply_text(msg)
148 |
149 |
150 | # ============================= Settings =========================================
151 |
152 |
153 | def updated_stats(chat, queue, vol=100):
154 | if chat.id in callsmusic.pytgcalls.active_calls:
155 | # if chat.id in active_chats:
156 | stats = "Settings of **{}**".format(chat.title)
157 | if len(que) > 0:
158 | stats += "\n\n"
159 | stats += "Volume : {}%\n".format(vol)
160 | stats += "Songs in queue : `{}`\n".format(len(que))
161 | stats += "Now Playing : **{}**\n".format(queue[0][0])
162 | stats += "Requested by : {}".format(queue[0][1].mention)
163 | else:
164 | stats = None
165 | return stats
166 |
167 |
168 | def r_ply(type_):
169 | if type_ == "play":
170 | pass
171 | else:
172 | pass
173 | mar = InlineKeyboardMarkup(
174 | [
175 | [
176 | InlineKeyboardButton("⏹", "leave"),
177 | InlineKeyboardButton("⏸", "puse"),
178 | InlineKeyboardButton("▶️", "resume"),
179 | InlineKeyboardButton("⏭", "skip"),
180 | ],
181 | [
182 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
183 | ],
184 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
185 | ]
186 | )
187 | return mar
188 |
189 |
190 | @Client.on_message(filters.command("current") & filters.group & ~filters.edited)
191 | async def ee(client, message):
192 | if message.chat.id in DISABLED_GROUPS:
193 | return
194 | queue = que.get(message.chat.id)
195 | stats = updated_stats(message.chat, queue)
196 | if stats:
197 | await message.reply(stats)
198 | else:
199 | await message.reply("No VC instances running in this chat")
200 |
201 |
202 | @Client.on_message(filters.command("player") & filters.group & ~filters.edited)
203 | @authorized_users_only
204 | async def settings(client, message):
205 | if message.chat.id in DISABLED_GROUPS:
206 | await message.reply("Music Player is Disabled")
207 | return
208 | playing = None
209 | chat_id = get_chat_id(message.chat)
210 | if chat_id in callsmusic.pytgcalls.active_calls:
211 | playing = True
212 | queue = que.get(chat_id)
213 | stats = updated_stats(message.chat, queue)
214 | if stats:
215 | if playing:
216 | await message.reply(stats, reply_markup=r_ply("pause"))
217 |
218 | else:
219 | await message.reply(stats, reply_markup=r_ply("play"))
220 | else:
221 | await message.reply("No VC instances running in this chat")
222 |
223 |
224 | @Client.on_message(
225 | filters.command("musicplayer") & ~filters.edited & ~filters.bot & ~filters.private
226 | )
227 | @authorized_users_only
228 | async def hfmm(_, message):
229 | global DISABLED_GROUPS
230 | try:
231 | user_id = message.from_user.id
232 | except:
233 | return
234 | if len(message.command) != 2:
235 | await message.reply_text(
236 | "I only recognize `/musicplayer on` and /musicplayer `off only`"
237 | )
238 | return
239 | status = message.text.split(None, 1)[1]
240 | message.chat.id
241 | if status == "ON" or status == "on" or status == "On":
242 | lel = await message.reply("`Processing...`")
243 | if not message.chat.id in DISABLED_GROUPS:
244 | await lel.edit("Music Player Already Activated In This Chat")
245 | return
246 | DISABLED_GROUPS.remove(message.chat.id)
247 | await lel.edit(
248 | f"Music Player Successfully Enabled For Users In The Chat {message.chat.id}"
249 | )
250 |
251 | elif status == "OFF" or status == "off" or status == "Off":
252 | lel = await message.reply("`Processing...`")
253 |
254 | if message.chat.id in DISABLED_GROUPS:
255 | await lel.edit("Music Player Already turned off In This Chat")
256 | return
257 | DISABLED_GROUPS.append(message.chat.id)
258 | await lel.edit(
259 | f"Music Player Successfully Deactivated For Users In The Chat {message.chat.id}"
260 | )
261 | else:
262 | await message.reply_text(
263 | "I only recognize `/musicplayer on` and /musicplayer `off only`"
264 | )
265 |
266 |
267 | @Client.on_callback_query(filters.regex(pattern=r"^(playlist)$"))
268 | async def p_cb(b, cb):
269 | global que
270 | que.get(cb.message.chat.id)
271 | type_ = cb.matches[0].group(1)
272 | cb.message.chat.id
273 | cb.message.chat
274 | cb.message.reply_markup.inline_keyboard[1][0].callback_data
275 | if type_ == "playlist":
276 | queue = que.get(cb.message.chat.id)
277 | if not queue:
278 | await cb.message.edit("Player is idle")
279 | temp = []
280 | for t in queue:
281 | temp.append(t)
282 | now_playing = temp[0][0]
283 | by = temp[0][1].mention(style="md")
284 | msg = "**Now Playing** in {}".format(cb.message.chat.title)
285 | msg += "\n- " + now_playing
286 | msg += "\n- Req by " + by
287 | temp.pop(0)
288 | if temp:
289 | msg += "\n\n"
290 | msg += "**Queue**"
291 | for song in temp:
292 | name = song[0]
293 | usr = song[1].mention(style="md")
294 | msg += f"\n- {name}"
295 | msg += f"\n- Req by {usr}\n"
296 | await cb.message.edit(msg)
297 |
298 |
299 | @Client.on_callback_query(
300 | filters.regex(pattern=r"^(play|pause|skip|leave|puse|resume|menu|cls)$")
301 | )
302 | @cb_admin_check
303 | async def m_cb(b, cb):
304 | global que
305 | if (
306 | cb.message.chat.title.startswith("Channel Music: ")
307 | and chat.title[14:].isnumeric()
308 | ):
309 | chet_id = int(chat.title[13:])
310 | else:
311 | chet_id = cb.message.chat.id
312 | qeue = que.get(chet_id)
313 | type_ = cb.matches[0].group(1)
314 | cb.message.chat.id
315 | m_chat = cb.message.chat
316 |
317 | the_data = cb.message.reply_markup.inline_keyboard[1][0].callback_data
318 | if type_ == "pause":
319 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
320 | callsmusic.pytgcalls.active_calls[chet_id] == "paused"
321 | ):
322 | await cb.answer("Chat is not connected!", show_alert=True)
323 | else:
324 | callsmusic.pytgcalls.pause_stream(chet_id)
325 |
326 | await cb.answer("Music Paused!")
327 | await cb.message.edit(
328 | updated_stats(m_chat, qeue), reply_markup=r_ply("play")
329 | )
330 |
331 | elif type_ == "play":
332 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
333 | callsmusic.pytgcalls.active_calls[chet_id] == "playing"
334 | ):
335 | await cb.answer("Chat is not connected!", show_alert=True)
336 | else:
337 | callsmusic.pytgcalls.resume_stream(chet_id)
338 | await cb.answer("Music Resumed!")
339 | await cb.message.edit(
340 | updated_stats(m_chat, qeue), reply_markup=r_ply("pause")
341 | )
342 |
343 | elif type_ == "playlist":
344 | queue = que.get(cb.message.chat.id)
345 | if not queue:
346 | await cb.message.edit("Player is idle")
347 | temp = []
348 | for t in queue:
349 | temp.append(t)
350 | now_playing = temp[0][0]
351 | by = temp[0][1].mention(style="md")
352 | msg = "**Now Playing** in {}".format(cb.message.chat.title)
353 | msg += "\n- " + now_playing
354 | msg += "\n- Req by " + by
355 | temp.pop(0)
356 | if temp:
357 | msg += "\n\n"
358 | msg += "**Queue**"
359 | for song in temp:
360 | name = song[0]
361 | usr = song[1].mention(style="md")
362 | msg += f"\n- {name}"
363 | msg += f"\n- Req by {usr}\n"
364 | await cb.message.edit(msg)
365 |
366 | elif type_ == "resume":
367 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
368 | callsmusic.pytgcalls.active_calls[chet_id] == "playing"
369 | ):
370 | await cb.answer("Chat is not connected or already playng", show_alert=True)
371 | else:
372 | callsmusic.pytgcalls.resume_stream(chet_id)
373 | await cb.answer("Music Resumed!")
374 | elif type_ == "puse":
375 | if (chet_id not in callsmusic.pytgcalls.active_calls) or (
376 | callsmusic.pytgcalls.active_calls[chet_id] == "paused"
377 | ):
378 | await cb.answer("Chat is not connected or already paused", show_alert=True)
379 | else:
380 | callsmusic.pytgcalls.pause_stream(chet_id)
381 |
382 | await cb.answer("Music Paused!")
383 | elif type_ == "cls":
384 | await cb.answer("Closed menu")
385 | await cb.message.delete()
386 |
387 | elif type_ == "menu":
388 | stats = updated_stats(cb.message.chat, qeue)
389 | await cb.answer("Menu opened")
390 | marr = InlineKeyboardMarkup(
391 | [
392 | [
393 | InlineKeyboardButton("⏹", "leave"),
394 | InlineKeyboardButton("⏸", "puse"),
395 | InlineKeyboardButton("▶️", "resume"),
396 | InlineKeyboardButton("⏭", "skip"),
397 | ],
398 | [
399 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
400 | ],
401 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
402 | ]
403 | )
404 | await cb.message.edit(stats, reply_markup=marr)
405 | elif type_ == "skip":
406 | if qeue:
407 | qeue.pop(0)
408 | if chet_id not in callsmusic.pytgcalls.active_calls:
409 | await cb.answer("Chat is not connected!", show_alert=True)
410 | else:
411 | queues.task_done(chet_id)
412 |
413 | if queues.is_empty(chet_id):
414 | callsmusic.pytgcalls.leave_group_call(chet_id)
415 |
416 | await cb.message.edit("- No More Playlist..\n- Leaving VC!")
417 | else:
418 | callsmusic.pytgcalls.change_stream(
419 | chet_id, queues.get(chet_id)["file"]
420 | )
421 | await cb.answer("Skipped")
422 | await cb.message.edit((m_chat, qeue), reply_markup=r_ply(the_data))
423 | await cb.message.reply_text(
424 | f"- Skipped track\n- Now Playing **{qeue[0][0]}**"
425 | )
426 |
427 | else:
428 | if chet_id in callsmusic.pytgcalls.active_calls:
429 | try:
430 | queues.clear(chet_id)
431 | except QueueEmpty:
432 | pass
433 |
434 | callsmusic.pytgcalls.leave_group_call(chet_id)
435 | await cb.message.edit("Successfully Left the Chat!")
436 | else:
437 | await cb.answer("Chat is not connected!", show_alert=True)
438 |
439 |
440 | @Client.on_message(command("play") & other_filters)
441 | async def play(_, message: Message):
442 | global que
443 | global useer
444 | if message.chat.id in DISABLED_GROUPS:
445 | return
446 | lel = await message.reply("🔄 **Processing Sounds**")
447 | administrators = await get_administrators(message.chat)
448 | chid = message.chat.id
449 |
450 | try:
451 | user = await USER.get_me()
452 | except:
453 | user.first_name = "helper"
454 | usar = user
455 | wew = usar.id
456 | try:
457 | # chatdetails = await USER.get_chat(chid)
458 | await _.get_chat_member(chid, wew)
459 | except:
460 | for administrator in administrators:
461 | if administrator == message.from_user.id:
462 | if message.chat.title.startswith("Channel Music: "):
463 | await lel.edit(
464 | "Remember to add helper to your channel",
465 | )
466 | pass
467 | try:
468 | invitelink = await _.export_chat_invite_link(chid)
469 | except:
470 | await lel.edit(
471 | "Add me as admin of yor group first",
472 | )
473 | return
474 |
475 | try:
476 | await USER.join_chat(invitelink)
477 | await USER.send_message(
478 | message.chat.id, "I joined this group for playing music in VC"
479 | )
480 | await lel.edit(
481 | "helper userbot joined your chat",
482 | )
483 |
484 | except UserAlreadyParticipant:
485 | pass
486 | except Exception:
487 | # print(e)
488 | await lel.edit(
489 | f"🔴 Flood Wait Error 🔴 \nUser {user.first_name} couldn't join your group due to heavy requests for userbot! Make sure user is not banned in group."
490 | "\n\nOr manually add assistant to your Group and try again",
491 | )
492 | try:
493 | await USER.get_chat(chid)
494 | # lmoa = await client.get_chat_member(chid,wew)
495 | except:
496 | await lel.edit(
497 | f" {user.first_name} Userbot not in this chat, Ask admin to send /play command for first time or add {user.first_name} manually"
498 | )
499 | return
500 | text_links=None
501 | await lel.edit("🔎 **Finding**")
502 | if message.reply_to_message:
503 | entities = []
504 | toxt = message.reply_to_message.text or message.reply_to_message.caption
505 | if message.reply_to_message.entities:
506 | entities = message.reply_to_message.entities + entities
507 | elif message.reply_to_message.caption_entities:
508 | entities = message.reply_to_message.entities + entities
509 | urls = [entity for entity in entities if entity.type == 'url']
510 | text_links = [
511 | entity for entity in entities if entity.type == 'text_link'
512 | ]
513 | else:
514 | urls=None
515 | if text_links:
516 | urls = True
517 | user_id = message.from_user.id
518 | user_name = message.from_user.first_name
519 | rpk = "[" + user_name + "](tg://user?id=" + str(user_id) + ")"
520 | audio = (
521 | (message.reply_to_message.audio or message.reply_to_message.voice)
522 | if message.reply_to_message
523 | else None
524 | )
525 | if audio:
526 | if round(audio.duration / 60) > DURATION_LIMIT:
527 | raise DurationLimitError(
528 | f"Videos longer than {DURATION_LIMIT} minute(s) aren't allowed to play! ✖"
529 | )
530 | keyboard = InlineKeyboardMarkup(
531 | [
532 | [
533 | InlineKeyboardButton("🎚 Playlist ", callback_data="playlist"),
534 | InlineKeyboardButton("Menu ⏯ ", callback_data="menu"),
535 | ],
536 | [InlineKeyboardButton(text=" Close Menu 🎛", callback_data="cls")],
537 | ]
538 | )
539 | file_name = get_file_name(audio)
540 | title = file_name
541 | thumb_name = "https://telegra.ph/file/f6086f8909fbfeb0844f2.png"
542 | thumbnail = thumb_name
543 | duration = round(audio.duration / 60)
544 | views = "Locally added"
545 | requested_by = message.from_user.first_name
546 | await generate_cover(requested_by, title, views, duration, thumbnail)
547 | file_path = await convert(
548 | (await message.reply_to_message.download(file_name))
549 | if not path.isfile(path.join("downloads", file_name))
550 | else file_name
551 | )
552 | elif urls:
553 | query = toxt
554 | await lel.edit("🎵 **Downloading..**")
555 | ydl_opts = {"format": "bestaudio/best"}
556 | try:
557 | results = YoutubeSearch(query, max_results=1).to_dict()
558 | url = f"https://youtube.com{results[0]['url_suffix']}"
559 | # print(results)
560 | title = results[0]["title"][:40]
561 | thumbnail = results[0]["thumbnails"][0]
562 | thumb_name = f"thumb{title}.jpg"
563 | thumb = requests.get(thumbnail, allow_redirects=True)
564 | open(thumb_name, "wb").write(thumb.content)
565 | duration = results[0]["duration"]
566 | results[0]["url_suffix"]
567 | views = results[0]["views"]
568 |
569 | except Exception as e:
570 | await lel.edit(
571 | "Song not found.Try another song or maybe spell it properly."
572 | )
573 | print(str(e))
574 | return
575 | dlurl=url
576 | dlurl=dlurl.replace("youtube","youtubepp")
577 | keyboard = InlineKeyboardMarkup(
578 | [
579 | [
580 | InlineKeyboardButton("⏹", "leave"),
581 | InlineKeyboardButton("⏸", "puse"),
582 | InlineKeyboardButton("▶️", "resume"),
583 | InlineKeyboardButton("⏭", "skip"),
584 | ],
585 | [
586 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
587 | InlineKeyboardButton("Settings ⚙", "menu"),
588 | ],
589 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
590 | ]
591 | )
592 | requested_by = message.from_user.first_name
593 | await generate_cover(requested_by, title, views, duration, thumbnail)
594 | file_path = await convert(youtube.download(url))
595 | else:
596 | query = ""
597 | for i in message.command[1:]:
598 | query += " " + str(i)
599 | print(query)
600 | await lel.edit("🎵 **Downloading..**")
601 | ydl_opts = {"format": "bestaudio/best"}
602 |
603 | try:
604 | results = YoutubeSearch(query, max_results=5).to_dict()
605 | except:
606 | await lel.edit("Give me something to play")
607 | # Looks like hell. Aren't it?? FUCK OFF
608 | try:
609 | toxxt = "**Select the song you want to play**\n\n"
610 | j = 0
611 | useer=user_name
612 | emojilist = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣",]
613 |
614 | while j < 5:
615 | toxxt += f"{emojilist[j]} **Title - [{results[j]['title']}](https://youtube.com{results[j]['url_suffix']})**\n"
616 | toxxt += f" ╚ **Duration** - {results[j]['duration']}\n"
617 | toxxt += f" ╚ **Views** - {results[j]['views']}\n"
618 | toxxt += f" ╚ **Channel** - {results[j]['channel']}\n\n"
619 |
620 | j += 1
621 | koyboard = InlineKeyboardMarkup(
622 | [
623 | [
624 | InlineKeyboardButton("1️⃣", callback_data=f'plll 0|{query}|{user_id}'),
625 | InlineKeyboardButton("2️⃣", callback_data=f'plll 1|{query}|{user_id}'),
626 | InlineKeyboardButton("3️⃣", callback_data=f'plll 2|{query}|{user_id}'),
627 | ],
628 | [
629 | InlineKeyboardButton("4️⃣", callback_data=f'plll 3|{query}|{user_id}'),
630 | InlineKeyboardButton("5️⃣", callback_data=f'plll 4|{query}|{user_id}'),
631 | ],
632 | [InlineKeyboardButton(text="Close ", callback_data="cls")],
633 | ]
634 | )
635 | await lel.edit(toxxt,reply_markup=koyboard,disable_web_page_preview=True)
636 | # WHY PEOPLE ALWAYS LOVE PORN ?? (A point to think)
637 | return
638 | # Returning to pornhub
639 | except:
640 | await lel.edit("No Enough results to choose.. Starting direct play..")
641 |
642 | # print(results)
643 | try:
644 | url = f"https://youtube.com{results[0]['url_suffix']}"
645 | title = results[0]["title"][:40]
646 | thumbnail = results[0]["thumbnails"][0]
647 | thumb_name = f"thumb{title}.jpg"
648 | thumb = requests.get(thumbnail, allow_redirects=True)
649 | open(thumb_name, "wb").write(thumb.content)
650 | duration = results[0]["duration"]
651 | results[0]["url_suffix"]
652 | views = results[0]["views"]
653 |
654 | except Exception as e:
655 | await lel.edit(
656 | "Song not found.Try another song or maybe spell it properly."
657 | )
658 | print(str(e))
659 | return
660 | dlurl=url
661 | dlurl=dlurl.replace("youtube","youtubepp")
662 | keyboard = InlineKeyboardMarkup(
663 | [
664 | [
665 | InlineKeyboardButton("⏹", "leave"),
666 | InlineKeyboardButton("⏸", "puse"),
667 | InlineKeyboardButton("▶️", "resume"),
668 | InlineKeyboardButton("⏭", "skip"),
669 | ],
670 | [
671 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
672 | InlineKeyboardButton("Settings ⚙", "menu"),
673 | ],
674 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
675 | ]
676 | )
677 | requested_by = message.from_user.first_name
678 | await generate_cover(requested_by, title, views, duration, thumbnail)
679 | file_path = await convert(youtube.download(url))
680 | chat_id = get_chat_id(message.chat)
681 | if chat_id in callsmusic.pytgcalls.active_calls:
682 | position = await queues.put(chat_id, file=file_path)
683 | qeue = que.get(chat_id)
684 | s_name = title
685 | r_by = message.from_user
686 | loc = file_path
687 | appendable = [s_name, r_by, loc]
688 | qeue.append(appendable)
689 | await message.reply_photo(
690 | photo="final.png",
691 | caption=f"#⃣ Your requested song **queued** at position {position}!",
692 | reply_markup=keyboard,
693 | )
694 | os.remove("final.png")
695 | return await lel.delete()
696 | else:
697 | chat_id = get_chat_id(message.chat)
698 | que[chat_id] = []
699 | qeue = que.get(chat_id)
700 | s_name = title
701 | r_by = message.from_user
702 | loc = file_path
703 | appendable = [s_name, r_by, loc]
704 | qeue.append(appendable)
705 | try:
706 | callsmusic.pytgcalls.join_group_call(chat_id, file_path)
707 | except:
708 | message.reply("Group Call is not connected or I can't join it")
709 | return
710 | await message.reply_photo(
711 | photo="final.png",
712 | reply_markup=keyboard,
713 | caption="▶️ **Playing** here the song requested by {} via Youtube Music 😜".format(
714 | message.from_user.mention()
715 | ),
716 | )
717 | os.remove("final.png")
718 | return await lel.delete()
719 |
720 |
721 | @Client.on_message(filters.command("ytplay") & filters.group & ~filters.edited)
722 | async def ytplay(_, message: Message):
723 | global que
724 | if message.chat.id in DISABLED_GROUPS:
725 | return
726 | lel = await message.reply("🔄 **Processing**")
727 | administrators = await get_administrators(message.chat)
728 | chid = message.chat.id
729 |
730 | try:
731 | user = await USER.get_me()
732 | except:
733 | user.first_name = "helper"
734 | usar = user
735 | wew = usar.id
736 | try:
737 | # chatdetails = await USER.get_chat(chid)
738 | await _.get_chat_member(chid, wew)
739 | except:
740 | for administrator in administrators:
741 | if administrator == message.from_user.id:
742 | if message.chat.title.startswith("Channel Music: "):
743 | await lel.edit(
744 | "Remember to add helper to your channel",
745 | )
746 | pass
747 | try:
748 | invitelink = await _.export_chat_invite_link(chid)
749 | except:
750 | await lel.edit(
751 | "Add me as admin of yor group first",
752 | )
753 | return
754 |
755 | try:
756 | await USER.join_chat(invitelink)
757 | await USER.send_message(
758 | message.chat.id, "I joined this group for playing music in VC"
759 | )
760 | await lel.edit(
761 | "helper userbot joined your chat",
762 | )
763 |
764 | except UserAlreadyParticipant:
765 | pass
766 | except Exception:
767 | # print(e)
768 | await lel.edit(
769 | f"🔴 Flood Wait Error 🔴 \nUser {user.first_name} couldn't join your group due to heavy requests for userbot! Make sure user is not banned in group."
770 | "\n\nOr manually add assistant to your Group and try again",
771 | )
772 | try:
773 | await USER.get_chat(chid)
774 | # lmoa = await client.get_chat_member(chid,wew)
775 | except:
776 | await lel.edit(
777 | f" {user.first_name} Userbot not in this chat, Ask admin to send /play command for first time or add {user.first_name} manually"
778 | )
779 | return
780 | await lel.edit("🔎 **Finding**")
781 | user_id = message.from_user.id
782 | user_name = message.from_user.first_name
783 |
784 |
785 | query = ""
786 | for i in message.command[1:]:
787 | query += " " + str(i)
788 | print(query)
789 | await lel.edit("🎵 **Processing**")
790 | ydl_opts = {"format": "bestaudio/best"}
791 | try:
792 | results = YoutubeSearch(query, max_results=1).to_dict()
793 | url = f"https://youtube.com{results[0]['url_suffix']}"
794 | # print(results)
795 | title = results[0]["title"][:40]
796 | thumbnail = results[0]["thumbnails"][0]
797 | thumb_name = f"thumb{title}.jpg"
798 | thumb = requests.get(thumbnail, allow_redirects=True)
799 | open(thumb_name, "wb").write(thumb.content)
800 | duration = results[0]["duration"]
801 | results[0]["url_suffix"]
802 | views = results[0]["views"]
803 |
804 | except Exception as e:
805 | await lel.edit(
806 | "Song not found.Try another song or maybe spell it properly."
807 | )
808 | print(str(e))
809 | return
810 | dlurl=url
811 | dlurl=dlurl.replace("youtube","youtubepp")
812 | keyboard = InlineKeyboardMarkup(
813 | [
814 | [
815 | InlineKeyboardButton("⏹", "leave"),
816 | InlineKeyboardButton("⏸", "puse"),
817 | InlineKeyboardButton("▶️", "resume"),
818 | InlineKeyboardButton("⏭", "skip"),
819 | ],
820 | [
821 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
822 | InlineKeyboardButton("Settings ⚙", "menu"),
823 | ],
824 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
825 | ]
826 | )
827 | requested_by = message.from_user.first_name
828 | await generate_cover(requested_by, title, views, duration, thumbnail)
829 | file_path = await convert(youtube.download(url))
830 | chat_id = get_chat_id(message.chat)
831 | if chat_id in callsmusic.pytgcalls.active_calls:
832 | position = await queues.put(chat_id, file=file_path)
833 | qeue = que.get(chat_id)
834 | s_name = title
835 | r_by = message.from_user
836 | loc = file_path
837 | appendable = [s_name, r_by, loc]
838 | qeue.append(appendable)
839 | await message.reply_photo(
840 | photo="final.png",
841 | caption=f"#⃣ Your requested song **queued** at position {position}!",
842 | reply_markup=keyboard,
843 | )
844 | os.remove("final.png")
845 | return await lel.delete()
846 | else:
847 | chat_id = get_chat_id(message.chat)
848 | que[chat_id] = []
849 | qeue = que.get(chat_id)
850 | s_name = title
851 | r_by = message.from_user
852 | loc = file_path
853 | appendable = [s_name, r_by, loc]
854 | qeue.append(appendable)
855 | try:
856 | callsmusic.pytgcalls.join_group_call(chat_id, file_path)
857 | except:
858 | message.reply("Group Call is not connected or I can't join it")
859 | return
860 | await message.reply_photo(
861 | photo="final.png",
862 | reply_markup=keyboard,
863 | caption="▶️ **Playing** here the song requested by {} via Youtube Music 😜".format(
864 | message.from_user.mention()
865 | ),
866 | )
867 | os.remove("final.png")
868 | return await lel.delete()
869 |
870 |
871 | @Client.on_message(filters.command("splay") & filters.group & ~filters.edited)
872 | async def jiosaavn(client: Client, message_: Message):
873 | global que
874 | if message_.chat.id in DISABLED_GROUPS:
875 | return
876 | lel = await message_.reply("🔄 **Processing**")
877 | administrators = await get_administrators(message_.chat)
878 | chid = message_.chat.id
879 | try:
880 | user = await USER.get_me()
881 | except:
882 | user.first_name = "DaisyMusic"
883 | usar = user
884 | wew = usar.id
885 | try:
886 | # chatdetails = await USER.get_chat(chid)
887 | await client.get_chat_member(chid, wew)
888 | except:
889 | for administrator in administrators:
890 | if administrator == message_.from_user.id:
891 | if message_.chat.title.startswith("Channel Music: "):
892 | await lel.edit(
893 | "Remember to add helper to your channel",
894 | )
895 | pass
896 | try:
897 | invitelink = await client.export_chat_invite_link(chid)
898 | except:
899 | await lel.edit(
900 | "Add me as admin of yor group first",
901 | )
902 | return
903 |
904 | try:
905 | await USER.join_chat(invitelink)
906 | await USER.send_message(
907 | message_.chat.id, "I joined this group for playing music in VC"
908 | )
909 | await lel.edit(
910 | "helper userbot joined your chat",
911 | )
912 |
913 | except UserAlreadyParticipant:
914 | pass
915 | except Exception:
916 | # print(e)
917 | await lel.edit(
918 | f"🔴 Flood Wait Error 🔴 \nUser {user.first_name} couldn't join your group due to heavy requests for userbot! Make sure user is not banned in group."
919 | "\n\nOr manually add @SophiaSLBot to your Group and try again",
920 | )
921 | try:
922 | await USER.get_chat(chid)
923 | # lmoa = await client.get_chat_member(chid,wew)
924 | except:
925 | await lel.edit(
926 | " helper Userbot not in this chat, Ask admin to send /play command for first time or add assistant manually"
927 | )
928 | return
929 | requested_by = message_.from_user.first_name
930 | chat_id = message_.chat.id
931 | text = message_.text.split(" ", 1)
932 | query = text[1]
933 | res = lel
934 | await res.edit(f"Searching 👀👀👀 for `{query}` on jio saavn")
935 | try:
936 | songs = await arq.saavn(query)
937 | if not songs.ok:
938 | await message_.reply_text(songs.result)
939 | return
940 | sname = songs.result[0].song
941 | slink = songs.result[0].media_url
942 | ssingers = songs.result[0].singers
943 | sthumb = songs.result[0].image
944 | sduration = int(songs.result[0].duration)
945 | except Exception as e:
946 | await res.edit("Found Literally Nothing!, You Should Work On Your English.")
947 | print(str(e))
948 | return
949 | try:
950 | duuration= round(sduration / 60)
951 | if duuration > DURATION_LIMIT:
952 | await cb.message.edit(f"Music longer than {DURATION_LIMIT}min are not allowed to play")
953 | return
954 | except:
955 | pass
956 | keyboard = InlineKeyboardMarkup(
957 | [
958 | [
959 | InlineKeyboardButton("⏹", "leave"),
960 | InlineKeyboardButton("⏸", "puse"),
961 | InlineKeyboardButton("▶️", "resume"),
962 | InlineKeyboardButton("⏭", "skip"),
963 | ],
964 | [
965 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
966 | InlineKeyboardButton("Settings ⚙", "menu"),
967 | ],
968 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
969 | ]
970 | )
971 | file_path = await convert(wget.download(slink))
972 | chat_id = get_chat_id(message_.chat)
973 | if chat_id in callsmusic.pytgcalls.active_calls:
974 | position = await queues.put(chat_id, file=file_path)
975 | qeue = que.get(chat_id)
976 | s_name = sname
977 | r_by = message_.from_user
978 | loc = file_path
979 | appendable = [s_name, r_by, loc]
980 | qeue.append(appendable)
981 | await res.delete()
982 | m = await client.send_photo(
983 | chat_id=message_.chat.id,
984 | reply_markup=keyboard,
985 | photo="final.png",
986 | caption=f"✯{bn}✯=#️⃣ Queued at position {position}",
987 | )
988 |
989 | else:
990 | await res.edit_text(f"{bn}=▶️ Playing.....")
991 | que[chat_id] = []
992 | qeue = que.get(chat_id)
993 | s_name = sname
994 | r_by = message_.from_user
995 | loc = file_path
996 | appendable = [s_name, r_by, loc]
997 | qeue.append(appendable)
998 | try:
999 | callsmusic.pytgcalls.join_group_call(chat_id, file_path)
1000 | except:
1001 | res.edit("Group call is not connected of I can't join it")
1002 | return
1003 | await res.edit("Generating Thumbnail.")
1004 | await generate_cover(requested_by, sname, ssingers, sduration, sthumb)
1005 | await res.delete()
1006 | m = await client.send_photo(
1007 | chat_id=message_.chat.id,
1008 | reply_markup=keyboard,
1009 | photo="final.png",
1010 | caption=f"Playing {sname} Via Jiosaavn",
1011 | )
1012 | os.remove("final.png")
1013 |
1014 |
1015 | @Client.on_callback_query(filters.regex(pattern=r"plll"))
1016 | async def lol_cb(b, cb):
1017 | global que
1018 |
1019 | cbd = cb.data.strip()
1020 | chat_id = cb.message.chat.id
1021 | typed_=cbd.split(None, 1)[1]
1022 | #useer_id = cb.message.reply_to_message.from_user.id
1023 | try:
1024 | x,query,useer_id = typed_.split("|")
1025 | except:
1026 | await cb.message.edit("Song Not Found")
1027 | return
1028 | useer_id = int(useer_id)
1029 | if cb.from_user.id != useer_id:
1030 | await cb.answer("You ain't the person who requested to play the song!", show_alert=True)
1031 | return
1032 | await cb.message.edit("Hang On... Player Starting")
1033 | x=int(x)
1034 | try:
1035 | useer_name = cb.message.reply_to_message.from_user.first_name
1036 | except:
1037 | useer_name = cb.message.from_user.first_name
1038 |
1039 | results = YoutubeSearch(query, max_results=5).to_dict()
1040 | resultss=results[x]["url_suffix"]
1041 | title=results[x]["title"][:40]
1042 | thumbnail=results[x]["thumbnails"][0]
1043 | duration=results[x]["duration"]
1044 | views=results[x]["views"]
1045 | url = f"https://youtube.com{resultss}"
1046 |
1047 | try:
1048 | duuration= round(duration / 60)
1049 | if duuration > DURATION_LIMIT:
1050 | await cb.message.edit(f"Music longer than {DURATION_LIMIT}min are not allowed to play")
1051 | return
1052 | except:
1053 | pass
1054 | try:
1055 | thumb_name = f"thumb{title}.jpg"
1056 | thumb = requests.get(thumbnail, allow_redirects=True)
1057 | open(thumb_name, "wb").write(thumb.content)
1058 | except Exception as e:
1059 | print(e)
1060 | return
1061 | dlurl=url
1062 | dlurl=dlurl.replace("youtube","youtubepp")
1063 | keyboard = InlineKeyboardMarkup(
1064 | [
1065 | [
1066 | InlineKeyboardButton("⏹", "leave"),
1067 | InlineKeyboardButton("⏸", "puse"),
1068 | InlineKeyboardButton("▶️", "resume"),
1069 | InlineKeyboardButton("⏭", "skip"),
1070 | ],
1071 | [
1072 | InlineKeyboardButton("🎚 Playlist ", "playlist"),
1073 | InlineKeyboardButton("Settings ⚙", "menu"),
1074 | ],
1075 | [InlineKeyboardButton(" Close Menu 🎛", "cls")],
1076 | ]
1077 | )
1078 | requested_by = useer_name
1079 | await generate_cover(requested_by, title, views, duration, thumbnail)
1080 | file_path = await convert(youtube.download(url))
1081 | if chat_id in callsmusic.pytgcalls.active_calls:
1082 | position = await queues.put(chat_id, file=file_path)
1083 | qeue = que.get(chat_id)
1084 | s_name = title
1085 | try:
1086 | r_by = cb.message.reply_to_message.from_user
1087 | except:
1088 | r_by = cb.message.from_user
1089 | loc = file_path
1090 | appendable = [s_name, r_by, loc]
1091 | qeue.append(appendable)
1092 | await cb.message.delete()
1093 | await b.send_photo(chat_id,
1094 | photo="final.png",
1095 | caption=f"#⃣ Song requested by {r_by.mention} **queued** at position {position}!",
1096 | reply_markup=keyboard,
1097 | )
1098 | os.remove("final.png")
1099 |
1100 | else:
1101 | que[chat_id] = []
1102 | qeue = que.get(chat_id)
1103 | s_name = title
1104 | try:
1105 | r_by = cb.message.reply_to_message.from_user
1106 | except:
1107 | r_by = cb.message.from_user
1108 | loc = file_path
1109 | appendable = [s_name, r_by, loc]
1110 | qeue.append(appendable)
1111 |
1112 | callsmusic.pytgcalls.join_group_call(chat_id, file_path)
1113 | await cb.message.delete()
1114 | await b.send_photo(chat_id,
1115 | photo="final.png",
1116 | reply_markup=keyboard,
1117 | caption=f"**🎬Song:**{title}\n **⏳Duration:**{duration}\n **▶️Now Playing at** {cb.message.chat.title}",
1118 | )
1119 | os.remove("final.png")
1120 |
--------------------------------------------------------------------------------