├── .gitattributes ├── .gitignore ├── AniPlay ├── __init__.py ├── __main__.py └── plugins │ ├── AnimeDex.py │ ├── ErrorHandler.py │ ├── __init__.py │ ├── button.py │ ├── cb.py │ ├── cmd.py │ └── other.py ├── Dockerfile ├── LICENSE ├── README.md ├── config.py └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.session-journal 3 | *.session -------------------------------------------------------------------------------- /AniPlay/__init__.py: -------------------------------------------------------------------------------- 1 | from config import * 2 | from pyrogram.client import Client 3 | 4 | app = Client( 5 | "AniPlay", 6 | api_id=int(API_ID), 7 | api_hash=API_HASH, 8 | bot_token=TOKEN 9 | ) -------------------------------------------------------------------------------- /AniPlay/__main__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import asyncio 3 | from AniPlay import app 4 | from pyrogram import idle 5 | from AniPlay.plugins import ALL_MODULES 6 | 7 | loop = asyncio.get_event_loop() 8 | 9 | 10 | async def init(): 11 | for module in ALL_MODULES: 12 | importlib.import_module("AniPlay.plugins." + module) 13 | print("[INFO]: Imported Modules Successfully") 14 | 15 | await app.start() 16 | print("[INFO]: Bot Started") 17 | await idle() 18 | print("[INFO]: BOT STOPPED") 19 | await app.stop() 20 | for task in asyncio.all_tasks(): 21 | task.cancel() 22 | 23 | 24 | if __name__ == "__main__": 25 | loop.run_until_complete(init()) 26 | -------------------------------------------------------------------------------- /AniPlay/plugins/AnimeDex.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import urllib.parse 3 | from AniPlay.plugins.other import emoji 4 | 5 | 6 | class AnimeDex: 7 | def __init__(self) -> None: 8 | pass 9 | 10 | def search(query): 11 | url = "https://api.anime-dex.workers.dev/search/" + str( 12 | urllib.parse.quote(query) 13 | ) 14 | data = requests.get(url).json()["results"] 15 | return data 16 | 17 | def anime(id): 18 | data = requests.get("https://api.anime-dex.workers.dev/anime/" + id).json()[ 19 | "results" 20 | ] 21 | if data["source"] != "gogoanime": 22 | return 23 | 24 | title = data["name"] 25 | text = f"{emoji()} **{title}**\n" 26 | img = data["image"] 27 | 28 | for i, j in data.items(): 29 | if i not in ["name", "image", "id", "plot_summary", "source", "episodes"]: 30 | text += ( 31 | "\n**" + i.title().strip() + " :** " + j.strip().replace("\n", " ") 32 | ) 33 | 34 | text += "\n**Episodes :** " + str(len(data["episodes"])) 35 | 36 | return img, text, data["episodes"] 37 | 38 | def episode(id): 39 | data = requests.get("https://api.anime-dex.workers.dev/episode/" + id).json()[ 40 | "results" 41 | ] 42 | text = data["name"] 43 | surl = [ 44 | ("Stream 1", data["stream"]["sources"][0]["file"]), 45 | ("Stream 2", data["stream"]["sources_bk"][0]["file"]), 46 | ] 47 | murl = data["servers"].items() 48 | 49 | return text, surl, murl 50 | 51 | def download(id): 52 | data = requests.get("https://api.anime-dex.workers.dev/download/" + id).json()[ 53 | "results" 54 | ] 55 | return data 56 | -------------------------------------------------------------------------------- /AniPlay/plugins/ErrorHandler.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import Message, CallbackQuery 2 | 3 | 4 | def CMDErrorHandler(func): 5 | async def new_func(_, message: Message): 6 | try: 7 | return await func(_, message) 8 | except Exception as e: 9 | print(message.from_user.id, str(e)) 10 | try: 11 | await message.reply_text( 12 | "Something went wrong.\n\nReport @TechZBots_Support" 13 | ) 14 | except: 15 | pass 16 | return 17 | 18 | return new_func 19 | 20 | 21 | def CBErrorHandler(func): 22 | async def new_func(_, query: CallbackQuery): 23 | try: 24 | return await func(_, query) 25 | except Exception as e: 26 | print(query.from_user.id, str(e)) 27 | try: 28 | await query.message.edit( 29 | "Something went wrong.\n\nReport @TechZBots_Support" 30 | ) 31 | except: 32 | pass 33 | return 34 | 35 | return new_func 36 | -------------------------------------------------------------------------------- /AniPlay/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | import glob 2 | from os.path import basename, dirname, isfile 3 | 4 | 5 | def __list_all_modules(): 6 | mod_paths = glob.glob(dirname(__file__) + "/*.py") 7 | all_modules = [ 8 | basename(f)[:-3] 9 | for f in mod_paths 10 | if isfile(f) 11 | and f.endswith(".py") 12 | and not f.endswith("__init__.py") 13 | and not f.endswith("__main__.py") 14 | ] 15 | 16 | return all_modules 17 | 18 | 19 | print("[INFO]: IMPORTING MODULES") 20 | ALL_MODULES = sorted(__list_all_modules()) 21 | __all__ = ALL_MODULES + ["ALL_MODULES"] 22 | -------------------------------------------------------------------------------- /AniPlay/plugins/button.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 2 | import random 3 | from string import hexdigits 4 | 5 | cache = dict() 6 | 7 | 8 | def get_hash(data, back): 9 | while True: 10 | hash = "".join(random.choices(hexdigits, k=10)) 11 | if not cache.get(hash): 12 | cache[hash] = (data, back) 13 | break 14 | return hash 15 | 16 | 17 | def get_hash_btn(data: None, hash: None): 18 | if hash: 19 | cache[hash] = data 20 | return hash 21 | 22 | if not data: 23 | while True: 24 | hash = "".join(random.choices(hexdigits, k=10)) 25 | if not cache.get(hash): 26 | cache[hash] = "" 27 | break 28 | else: 29 | while True: 30 | hash = "".join(random.choices(hexdigits, k=10)) 31 | if not cache.get(hash): 32 | cache[hash] = data 33 | break 34 | return hash 35 | 36 | 37 | def get_hash_anime(data): 38 | while True: 39 | hash = "".join(random.choices(hexdigits, k=10)) 40 | if not cache.get(hash): 41 | cache[hash] = data 42 | break 43 | return hash 44 | 45 | 46 | class BTN: 47 | def searchCMD(id, data, back): 48 | temp = [] 49 | pos = 1 50 | x = [] 51 | 52 | for i in data: 53 | cb = f"AnimeS {id} " + get_hash(i["id"], back) 54 | temp.append([InlineKeyboardButton(text=i["title"], callback_data=cb)]) 55 | pos = len(temp) 56 | hash = get_hash_btn(None, None) 57 | 58 | if len(temp) > 10: 59 | b_parts = [] 60 | x = 0 61 | page = 0 62 | 63 | while pos > 10: 64 | t = temp[x : x + 10] 65 | 66 | if len(t) == 0: 67 | break 68 | b_parts.append(t) 69 | x += 10 70 | pos -= 10 71 | 72 | if page == 0: 73 | b_parts[page].append( 74 | [ 75 | InlineKeyboardButton( 76 | text="Next ⫸", 77 | callback_data=f"switch_anime {id} {hash} 1", 78 | ) 79 | ] 80 | ) 81 | else: 82 | b_parts[page].append( 83 | [ 84 | InlineKeyboardButton( 85 | text="⫷ Prev", 86 | callback_data=f"switch_anime {id} {hash} {page-1}", 87 | ), 88 | InlineKeyboardButton( 89 | text="Next ⫸", 90 | callback_data=f"switch_anime {id} {hash} {page+1}", 91 | ), 92 | ] 93 | ) 94 | 95 | page += 1 96 | if pos > 0: 97 | b_parts.append(temp[x:]) 98 | b_parts[page].append( 99 | [ 100 | InlineKeyboardButton( 101 | text="⫷ Prev", 102 | callback_data=f"switch_anime {id} {hash} {page-1}", 103 | ) 104 | ] 105 | ) 106 | 107 | hash = get_hash_btn((b_parts, back), hash) 108 | BTN = b_parts[0] 109 | return InlineKeyboardMarkup(BTN) 110 | else: 111 | return InlineKeyboardMarkup(temp) 112 | 113 | def AnimeS(id, data, back): 114 | temp = [] 115 | pos = 1 116 | x = [] 117 | 118 | for i in data: 119 | cb = f"episode {id} " + get_hash(i[1], back) 120 | if pos % 4 == 0: 121 | x.append(InlineKeyboardButton(text=i[0], callback_data=cb)) 122 | temp.append(x) 123 | x = [] 124 | else: 125 | x.append(InlineKeyboardButton(text=i[0], callback_data=cb)) 126 | pos += 1 127 | 128 | if len(x) != 0: 129 | temp.append(x) 130 | 131 | hash = get_hash_btn(None, None) 132 | 133 | if len(temp) > 23: 134 | b_parts = [] 135 | x = 0 136 | page = 0 137 | 138 | while pos > 23: 139 | t = temp[x : x + 23] 140 | 141 | if len(t) == 0: 142 | break 143 | b_parts.append(t) 144 | x += 23 145 | pos -= 23 146 | 147 | if page == 0: 148 | b_parts[page].append( 149 | [ 150 | InlineKeyboardButton( 151 | text="Back", callback_data=f"searchBACK {id} {back}" 152 | ), 153 | InlineKeyboardButton( 154 | text="Next ⫸", callback_data=f"switch_ep {id} {hash} 1" 155 | ), 156 | ] 157 | ) 158 | else: 159 | b_parts[page].append( 160 | [ 161 | InlineKeyboardButton( 162 | text="⫷ Prev", 163 | callback_data=f"switch_ep {id} {hash} {page-1}", 164 | ), 165 | InlineKeyboardButton( 166 | text="Next ⫸", 167 | callback_data=f"switch_ep {id} {hash} {page+1}", 168 | ), 169 | ] 170 | ) 171 | b_parts[page].append( 172 | [ 173 | InlineKeyboardButton( 174 | text="Back", callback_data=f"searchBACK {id} {back}" 175 | ) 176 | ] 177 | ) 178 | page += 1 179 | if pos > 0: 180 | b_parts.append(temp[x:]) 181 | b_parts[page].append( 182 | [ 183 | InlineKeyboardButton( 184 | text="⫷ Prev", 185 | callback_data=f"switch_ep {id} {hash} {page-1}", 186 | ), 187 | InlineKeyboardButton( 188 | text="Back", callback_data=f"searchBACK {id} {back}" 189 | ), 190 | ] 191 | ) 192 | 193 | hash = get_hash_btn((b_parts, back), hash) 194 | BTN = b_parts[0] 195 | return InlineKeyboardMarkup(BTN) 196 | else: 197 | temp.append( 198 | [ 199 | InlineKeyboardButton( 200 | text="Back", callback_data=f"searchBACK {id} {back}" 201 | ) 202 | ] 203 | ) 204 | return InlineKeyboardMarkup(temp) 205 | 206 | def episode(id, surl, murl, back, dl_open_cb): 207 | temp = [] 208 | pos = 1 209 | x = [] 210 | temp.append( 211 | [InlineKeyboardButton(text="⬇️ Direct Url ⬇️", callback_data="engSUB")] 212 | ) 213 | 214 | for i in surl: 215 | if pos % 3 == 0: 216 | x.append( 217 | InlineKeyboardButton( 218 | text=i[0], url="https://animedex.pages.dev/embed?url=" + i[1] 219 | ) 220 | ) 221 | temp.append(x) 222 | x = [] 223 | else: 224 | x.append( 225 | InlineKeyboardButton( 226 | text=i[0], url="https://animedex.pages.dev/embed?url=" + i[1] 227 | ) 228 | ) 229 | pos += 1 230 | 231 | if len(x) != 0: 232 | temp.append(x) 233 | 234 | x = [] 235 | pos = 1 236 | 237 | if len(murl) != 0: 238 | temp.append([InlineKeyboardButton(text="➖➖➖➖➖➖➖➖➖➖", callback_data="line")]) 239 | 240 | temp.append( 241 | [InlineKeyboardButton(text="⬇️ Mirror Url ⬇️", callback_data="engDUB")] 242 | ) 243 | 244 | for i in murl: 245 | if pos % 3 == 0: 246 | x.append(InlineKeyboardButton(text=i[0].title(), url=i[1])) 247 | temp.append(x) 248 | x = [] 249 | else: 250 | x.append(InlineKeyboardButton(text=i[0].title(), url=i[1])) 251 | pos += 1 252 | 253 | if len(x) != 0: 254 | temp.append(x) 255 | temp.append( 256 | [InlineKeyboardButton(text="📥 Download 📥", callback_data=dl_open_cb)] 257 | ) 258 | temp.append( 259 | [InlineKeyboardButton(text="Back", callback_data=f"AnimeS {id} {back}")] 260 | ) 261 | return InlineKeyboardMarkup(temp) 262 | 263 | def download(id, links, back): 264 | temp = [] 265 | 266 | for q, l in links.items(): 267 | q = q.split("x")[1] + "p" 268 | temp.append([InlineKeyboardButton(text=q, url=l)]) 269 | 270 | temp.append([InlineKeyboardButton(text="Back", callback_data=back)]) 271 | return InlineKeyboardMarkup(temp) 272 | -------------------------------------------------------------------------------- /AniPlay/plugins/cb.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 2 | from pyrogram.types import Message, CallbackQuery, InputMediaPhoto 3 | from pyrogram import filters 4 | from AniPlay import app 5 | from AniPlay.plugins.AnimeDex import AnimeDex 6 | from AniPlay.plugins.button import BTN, cache, get_hash 7 | from AniPlay.plugins.ErrorHandler import CBErrorHandler 8 | 9 | QUERY = "**Search Results:** `{}`" 10 | 11 | 12 | @app.on_callback_query(filters.regex("searchBACK")) 13 | @CBErrorHandler 14 | async def searchBACK(_, query: CallbackQuery): 15 | user = query.from_user.id 16 | 17 | _, id, hash = query.data.split(" ") 18 | 19 | if str(user) != id: 20 | return await query.answer("This Is Not Your Query...") 21 | 22 | url = cache.get(hash) 23 | 24 | if not url: 25 | await query.answer("Search Query Expired... Try Again") 26 | return await query.message.delete() 27 | 28 | await query.answer("Loading ...") 29 | data = AnimeDex.search(url[1]) 30 | button = BTN.searchCMD(user, data, url[1]) 31 | await query.message.edit( 32 | f"{QUERY.format(url[1])}\n\n© {query.from_user.mention}", reply_markup=button 33 | ) 34 | 35 | 36 | @app.on_callback_query(filters.regex("AnimeS")) 37 | @CBErrorHandler 38 | async def AnimeS(_, query: CallbackQuery): 39 | user = query.from_user.id 40 | 41 | _, id, hash = query.data.split(" ") 42 | 43 | if str(user) != id: 44 | return await query.answer("This Is Not Your Query...") 45 | 46 | anime = cache.get(hash) 47 | print(anime) 48 | 49 | if not anime: 50 | await query.answer("Search Query Expired... Try Again") 51 | return await query.message.delete() 52 | 53 | await query.answer("Loading ...") 54 | img, text, ep = AnimeDex.anime(anime[0]) 55 | 56 | text += "\n\n© " + query.from_user.mention 57 | button = BTN.AnimeS(id, ep, hash) 58 | 59 | if query.message.photo: 60 | await query.message.edit_media( 61 | media=InputMediaPhoto(img, caption=text), reply_markup=button 62 | ) 63 | else: 64 | try: 65 | await query.message.reply_to_message.reply_photo( 66 | photo=img, caption=text, reply_markup=button 67 | ) 68 | except: 69 | await query.message.reply_photo( 70 | photo=img, caption=text, reply_markup=button 71 | ) 72 | await query.message.delete() 73 | 74 | 75 | @app.on_callback_query(filters.regex("episode")) 76 | @CBErrorHandler 77 | async def episode(_, query: CallbackQuery): 78 | user = query.from_user.id 79 | dl_back_cb = query.data 80 | 81 | _, id, hash = query.data.split(" ") 82 | 83 | if str(user) != id: 84 | return await query.answer("This Is Not Your Query...") 85 | 86 | epid = cache.get(hash) 87 | 88 | if not epid: 89 | await query.answer("Search Query Expired... Try Again") 90 | return await query.message.delete() 91 | 92 | await query.answer("Loading ...") 93 | text, surl, murl = AnimeDex.episode(epid[0]) 94 | dl_hash = get_hash(epid[0], dl_back_cb) 95 | dl_open_cb = f"download {id} {dl_hash}" 96 | button = BTN.episode(id, surl, murl, epid[1], dl_open_cb) 97 | 98 | await query.message.edit( 99 | f"**{text}**\n\n© {query.from_user.mention}", reply_markup=button 100 | ) 101 | 102 | 103 | @app.on_callback_query(filters.regex("download")) 104 | @CBErrorHandler 105 | async def download(_, query: CallbackQuery): 106 | user = query.from_user.id 107 | 108 | _, id, hash = query.data.split(" ") 109 | 110 | if str(user) != id: 111 | return await query.answer("This Is Not Your Query...") 112 | 113 | data = cache.get(hash) 114 | 115 | if not data: 116 | await query.answer("Search Query Expired... Try Again") 117 | return await query.message.delete() 118 | 119 | await query.answer("Loading ...") 120 | links = AnimeDex.download(data[0]) 121 | text = data[0].replace("-", " ").title() 122 | button = BTN.download(id, links, data[1]) 123 | 124 | await query.message.edit( 125 | f"**{text}**\n\n© {query.from_user.mention}", reply_markup=button 126 | ) 127 | 128 | 129 | @app.on_callback_query(filters.regex("line")) 130 | async def liner(_, query: CallbackQuery): 131 | try: 132 | await query.answer() 133 | except: 134 | return 135 | 136 | 137 | @app.on_callback_query(filters.regex("engSUB")) 138 | async def engSub(_, query: CallbackQuery): 139 | try: 140 | await query.answer("Direct Stream Urls") 141 | except: 142 | return 143 | 144 | 145 | @app.on_callback_query(filters.regex("engDUB")) 146 | async def engDub(_, query: CallbackQuery): 147 | try: 148 | await query.answer("Mirror Stream Urls") 149 | except: 150 | return 151 | 152 | 153 | @app.on_callback_query(filters.regex("switch_ep")) 154 | @CBErrorHandler 155 | async def switch_ep(_, query: CallbackQuery): 156 | user = query.from_user.id 157 | 158 | _, id, hash, pos = query.data.split(" ") 159 | 160 | if str(user) != id: 161 | return await query.answer("This Is Not Your Query...") 162 | 163 | data = cache.get(hash) 164 | 165 | if not data: 166 | await query.answer("Search Query Expired... Try Again") 167 | return await query.message.delete() 168 | 169 | await query.answer("Loading ...") 170 | pos = int(pos) 171 | current = data[0][pos] 172 | await query.edit_message_reply_markup(reply_markup=InlineKeyboardMarkup(current)) 173 | 174 | 175 | @app.on_callback_query(filters.regex("switch_anime")) 176 | @CBErrorHandler 177 | async def switch_anime(_, query: CallbackQuery): 178 | user = query.from_user.id 179 | 180 | _, id, hash, pos = query.data.split(" ") 181 | 182 | if str(user) != id: 183 | return await query.answer("This Is Not Your Query...") 184 | 185 | data: list = cache.get(hash) 186 | 187 | if not data: 188 | await query.answer("Search Query Expired... Try Again") 189 | return await query.message.delete() 190 | 191 | await query.answer("Loading ...") 192 | 193 | pos = int(pos) 194 | current = data[0][pos] 195 | await query.edit_message_reply_markup(reply_markup=InlineKeyboardMarkup(current)) 196 | -------------------------------------------------------------------------------- /AniPlay/plugins/cmd.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import Message 2 | from pyrogram import filters 3 | from AniPlay import app 4 | from AniPlay.plugins.AnimeDex import AnimeDex 5 | from AniPlay.plugins.button import BTN 6 | from AniPlay.plugins.ErrorHandler import CMDErrorHandler 7 | 8 | 9 | @app.on_message(filters.command(["start", "ping", "help", "alive"])) 10 | @CMDErrorHandler 11 | async def start(_, message: Message): 12 | try: 13 | await message.reply_text( 14 | "Bot Is Online...\n\nSearch Animes Using /search or /s" 15 | ) 16 | except: 17 | return 18 | 19 | 20 | QUERY = "**Search Results:** `{}`" 21 | 22 | 23 | @app.on_message(filters.command(["search", "s"])) 24 | @CMDErrorHandler 25 | async def searchCMD(_, message: Message): 26 | try: 27 | user = message.from_user.id 28 | query = " ".join(message.command[1:]) 29 | if query == "": 30 | return await message.reply_text("Give me something to search ^_^") 31 | data = AnimeDex.search(query) 32 | button = BTN.searchCMD(user, data, query) 33 | await message.reply_text( 34 | f"{QUERY.format(query)}\n\n© {message.from_user.mention}", 35 | reply_markup=button, 36 | ) 37 | except Exception as e: 38 | print(e) 39 | try: 40 | return await message.reply_text( 41 | "**Anime Not Found...**\n\nProbably Incorrect Name, Try again" 42 | ) 43 | except: 44 | return 45 | 46 | 47 | @app.on_message(filters.command(["report"])) 48 | @CMDErrorHandler 49 | async def reportCMD(_, message: Message): 50 | await message.reply_text("Report Bugs Here: @TechZBots_Support") 51 | -------------------------------------------------------------------------------- /AniPlay/plugins/other.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def emoji(): 5 | emoji = '🖥📷🎥⛩⛺️🏠🧲🧿🎁🎈🧸💮🔆🔰♻️💠' 6 | return random.choice(emoji) 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | 3 | # Copy the app files to the container 4 | COPY . /app 5 | 6 | # Set the working directory 7 | WORKDIR /app 8 | 9 | # Install dependencies 10 | RUN pip install -U -r requirements.txt 11 | 12 | # Run the Python app 13 | CMD ["python", "-m","AniPlay"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 TechShreyash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 | 4 |