diff --git a/.gitignore b/.gitignore index ce5d8ae1e0..93417d457e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ test* *.mp3 *.webm *.webp +*.rdb *.mp4 *.tgs *.txt @@ -44,4 +45,6 @@ bin-release/ *.raw # fly.io configs -fly.toml \ No newline at end of file +fly.toml + +resources/webapp \ No newline at end of file diff --git a/assistant/__init__.py b/assistant/__init__.py index 2a2a775b4d..c7ee644745 100644 --- a/assistant/__init__.py +++ b/assistant/__init__.py @@ -13,8 +13,6 @@ from pyUltroid._misc import owner_and_sudos from pyUltroid._misc._assistant import asst_cmd, callback, in_pattern from pyUltroid.fns.helper import * -from pyUltroid.fns.tools import get_stored_file -from strings import get_languages, get_string OWNER_NAME = ultroid_bot.full_name OWNER_ID = ultroid_bot.uid diff --git a/assistant/callbackstuffs.py b/assistant/callbackstuffs.py index 15598fee2c..dbeb54a467 100644 --- a/assistant/callbackstuffs.py +++ b/assistant/callbackstuffs.py @@ -26,6 +26,7 @@ from telethon.utils import get_peer_id from pyUltroid.fns.helper import fast_download, progress +from strings import get_string from pyUltroid.fns.tools import Carbon, async_searcher, get_paste, telegraph_client from pyUltroid.startup.loader import Loader diff --git a/assistant/inlinestuff.py b/assistant/inlinestuff.py index ae097f119a..f0ed07d4df 100644 --- a/assistant/inlinestuff.py +++ b/assistant/inlinestuff.py @@ -621,3 +621,20 @@ async def inline_tl(ult): "Tʟ Sᴇᴀʀᴄʜ": "tl", } ) + +# --------------- Handle users who haven't started the assistant bot --------------- +@in_pattern("startbot") +async def start_bot(event): + result = await event.builder.article( + title="Start Assistant Bot to Continue", + text=( + "To create or manage your sticker pack, you need to start the assistant bot first.\n\n" + "Click the button below to start it." + ), + buttons=[ + [Button.url("Start Bot", f"https://t.me/{asst.me.username}")] + ] + ) + await event.answer([result], private=True, cache_time=0, gallery=False) + + diff --git a/assistant/localization.py b/assistant/localization.py index 9f1c18ab5b..2be4b0ec06 100644 --- a/assistant/localization.py +++ b/assistant/localization.py @@ -7,13 +7,12 @@ import re +from strings import get_languages, get_string from . import ( Button, ULTConfig, callback, get_back_button, - get_languages, - get_string, udB, ) diff --git a/install-termux b/install-termux index ee02e5b295..ad020fc1cc 100644 --- a/install-termux +++ b/install-termux @@ -9,7 +9,7 @@ if [ -d "resources" ] then echo "Current directory Identified.." else - apt install git -y + apt install git redis -y git clone https://github.com/TeamUltroid/Ultroid cd Ultroid fi diff --git a/plugins/__init__.py b/plugins/__init__.py index 82ef3187a4..8e9947728a 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -17,12 +17,13 @@ from pyUltroid import * from pyUltroid._misc._assistant import asst_cmd, callback, in_pattern from pyUltroid._misc._decorators import ultroid_cmd +from telethon.tl.types import Message from pyUltroid._misc._wrappers import eod, eor from pyUltroid.dB import DEVLIST, ULTROID_IMAGES from pyUltroid.fns.helper import * from pyUltroid.fns.misc import * from pyUltroid.fns.tools import * -from pyUltroid.startup._database import _BaseDatabase as Database +from pyUltroid.database.base import BaseDatabase as Database from pyUltroid.version import __version__, ultroid_version from strings import get_help, get_string from catbox import CatboxUploader @@ -75,19 +76,6 @@ def inline_pic(): -1001473548283, # SharingUserbot ] -KANGING_STR = [ - "Using Witchery to kang this sticker...", - "Plagiarising hehe...", - "Inviting this sticker over to my pack...", - "Kanging this sticker...", - "Hey that's a nice sticker!\nMind if I kang?!..", - "Hehe me stel ur stiker...", - "Ay look over there (☉。☉)!→\nWhile I kang this...", - "Roses are red violets are blue, kanging this sticker so my pack looks cool", - "Imprisoning this sticker...", - "Mr.Steal-Your-Sticker is stealing this sticker... ", -] - ATRA_COL = [ "DarkCyan", @@ -103,3 +91,41 @@ def inline_pic(): "Moccasin", "PowderBlue", ] + + + +def deEmojify(inputString: str) -> str: + """Remove emojis and other non-safe characters from string""" + EMOJI_PATTERN = re.compile( + "[" + "\U0001F1E0-\U0001F1FF" # flags (iOS) + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F600-\U0001F64F" # emoticons + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F700-\U0001F77F" # alchemical symbols + "\U0001F780-\U0001F7FF" # Geometric Shapes Extended + "\U0001F800-\U0001F8FF" # Supplemental Arrows-C + "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs + "\U0001FA00-\U0001FA6F" # Chess Symbols + "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A + "\U00002702-\U000027B0" # Dingbats + "]+",) + return re.sub(EMOJI_PATTERN, "", inputString) + + +async def something(e, msg, media, button, reply=True, chat=None): + if e.client._bot: + return await e.reply(msg, file=media, buttons=button) + num = len(STUFF) + 1 + STUFF.update({num: {"msg": msg, "media": media, "button": button}}) + try: + res = await e.client.inline_query(asst.me.username, f"stf{num}") + return await res[0].click( + chat or e.chat_id, + reply_to=bool(isinstance(e, Message) and reply), + hide_via=True, + silent=True, + ) + + except Exception as er: + LOGS.exception(er) diff --git a/plugins/_chatactions.py b/plugins/_chatactions.py deleted file mode 100644 index 3ae1c90111..0000000000 --- a/plugins/_chatactions.py +++ /dev/null @@ -1,253 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -import asyncio - -from telethon import events -from telethon.errors.rpcerrorlist import UserNotParticipantError -from telethon.tl.functions.channels import GetParticipantRequest -from telethon.utils import get_display_name - -from pyUltroid.dB import stickers -from pyUltroid.dB.echo_db import check_echo -from pyUltroid.dB.forcesub_db import get_forcesetting -from pyUltroid.dB.gban_mute_db import is_gbanned -from pyUltroid.dB.greetings_db import get_goodbye, get_welcome, must_thank -from pyUltroid.dB.nsfw_db import is_profan -from pyUltroid.fns.helper import inline_mention -from pyUltroid.fns.tools import async_searcher, create_tl_btn, get_chatbot_reply - -try: - from ProfanityDetector import detector -except ImportError: - detector = None -from . import LOG_CHANNEL, LOGS, asst, get_string, types, udB, ultroid_bot -from ._inline import something - - -@ultroid_bot.on(events.ChatAction()) -async def Function(event): - try: - await DummyHandler(event) - except Exception as er: - LOGS.exception(er) - - -async def DummyHandler(ult): - # clean chat actions - key = udB.get_key("CLEANCHAT") or [] - if ult.chat_id in key: - try: - await ult.delete() - except BaseException: - pass - - # thank members - if must_thank(ult.chat_id): - chat_count = (await ult.client.get_participants(ult.chat_id, limit=0)).total - if chat_count % 100 == 0: - stik_id = chat_count / 100 - 1 - sticker = stickers[stik_id] - await ult.respond(file=sticker) - # force subscribe - if ( - udB.get_key("FORCESUB") - and ((ult.user_joined or ult.user_added)) - and get_forcesetting(ult.chat_id) - ): - user = await ult.get_user() - if not user.bot: - joinchat = get_forcesetting(ult.chat_id) - try: - await ultroid_bot(GetParticipantRequest(int(joinchat), user.id)) - except UserNotParticipantError: - await ultroid_bot.edit_permissions( - ult.chat_id, user.id, send_messages=False - ) - res = await ultroid_bot.inline_query( - asst.me.username, f"fsub {user.id}_{joinchat}" - ) - await res[0].click(ult.chat_id, reply_to=ult.action_message.id) - - if ult.user_joined or ult.added_by: - user = await ult.get_user() - chat = await ult.get_chat() - # gbans and @UltroidBans checks - if udB.get_key("ULTROID_BANS"): - try: - is_banned = await async_searcher( - "https://bans.ultroid.tech/api/status", - json={"userId": user.id}, - post=True, - re_json=True, - ) - if is_banned["is_banned"]: - await ult.client.edit_permissions( - chat.id, - user.id, - view_messages=False, - ) - await ult.respond( - f'**@UltroidBans:** Banned user detected and banned!\n`{str(is_banned)}`.\nBan reason: {is_banned["reason"]}', - ) - - except BaseException: - pass - reason = is_gbanned(user.id) - if reason and chat.admin_rights: - try: - await ult.client.edit_permissions( - chat.id, - user.id, - view_messages=False, - ) - gban_watch = get_string("can_1").format(inline_mention(user), reason) - await ult.reply(gban_watch) - except Exception as er: - LOGS.exception(er) - - # greetings - elif get_welcome(ult.chat_id): - user = await ult.get_user() - chat = await ult.get_chat() - title = chat.title or "this chat" - count = ( - chat.participants_count - or (await ult.client.get_participants(chat, limit=0)).total - ) - mention = inline_mention(user) - name = user.first_name - fullname = get_display_name(user) - uu = user.username - username = f"@{uu}" if uu else mention - wel = get_welcome(ult.chat_id) - msgg = wel["welcome"] - med = wel["media"] or None - userid = user.id - msg = None - if msgg: - msg = msgg.format( - mention=mention, - group=title, - count=count, - name=name, - fullname=fullname, - username=username, - userid=userid, - ) - if wel.get("button"): - btn = create_tl_btn(wel["button"]) - await something(ult, msg, med, btn) - elif msg: - send = await ult.reply( - msg, - file=med, - ) - await asyncio.sleep(150) - await send.delete() - else: - await ult.reply(file=med) - elif (ult.user_left or ult.user_kicked) and get_goodbye(ult.chat_id): - user = await ult.get_user() - chat = await ult.get_chat() - title = chat.title or "this chat" - count = ( - chat.participants_count - or (await ult.client.get_participants(chat, limit=0)).total - ) - mention = inline_mention(user) - name = user.first_name - fullname = get_display_name(user) - uu = user.username - username = f"@{uu}" if uu else mention - wel = get_goodbye(ult.chat_id) - msgg = wel["goodbye"] - med = wel["media"] - userid = user.id - msg = None - if msgg: - msg = msgg.format( - mention=mention, - group=title, - count=count, - name=name, - fullname=fullname, - username=username, - userid=userid, - ) - if wel.get("button"): - btn = create_tl_btn(wel["button"]) - await something(ult, msg, med, btn) - elif msg: - send = await ult.reply( - msg, - file=med, - ) - await asyncio.sleep(150) - await send.delete() - else: - await ult.reply(file=med) - - -@ultroid_bot.on(events.NewMessage(incoming=True)) -async def chatBot_replies(e): - sender = await e.get_sender() - if not isinstance(sender, types.User) or sender.bot: - return - if check_echo(e.chat_id, e.sender_id): - try: - await e.respond(e.message) - except Exception as er: - LOGS.exception(er) - key = udB.get_key("CHATBOT_USERS") or {} - if e.text and key.get(e.chat_id) and sender.id in key[e.chat_id]: - msg = await get_chatbot_reply(e.message.message) - if msg: - sleep = udB.get_key("CHATBOT_SLEEP") or 1.5 - await asyncio.sleep(sleep) - await e.reply(msg) - chat = await e.get_chat() - if e.is_group and sender.username: - await uname_stuff(e.sender_id, sender.username, sender.first_name) - elif e.is_private and chat.username: - await uname_stuff(e.sender_id, chat.username, chat.first_name) - if detector and is_profan(e.chat_id) and e.text: - x, y = detector(e.text) - if y: - await e.delete() - - -@ultroid_bot.on(events.Raw(types.UpdateUserName)) -async def uname_change(e): - await uname_stuff(e.user_id, e.usernames[0] if e.usernames else None, e.first_name) - - -async def uname_stuff(id, uname, name): - if udB.get_key("USERNAME_LOG"): - old_ = udB.get_key("USERNAME_DB") or {} - old = old_.get(id) - # Ignore Name Logs - if old and old == uname: - return - if old and uname: - await asst.send_message( - LOG_CHANNEL, - get_string("can_2").format(old, uname), - ) - elif old: - await asst.send_message( - LOG_CHANNEL, - get_string("can_3").format(f"[{name}](tg://user?id={id})", old), - ) - elif uname: - await asst.send_message( - LOG_CHANNEL, - get_string("can_4").format(f"[{name}](tg://user?id={id})", uname), - ) - - old_[id] = uname - udB.set_key("USERNAME_DB", old_) diff --git a/plugins/_help.py b/plugins/_help.py index 1f7d4e6e52..adf217b881 100644 --- a/plugins/_help.py +++ b/plugins/_help.py @@ -14,43 +14,53 @@ from pyUltroid.dB._core import HELP, LIST from pyUltroid.fns.tools import cmd_regex_replace +from pyUltroid.configs import Var from . import HNDLR, LOGS, OWNER_NAME, asst, get_string, inline_pic, udB, ultroid_cmd -_main_help_menu = [ - [ - Button.inline(get_string("help_4"), data="uh_Official_"), - Button.inline(get_string("help_5"), data="uh_Addons_"), - ], - [ - Button.inline(get_string("help_6"), data="uh_VCBot_"), - Button.inline(get_string("help_7"), data="inlone"), - ], - [ - Button.inline(get_string("help_8"), data="ownr"), - Button.url( - get_string("help_9"), url=f"https://t.me/{asst.me.username}?start=set" - ), - ], - [Button.inline(get_string("help_10"), data="close")], -] +def get_help_menu(): + return [ + [ + Button.inline(get_string("help_4"), data="uh_Official_"), + Button.inline(get_string("help_5"), data="uh_Addons_"), + ], + [ + Button.inline(get_string("help_6"), data="uh_VCBot_"), + Button.inline(get_string("help_7"), data="inlone"), + ], + [ + Button.inline(get_string("help_8"), data="ownr"), + Button.url( + get_string("help_9"), url=f"https://t.me/{asst.me.username}?start=set" + ), + ], + [Button.inline(get_string("help_10"), data="close")], + ] @ultroid_cmd(pattern="help( (.*)|$)") async def _help(ult): plug = ult.pattern_match.group(1).strip() chat = await ult.get_chat() + _main_help_menu = get_help_menu() if plug: try: + # For Addons, allow lookup by base name (strip last two hashes) + plug_lookup = plug + if HELP.get("Addons") and plug not in HELP["Addons"]: + for addon_name in HELP["Addons"]: + if addon_name.count("_") >= 2 and plug == addon_name.rsplit("_", 2)[0]: + plug_lookup = addon_name + break if plug in HELP["Official"]: output = f"**Plugin** - `{plug}`\n" for i in HELP["Official"][plug]: output += i output += "\n© @TeamUltroid" await ult.eor(output) - elif HELP.get("Addons") and plug in HELP["Addons"]: - output = f"**Plugin** - `{plug}`\n" - for i in HELP["Addons"][plug]: + elif HELP.get("Addons") and plug_lookup in HELP["Addons"]: + output = f"**Plugin** - `{plug_lookup}`\n" + for i in HELP["Addons"][plug_lookup]: output += i output += "\n© @TeamUltroid" await ult.eor(output) @@ -63,7 +73,14 @@ async def _help(ult): else: try: x = get_string("help_11").format(plug) - for d in LIST[plug]: + # For Addons, allow lookup by base name in LIST as well + plug_list_lookup = plug + if plug not in LIST and HELP.get("Addons"): + for addon_name in LIST: + if addon_name.count("_") >= 2 and plug == addon_name.rsplit("_", 2)[0]: + plug_list_lookup = addon_name + break + for d in LIST[plug_list_lookup]: x += HNDLR + d x += "\n" x += "\n© @TeamUltroid" @@ -85,8 +102,13 @@ async def _help(ult): text = f"`{plug}` is not a valid plugin!" best_match = None for _ in compare_strings: - if plug in _ and not _.startswith("_"): - best_match = _ + cmp = _ + # For Addons, strip last two hashes for matching + if HELP.get("Addons") and _ in HELP["Addons"]: + if _.count("_") >= 2: + cmp = _.rsplit("_", 2)[0] + if plug in cmp and not cmp.startswith("_"): + best_match = cmp break if best_match: text += f"\nDid you mean `{best_match}`?" diff --git a/plugins/_inline.py b/plugins/_inline.py index 211e723ffe..92ac9f49cc 100644 --- a/plugins/_inline.py +++ b/plugins/_inline.py @@ -31,8 +31,10 @@ split_list, start_time, udB, + STUFF, + something, ) -from ._help import _main_help_menu +from ._help import get_help_menu # ================================================# @@ -57,6 +59,7 @@ # --------------------BUTTONS--------------------# +DELIM = "$$$" @in_pattern(owner=True, func=lambda x: not x.text) async def inline_alive(o): @@ -103,11 +106,11 @@ async def inline_handler(event): file=inline_pic(), link_preview=False, text=text, - buttons=_main_help_menu, + buttons=get_help_menu(), ) else: result = await event.builder.article( - title="Ultroid Help Menu", text=text, buttons=_main_help_menu + title="Ultroid Help Menu", text=text, buttons=get_help_menu() ) await event.answer([result], private=True, cache_time=300, gallery=True) @@ -167,7 +170,11 @@ async def setting(event): @callback(re.compile("uh_(.*)"), owner=True) async def help_func(ult): - key, count = ult.data_match.group(1).decode("utf-8").split("_") + data = ult.data_match.group(1).decode("utf-8") + if DELIM in data: + key, count = data.split(DELIM) + else: + key, count = data.split("_") if key == "VCBot" and HELP.get("VCBot") is None: return await ult.answer(get_string("help_12"), alert=True) elif key == "Addons" and HELP.get("Addons") is None: @@ -181,10 +188,14 @@ async def help_func(ult): @callback(re.compile("uplugin_(.*)"), owner=True) async def uptd_plugin(event): - key, file = event.data_match.group(1).decode("utf-8").split("_") + data = event.data_match.group(1).decode("utf-8") + if DELIM in data: + key, file = data.split(DELIM, 1) + else: + key, file = data.split("_", 1) index = None if "|" in file: - file, index = file.split("|") + file, index = file.split("|", 1) key_ = HELP.get(key, []) hel_p = f"Plugin Name - `{file}`\n" help_ = "" @@ -202,23 +213,23 @@ async def uptd_plugin(event): help_ += "\n© @TeamUltroid" buttons = [] if inline_pic(): - data = f"sndplug_{key}_{file}" + data_btn = f"sndplug{DELIM}{key}{DELIM}{file}" if index is not None: - data += f"|{index}" + data_btn += f"|{index}" buttons.append( [ Button.inline( "« Sᴇɴᴅ Pʟᴜɢɪɴ »", - data=data, + data=data_btn, ) ] ) - data = f"uh_{key}_" + data_back = f"uh_{key}{DELIM}" if index is not None: - data += f"|{index}" + data_back += f"|{index}" buttons.append( [ - Button.inline("« Bᴀᴄᴋ", data=data), + Button.inline("« Bᴀᴄᴋ", data=data_back), ] ) try: @@ -309,7 +320,7 @@ async def opner(event): len(HELP.get("Addons", [])), len(z), ), - buttons=_main_help_menu, + buttons=get_help_menu(), link_preview=False, ) @@ -327,10 +338,14 @@ def page_num(index, key): cols = udB.get_key("HELP_COLUMNS") or 2 loaded = HELP.get(key, []) emoji = udB.get_key("EMOJI_IN_HELP") or "✘" - List = [ - Button.inline(f"{emoji} {x} {emoji}", data=f"uplugin_{key}_{x}|{index}") - for x in sorted(loaded) - ] + List = [] + for x in sorted(loaded): + if key == "Addons": + # Efficiently remove last two hashes using rsplit + display_name = x.rsplit("_", 2)[0] if x.count("_") >= 2 else x + else: + display_name = x + List.append(Button.inline(f"{emoji} {display_name} {emoji}", data=f"uplugin_{key}{DELIM}{x}|{index}")) all_ = split_list(List, cols) fl_ = split_list(all_, rows) try: @@ -341,28 +356,17 @@ def page_num(index, key): if index == 0 and len(fl_) == 1: new_.append([Button.inline("« Bᴀᴄᴋ »", data="open")]) else: - new_.append( - [ - Button.inline( - "« Pʀᴇᴠɪᴏᴜs", - data=f"uh_{key}_{index-1}", - ), - Button.inline("« Bᴀᴄᴋ »", data="open"), - Button.inline( - "Nᴇxᴛ »", - data=f"uh_{key}_{index+1}", - ), - ] - ) + new_.append([ + Button.inline("« Pʀᴇᴠɪᴏᴜs", data=f"uh_{key}{DELIM}{index-1}"), + Button.inline("« Bᴀᴄᴋ »", data="open"), + Button.inline("Nᴇxᴛ »", data=f"uh_{key}{DELIM}{index+1}"), + ]) return new_ # --------------------------------------------------------------------------------- # -STUFF = {} - - @in_pattern("stf(.*)", owner=True) async def ibuild(e): n = e.pattern_match.group(1).strip() @@ -436,20 +440,3 @@ async def ibuild(e): ] await e.answer(result) - -async def something(e, msg, media, button, reply=True, chat=None): - if e.client._bot: - return await e.reply(msg, file=media, buttons=button) - num = len(STUFF) + 1 - STUFF.update({num: {"msg": msg, "media": media, "button": button}}) - try: - res = await e.client.inline_query(asst.me.username, f"stf{num}") - return await res[0].click( - chat or e.chat_id, - reply_to=bool(isinstance(e, Message) and reply), - hide_via=True, - silent=True, - ) - - except Exception as er: - LOGS.exception(er) diff --git a/plugins/_userlogs.py b/plugins/_userlogs.py deleted file mode 100644 index 876718001f..0000000000 --- a/plugins/_userlogs.py +++ /dev/null @@ -1,299 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -import os -import re - -from telethon.errors.rpcerrorlist import ( - ChannelPrivateError, - ChatWriteForbiddenError, - MediaCaptionTooLongError, - MediaEmptyError, - MessageTooLongError, - PeerIdInvalidError, - UserNotParticipantError, -) -from telethon.tl.types import MessageEntityMention, MessageEntityMentionName, User -from telethon.utils import get_display_name - -from pyUltroid.dB.botchat_db import tag_add, who_tag - -from . import ( - LOG_CHANNEL, - LOGS, - Button, - asst, - callback, - events, - get_string, - inline_mention, - udB, - ultroid_bot, -) - -CACHE_SPAM = {} -TAG_EDITS = {} - - -@ultroid_bot.on( - events.NewMessage( - incoming=True, - func=lambda e: (e.mentioned), - ), -) -async def all_messages_catcher(e): - x = await e.get_sender() - if isinstance(x, User) and (x.bot or x.verified): - return - if not udB.get_key("TAG_LOG"): - return - NEEDTOLOG = udB.get_key("TAG_LOG") - if e.chat_id == NEEDTOLOG: - return - buttons = await parse_buttons(e) - try: - sent = await asst.send_message(NEEDTOLOG, e.message, buttons=buttons) - if TAG_EDITS.get(e.chat_id): - TAG_EDITS[e.chat_id].update({e.id: {"id": sent.id, "msg": e}}) - else: - TAG_EDITS.update({e.chat_id: {e.id: {"id": sent.id, "msg": e}}}) - tag_add(sent.id, e.chat_id, e.id) - except MediaEmptyError as er: - LOGS.debug(f"handling {er}.") - try: - msg = await asst.get_messages(e.chat_id, ids=e.id) - sent = await asst.send_message(NEEDTOLOG, msg, buttons=buttons) - if TAG_EDITS.get(e.chat_id): - TAG_EDITS[e.chat_id].update({e.id: {"id": sent.id, "msg": e}}) - else: - TAG_EDITS.update({e.chat_id: {e.id: {"id": sent.id, "msg": e}}}) - tag_add(sent.id, e.chat_id, e.id) - except Exception as me: - if not isinstance(me, (PeerIdInvalidError, ValueError)): - LOGS.exception(me) - if e.photo or e.sticker or e.gif: - try: - media = await e.download_media() - sent = await asst.send_message( - NEEDTOLOG, e.message.text, file=media, buttons=buttons - ) - if TAG_EDITS.get(e.chat_id): - TAG_EDITS[e.chat_id].update({e.id: {"id": sent.id, "msg": e}}) - else: - TAG_EDITS.update({e.chat_id: {e.id: {"id": sent.id, "msg": e}}}) - return os.remove(media) - except Exception as er: - LOGS.exception(er) - await asst.send_message(NEEDTOLOG, get_string("com_4"), buttons=buttons) - except (PeerIdInvalidError, ValueError) as er: - LOGS.exception(er) - try: - CACHE_SPAM[NEEDTOLOG] - except KeyError: - await asst.send_message( - udB.get_key("LOG_CHANNEL"), get_string("userlogs_1") - ) - CACHE_SPAM.update({NEEDTOLOG: True}) - except ChatWriteForbiddenError: - try: - await asst.get_permissions(NEEDTOLOG, "me") - MSG = get_string("userlogs_4") - except UserNotParticipantError: - MSG = get_string("userlogs_2") - try: - CACHE_SPAM[NEEDTOLOG] - except KeyError: - await asst.send_message(LOG_CHANNEL, MSG) - CACHE_SPAM.update({NEEDTOLOG: True}) - except Exception as er: - LOGS.exception(er) - - -if udB.get_key("TAG_LOG"): - - @ultroid_bot.on(events.MessageEdited(func=lambda x: not x.out)) - async def upd_edits(event): - x = event.sender - if isinstance(x, User) and (x.bot or x.verified): - return - if event.chat_id not in TAG_EDITS: - if event.sender_id == udB.get_key("TAG_LOG"): - return - if event.is_private: - return - if entities := event.get_entities_text(): - is_self = False - username = event.client.me.username - if username: - username = username.lower() - for ent, text in entities: - if isinstance(ent, MessageEntityMention): - is_self = text[1:].lower() == username - elif isinstance(ent, MessageEntityMentionName): - is_self = ent.user_id == event.client.me.id - if is_self: - text = f"**#Edited & #Mentioned**\n\n{event.text}" - try: - sent = await asst.send_message( - udB.get_key("TAG_LOG"), - text, - buttons=await parse_buttons(event), - ) - except Exception as er: - return LOGS.exception(er) - if TAG_EDITS.get(event.chat_id): - TAG_EDITS[event.chat_id].update({event.id: {"id": sent.id}}) - else: - TAG_EDITS.update({event.chat_id: {event.id: {"id": sent.id}}}) - return - d_ = TAG_EDITS[event.chat_id] - if not d_.get(event.id): - return - d_ = d_[event.id] - if d_["msg"].text == event.text: - return - msg = None - if d_.get("count"): - d_["count"] += 1 - else: - msg = True - d_.update({"count": 1}) - if d_["count"] > 10: - return # some limit to take edits - try: - MSG = await asst.get_messages(udB.get_key("TAG_LOG"), ids=d_["id"]) - except Exception as er: - return LOGS.exception(er) - TEXT = MSG.text - if msg: - TEXT += "\n\n🖋 **Later Edited to !**" - strf = event.edit_date.strftime("%H:%M:%S") - if "\n" not in event.text: - TEXT += f"\n• `{strf}` : {event.text}" - else: - TEXT += f"\n• `{strf}` :\n-> {event.text}" - if d_["count"] == 10: - TEXT += "\n\n• __Only the first 10 Edits are shown.__" - try: - msg = await MSG.edit(TEXT, buttons=await parse_buttons(event)) - d_["msg"] = msg - except (MessageTooLongError, MediaCaptionTooLongError): - del TAG_EDITS[event.chat_id][event.id] - except Exception as er: - LOGS.exception(er) - - @ultroid_bot.on( - events.NewMessage( - outgoing=True, - chats=[udB.get_key("TAG_LOG")], - func=lambda e: e.reply_to, - ) - ) - async def idk(e): - id = e.reply_to_msg_id - chat, msg = who_tag(id) - if chat and msg: - try: - await ultroid_bot.send_message(chat, e.message, reply_to=msg) - except BaseException as er: - LOGS.exception(er) - - -# log for assistant/user joins/add - - -async def when_added_or_joined(event): - user = await event.get_user() - chat = await event.get_chat() - if not (user and user.is_self): - return - if getattr(chat, "username", None): - chat = f"[{chat.title}](https://t.me/{chat.username}/{event.action_message.id})" - else: - chat = f"[{chat.title}](https://t.me/c/{chat.id}/{event.action_message.id})" - key = "bot" if event.client._bot else "user" - buttons = Button.inline( - get_string("userlogs_3"), data=f"leave_ch_{event.chat_id}|{key}" - ) - if event.user_added: - tmp = event.added_by - text = f"#ADD_LOG\n\n{inline_mention(tmp)} just added {inline_mention(user)} to {chat}." - elif event.from_request: - text = f"#APPROVAL_LOG\n\n{inline_mention(user)} just got Chat Join Approval to {chat}." - else: - text = f"#JOIN_LOG\n\n{inline_mention(user)} just joined {chat}." - await asst.send_message(udB.get_key("LOG_CHANNEL"), text, buttons=buttons) - - -asst.add_event_handler( - when_added_or_joined, events.ChatAction(func=lambda x: x.user_added) -) -ultroid_bot.add_event_handler( - when_added_or_joined, - events.ChatAction(func=lambda x: x.user_added or x.user_joined), -) -_client = {"bot": asst, "user": ultroid_bot} - - -@callback( - re.compile( - "leave_ch_(.*)", - ), - from_users=[ultroid_bot.uid], -) -async def leave_ch_at(event): - cht = event.data_match.group(1).decode("UTF-8") - ch_id, client = cht.split("|") - try: - client = _client[client] - except KeyError: - return - try: - name = (await client.get_entity(int(ch_id))).title - await client.delete_dialog(int(ch_id)) - except UserNotParticipantError: - pass - except ChannelPrivateError: - return await event.edit( - "`[CANT_ACCESS_CHAT]` `Maybe already left or got banned.`" - ) - except Exception as er: - LOGS.exception(er) - return await event.answer(str(er)) - await event.edit(get_string("userlogs_5").format(name)) - - -@callback("do_nothing") -async def _(event): - await event.answer() - - -async def parse_buttons(event): - y, x = event.chat, event.sender - where_n, who_n = get_display_name(y), get_display_name(x) - where_l = event.message_link - buttons = [[Button.url(where_n, where_l)]] - if isinstance(x, User) and x.username: - try: - buttons.append( - [Button.mention(who_n, await asst.get_input_entity(x.username))] - ) - except Exception as er: - LOGS.exception(er) - buttons.append([Button.url(who_n, f"t.me/{x.username}")]) - elif getattr(x, "username"): - buttons.append([Button.url(who_n, f"t.me/{x.username}")]) - else: - buttons.append([Button.url(who_n, where_l)]) - replied = await event.get_reply_message() - if replied and replied.out: - button = Button.url("Replied to", replied.message_link) - if len(who_n) > 7: - buttons.append([button]) - else: - buttons[-1].append(button) - return buttons diff --git a/plugins/_wspr.py b/plugins/_wspr.py deleted file mode 100644 index 462a34e696..0000000000 --- a/plugins/_wspr.py +++ /dev/null @@ -1,204 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -import re - -from telethon import Button -from telethon.errors.rpcerrorlist import ( - BotInlineDisabledError, - BotResponseTimeoutError, - MessageNotModifiedError, -) -from telethon.tl import types -from telethon.tl.functions.users import GetFullUserRequest as gu - -from . import ( - HNDLR, - LOGS, - asst, - callback, - get_string, - in_pattern, - inline_mention, - ultroid_bot, - ultroid_cmd, -) - -buddhhu = {} - - -@ultroid_cmd( - pattern="wspr( (.*)|$)", -) -async def _(e): - if e.reply_to_msg_id: - okk = await e.get_reply_message() - put = f"@{okk.sender.username}" if okk.sender.username else okk.sender_id - else: - put = e.pattern_match.group(1).strip() - if put: - try: - results = await e.client.inline_query(asst.me.username, f"msg {put}") - except BotResponseTimeoutError: - return await e.eor( - get_string("help_2").format(HNDLR), - ) - except BotInlineDisabledError: - return await e.eor(get_string("help_3")) - await results[0].click(e.chat_id, reply_to=e.reply_to_msg_id, hide_via=True) - return await e.delete() - await e.eor(get_string("wspr_3")) - - -@in_pattern("wspr", owner=True) -async def _(e): - iuser = e.query.user_id - zzz = e.text.split(maxsplit=2) - try: - query = zzz[1] - if query.isdigit(): - query = int(query) - logi = await ultroid_bot.get_entity(query) - if not isinstance(logi, types.User): - raise ValueError("Invalid Username.") - except IndexError: - sur = await e.builder.article( - title="Give Username", - description="You Didn't Type Username or id.", - text="You Didn't Type Username or id.", - ) - return await e.answer([sur]) - except ValueError as er: - LOGS.exception(er) - sur = await e.builder.article( - title="User Not Found", - description="Make sure username or id is correct.", - text="Make sure username or id is correct.", - ) - return await e.answer([sur]) - try: - desc = zzz[2] - except IndexError: - sur = await e.builder.article( - title="Type ur msg", text="You Didn't Type Your Msg" - ) - return await e.answer([sur]) - button = [ - [ - Button.inline("Secret Msg", data=f"dd_{e.id}"), - Button.inline("Delete Msg", data=f"del_{e.id}"), - ], - [ - Button.switch_inline( - "New", query=f"wspr {logi.username or logi.id}", same_peer=True - ) - ], - ] - us = logi.username or logi.first_name - sur = await e.builder.article( - title=logi.first_name, - description=desc, - text=get_string("wspr_1").format(us), - buttons=button, - ) - buddhhu.update({e.id: [logi.id, iuser, desc]}) - await e.answer([sur]) - - -@in_pattern("msg", owner=True) -async def _(e): - zzz = e.text.split(maxsplit=1) - desc = "Touch me" - try: - query = zzz[1] - if query.isdigit(): - query = int(query) - logi = await ultroid_bot(gu(id=query)) - user = logi.users[0] - mention = inline_mention(user) - x = user.status - if isinstance(x, types.UserStatusOnline): - status = "Online" - elif isinstance(x, types.UserStatusOffline): - status = "Offline" - elif isinstance(x, types.UserStatusRecently): - status = "Last Seen Recently" - elif isinstance(x, types.UserStatusLastMonth): - status = "Last seen months ago" - elif isinstance(x, types.UserStatusLastWeek): - status = "Last seen weeks ago" - else: - status = "Can't Tell" - text = f"**Name:** `{user.first_name}`\n" - text += f"**Id:** `{user.id}`\n" - if user.username: - text += f"**Username:** `{user.username}`\n" - url = f"https://t.me/{user.username}" - else: - text += f"**Mention:** `{mention}`\n" - url = f"tg://user?id={user.id}" - text += f"**Status:** `{status}`\n" - text += f"**About:** `{logi.full_user.about}`" - button = [ - Button.url("Private", url=url), - Button.switch_inline( - "Secret msg", - query=f"wspr {query} Hello 👋", - same_peer=True, - ), - ] - sur = e.builder.article( - title=user.first_name, - description=desc, - text=text, - buttons=button, - ) - except IndexError: - sur = e.builder.article( - title="Give Username", - description="You Didn't Type Username or id.", - text="You Didn't Type Username or id.", - ) - except BaseException as er: - LOGS.exception(er) - name = get_string("wspr_4").format(query) - sur = e.builder.article( - title=name, - text=name, - ) - - await e.answer([sur]) - - -@callback( - re.compile( - "dd_(.*)", - ), -) -async def _(e): - ids = int(e.pattern_match.group(1).strip().decode("UTF-8")) - if buddhhu.get(ids): - if e.sender_id in buddhhu[ids]: - await e.answer(buddhhu[ids][-1], alert=True) - else: - await e.answer("Not For You", alert=True) - else: - await e.answer(get_string("wspr_2"), alert=True) - - -@callback(re.compile("del_(.*)")) -async def _(e): - ids = int(e.pattern_match.group(1).strip().decode("UTF-8")) - if buddhhu.get(ids): - if e.sender_id in buddhhu[ids]: - buddhhu.pop(ids) - try: - await e.edit(get_string("wspr_2")) - except MessageNotModifiedError: - pass - else: - await e.answer(get_string("wspr_5"), alert=True) diff --git a/plugins/afk.py b/plugins/afk.py deleted file mode 100644 index 74e06eb774..0000000000 --- a/plugins/afk.py +++ /dev/null @@ -1,165 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_afk") - - -import asyncio - -from telethon import events - -from pyUltroid.dB.afk_db import add_afk, del_afk, is_afk -from pyUltroid.dB.base import KeyManager - -from . import ( - LOG_CHANNEL, - NOSPAM_CHAT, - Redis, - asst, - get_string, - mediainfo, - udB, - ultroid_bot, - ultroid_cmd, - upload_file -) - -old_afk_msg = [] - -is_approved = KeyManager("PMPERMIT", cast=list).contains - - -@ultroid_cmd(pattern="afk( (.*)|$)", owner_only=True) -async def set_afk(event): - if event.client._bot or is_afk(): - return - text, media, media_type = None, None, None - if event.pattern_match.group(1).strip(): - text = event.text.split(maxsplit=1)[1] - reply = await event.get_reply_message() - if reply: - if reply.text and not text: - text = reply.text - if reply.media: - media_type = mediainfo(reply.media) - if media_type.startswith(("pic", "gif")): - file = await event.client.download_media(reply.media) - media = upload_file(file) - else: - media = reply.file.id - await event.eor("`Done`", time=2) - add_afk(text, media_type, media) - ultroid_bot.add_handler(remove_afk, events.NewMessage(outgoing=True)) - ultroid_bot.add_handler( - on_afk, - events.NewMessage( - incoming=True, func=lambda e: bool(e.mentioned or e.is_private) - ), - ) - msg1, msg2 = None, None - if text and media: - if "sticker" in media_type: - msg1 = await ultroid_bot.send_file(event.chat_id, file=media) - msg2 = await ultroid_bot.send_message( - event.chat_id, get_string("afk_5").format(text) - ) - else: - msg1 = await ultroid_bot.send_message( - event.chat_id, get_string("afk_5").format(text), file=media - ) - elif media: - if "sticker" in media_type: - msg1 = await ultroid_bot.send_file(event.chat_id, file=media) - msg2 = await ultroid_bot.send_message(event.chat_id, get_string("afk_6")) - else: - msg1 = await ultroid_bot.send_message( - event.chat_id, get_string("afk_6"), file=media - ) - elif text: - msg1 = await event.respond(get_string("afk_5").format(text)) - else: - msg1 = await event.respond(get_string("afk_6")) - old_afk_msg.append(msg1) - if msg2: - old_afk_msg.append(msg2) - return await asst.send_message(LOG_CHANNEL, msg2.text) - await asst.send_message(LOG_CHANNEL, msg1.text) - - -async def remove_afk(event): - if event.is_private and udB.get_key("PMSETTING") and not is_approved(event.chat_id): - return - elif "afk" in event.text.lower(): - return - elif event.chat_id in NOSPAM_CHAT: - return - if is_afk(): - _, _, _, afk_time = is_afk() - del_afk() - off = await event.reply(get_string("afk_1").format(afk_time)) - await asst.send_message(LOG_CHANNEL, get_string("afk_2").format(afk_time)) - for x in old_afk_msg: - try: - await x.delete() - except BaseException: - pass - await asyncio.sleep(10) - await off.delete() - - -async def on_afk(event): - if event.is_private and Redis("PMSETTING") and not is_approved(event.chat_id): - return - elif "afk" in event.text.lower(): - return - elif not is_afk(): - return - if event.chat_id in NOSPAM_CHAT: - return - sender = await event.get_sender() - if sender.bot or sender.verified: - return - text, media_type, media, afk_time = is_afk() - msg1, msg2 = None, None - if text and media: - if "sticker" in media_type: - msg1 = await event.reply(file=media) - msg2 = await event.reply(get_string("afk_3").format(afk_time, text)) - else: - msg1 = await event.reply( - get_string("afk_3").format(afk_time, text), file=media - ) - elif media: - if "sticker" in media_type: - msg1 = await event.reply(file=media) - msg2 = await event.reply(get_string("afk_4").format(afk_time)) - else: - msg1 = await event.reply(get_string("afk_4").format(afk_time), file=media) - elif text: - msg1 = await event.reply(get_string("afk_3").format(afk_time, text)) - else: - msg1 = await event.reply(get_string("afk_4").format(afk_time)) - for x in old_afk_msg: - try: - await x.delete() - except BaseException: - pass - old_afk_msg.append(msg1) - if msg2: - old_afk_msg.append(msg2) - - -if udB.get_key("AFK_DB"): - ultroid_bot.add_handler(remove_afk, events.NewMessage(outgoing=True)) - ultroid_bot.add_handler( - on_afk, - events.NewMessage( - incoming=True, func=lambda e: bool(e.mentioned or e.is_private) - ), - ) diff --git a/plugins/aiwrapper.py b/plugins/aiwrapper.py deleted file mode 100644 index ee63749f58..0000000000 --- a/plugins/aiwrapper.py +++ /dev/null @@ -1,445 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -""" -✘ Commands Available - - -• `{i}gemini ` - Get response from Google Gemini. - -• `{i}antr ` - Get response from Anthropic Claude. - -• `{i}gpt ` - Get response from OpenAI GPT. - -• `{i}deepseek ` - Get response from DeepSeek AI. - -Set custom models using: - • OPENAI_MODEL: default: gpt-4o-mini - • ANTHROPIC_MODEL: claude-3-opus-20240229 - • GEMINI_MODEL: gemini-1.5-flash - • DEEPSEEK_MODEL: deepseek-chat -""" - -import json -from . import LOGS, eor, get_string, udB, ultroid_cmd, async_searcher -import aiohttp -import asyncio - - -ENDPOINTS = { - "gpt": "https://api.openai.com/v1/chat/completions", - "antr": "https://api.anthropic.com/v1/messages", - "gemini": "https://generativelanguage.googleapis.com/v1beta/chat/completions", - "deepseek": "https://api.deepseek.com/chat/completions" -} - -DEFAULT_MODELS = { - "gpt": "gpt-4o-mini", - "antr": "claude-3-opus-20240229", - "gemini": "gemini-1.5-flash", - "deepseek": "deepseek-chat" -} - - -def get_model(provider): - """Get model name from database or use default""" - model_keys = { - "gpt": "OPENAI_MODEL", - "antr": "ANTHROPIC_MODEL", - "gemini": "GEMINI_MODEL", - "deepseek": "DEEPSEEK_MODEL" - } - return udB.get_key(model_keys[provider]) or DEFAULT_MODELS[provider] - - -async def stream_response(msg, text): - """Stream response by editing message""" - current = "" - # Split into chunks of ~100 characters at word boundaries - words = text.split() - chunks = [] - current_chunk = [] - - for word in words: - current_chunk.append(word) - if len(" ".join(current_chunk)) > 100: - chunks.append(" ".join(current_chunk[:-1])) - current_chunk = [word] - if current_chunk: - chunks.append(" ".join(current_chunk)) - - for chunk in chunks: - current += chunk + " " - try: - await msg.edit(current) - except Exception: - pass - await asyncio.sleep(0.5) - return current - - -async def get_ai_response(provider, prompt, api_key, stream=False): - """Get response from AI provider""" - try: - headers = {"Content-Type": "application/json"} - model = get_model(provider) - - if provider == "gpt": - headers["Authorization"] = f"Bearer {api_key}" - data = { - "model": model, - "messages": [{"role": "user", "content": prompt}], - "stream": stream - } - if not stream: - response = await async_searcher( - ENDPOINTS[provider], - headers=headers, - post=True, - json=data, - re_json=True - ) - yield response["choices"][0]["message"]["content"] - return - - async with aiohttp.ClientSession() as session: - async with session.post( - ENDPOINTS[provider], - headers=headers, - json=data - ) as resp: - async for line in resp.content: - if line: - try: - json_line = json.loads(line.decode('utf-8').strip().strip('data:').strip()) - if 'choices' in json_line and json_line['choices']: - content = json_line['choices'][0].get('delta', {}).get('content', '') - if content: - yield content - except Exception: - continue - - elif provider == "antr": - headers["x-api-key"] = api_key - headers["anthropic-version"] = "2023-06-01" - data = { - "model": model, - "messages": [{"role": "user", "content": prompt}], - "stream": stream - } - if not stream: - response = await async_searcher( - ENDPOINTS[provider], - headers=headers, - post=True, - json=data, - re_json=True - ) - yield response["content"][0]["text"] - return - - async with aiohttp.ClientSession() as session: - async with session.post( - ENDPOINTS[provider], - headers=headers, - json=data - ) as resp: - async for line in resp.content: - if line: - try: - json_line = json.loads(line.decode('utf-8').strip()) - if 'content' in json_line: - content = json_line['content'][0]['text'] - if content: - yield content - except Exception: - continue - - elif provider == "gemini": - headers["Authorization"] = f"Bearer {api_key}" - data = { - "model": model, - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": prompt} - ], - "stream": stream - } - - if not stream: - try: - response = await async_searcher( - ENDPOINTS[provider], - headers=headers, - post=True, - json=data, - re_json=True - ) - if "error" in response: - error = response["error"] - if error.get("code") == 429: - retry_delay = None - for detail in error.get("details", []): - if detail.get("@type") == "type.googleapis.com/google.rpc.RetryInfo": - retry_delay = detail.get("retryDelay", "60s").rstrip("s") - error_msg = f"⚠️ Rate limit exceeded. Please try again in {retry_delay} seconds." - if "free_tier" in str(error): - error_msg += "\nConsider upgrading to a paid tier for higher quotas." - yield error_msg - return - yield f"Error: {error.get('message', 'Unknown error occurred')}" - return - yield response["choices"][0]["message"]["content"] - except Exception as e: - LOGS.exception(e) - yield f"Error: {str(e)}" - return - - async with aiohttp.ClientSession() as session: - try: - async with session.post( - ENDPOINTS[provider], - headers=headers, - json=data - ) as resp: - if resp.status == 429: - error_data = await resp.json() - retry_delay = "60" - for detail in error_data.get("error", {}).get("details", []): - if detail.get("@type") == "type.googleapis.com/google.rpc.RetryInfo": - retry_delay = detail.get("retryDelay", "60s").rstrip("s") - yield f"⚠️ Rate limit exceeded. Please try again in {retry_delay} seconds." - return - - if resp.status != 200: - error_data = await resp.json() - yield f"Error: {error_data.get('error', {}).get('message', 'Unknown error occurred')}" - return - - async for line in resp.content: - if line: - text = line.decode('utf-8').strip() - if text.startswith('data: '): - data = text[6:] # Remove 'data: ' prefix - if data == '[DONE]': - break - try: - json_data = json.loads(data) - if 'choices' in json_data and json_data['choices']: - content = json_data['choices'][0].get('delta', {}).get('content', '') - if content: - yield content - except json.JSONDecodeError: - continue - except Exception as e: - LOGS.exception(e) - yield f"Error: {str(e)}" - - elif provider == "deepseek": - headers["Authorization"] = f"Bearer {api_key}" - data = { - "model": model, - "messages": [{"role": "user", "content": prompt}], - "stream": stream - } - if not stream: - response = await async_searcher( - ENDPOINTS[provider], - headers=headers, - post=True, - json=data, - re_json=True - ) - yield response["choices"][0]["message"]["content"] - return - - async with aiohttp.ClientSession() as session: - async with session.post( - ENDPOINTS[provider], - headers=headers, - json=data - ) as resp: - async for line in resp.content: - if line: - try: - json_line = json.loads(line.decode('utf-8').strip()) - if 'choices' in json_line and json_line['choices']: - content = json_line['choices'][0].get('delta', {}).get('content', '') - if content: - yield content - except Exception: - continue - - except Exception as e: - LOGS.exception(e) - yield f"Error: {str(e)}" - - -@ultroid_cmd(pattern="gemini( (.*)|$)") -async def gemini_ai(event): - """Use Google Gemini""" - prompt = event.pattern_match.group(1).strip() - if not prompt: - return await event.eor("❌ Please provide a prompt!") - - api_key = udB.get_key("GEMINI_API_KEY") - if not api_key: - return await event.eor("⚠️ Please set Gemini API key using `setdb GEMINI_API_KEY your_api_key`") - - msg = await event.eor("🤔 Thinking...") - model = get_model("gemini") - - header = ( - "🤖 **Google Gemini**\n" - f"**Model:** `{model}`\n" - "➖➖➖➖➖➖➖➖➖➖\n\n" - f"**🔍 Prompt:**\n{prompt}\n\n" - "**💡 Response:**\n" - ) - - if event.client.me.bot: - await msg.edit(header) - response = "" - async for chunk in get_ai_response("gemini", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(header + response) - except Exception: - pass - else: - response = "" - async for chunk in get_ai_response("gemini", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(header + response) - except Exception: - pass - -@ultroid_cmd(pattern="antr( (.*)|$)") -async def anthropic_ai(event): - """Use Anthropic Claude""" - prompt = event.pattern_match.group(1).strip() - if not prompt: - return await event.eor("❌ Please provide a prompt!") - - api_key = udB.get_key("ANTHROPIC_KEY") - if not api_key: - return await event.eor("⚠️ Please set Anthropic API key using `setdb ANTHROPIC_KEY your_api_key`") - - msg = await event.eor("🤔 Thinking...") - model = get_model("antr") - - formatted_response = ( - "🧠 **Anthropic Claude**\n" - f"**Model:** `{model}`\n" - "➖➖➖➖➖➖➖➖➖➖\n\n" - f"**🔍 Prompt:**\n{prompt}\n\n" - f"**💡 Response:**\n" - ) - - if event.client.me.bot: - await msg.edit(formatted_response) - response = "" - async for chunk in get_ai_response("antr", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(formatted_response + response) - except Exception: - pass - else: - response = "" - async for chunk in get_ai_response("antr", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(formatted_response + response) - except Exception: - pass - -@ultroid_cmd(pattern="gpt( (.*)|$)") -async def openai_ai(event): - """Use OpenAI GPT""" - prompt = event.pattern_match.group(1).strip() - if not prompt: - return await event.eor("❌ Please provide a prompt!") - - api_key = udB.get_key("OPENAI_API_KEY") - if not api_key: - return await event.eor("⚠️ Please set GPT API key using `setdb OPENAI_API_KEY your_api_key`") - - msg = await event.eor("🤔 Thinking...") - model = get_model("gpt") - - header = ( - "🌟 **OpenAI GPT**\n" - f"**Model:** `{model}`\n" - "➖➖➖➖➖➖➖➖➖➖\n\n" - f"**🔍 Prompt:**\n{prompt}\n\n" - "**💡 Response:**\n" - ) - - if event.client.me.bot: - await msg.edit(header) - response = "" - async for chunk in get_ai_response("gpt", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(header + response) - except Exception: - pass - else: - response ="" - async for chunk in get_ai_response("gpt", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(header + response) - except Exception: - pass - -@ultroid_cmd(pattern="deepseek( (.*)|$)") -async def deepseek_ai(event): - """Use DeepSeek AI""" - prompt = event.pattern_match.group(1).strip() - if not prompt: - return await event.eor("❌ Please provide a prompt!") - - api_key = udB.get_key("DEEPSEEK_API_KEY") - if not api_key: - return await event.eor("⚠️ Please set DeepSeek API key using `setdb DEEPSEEK_API_KEY your_api_key`") - - msg = await event.eor("🤔 Thinking...") - model = get_model("deepseek") - - formatted_response = ( - "🤖 **DeepSeek AI**\n" - f"**Model:** `{model}`\n" - "➖➖➖➖➖➖➖➖➖➖\n\n" - f"**🔍 Prompt:**\n{prompt}\n\n" - f"**💡 Response:**\n" - ) - - if event.client.me.bot: - await msg.edit(formatted_response) - response = "" - async for chunk in get_ai_response("deepseek", prompt, api_key, stream=True): - response += chunk - try: - await msg.edit(formatted_response + response) - except Exception: - pass - else: - response = "" - async for chunk in get_ai_response("deepseek", prompt, api_key, stream=True): - response += chunk - - try: - await msg.edit(formatted_response + response) - except Exception: - pass - diff --git a/plugins/antiflood.py b/plugins/antiflood.py deleted file mode 100644 index 3eb9d19dc0..0000000000 --- a/plugins/antiflood.py +++ /dev/null @@ -1,121 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_antiflood") - - -import re - -from telethon.events import NewMessage as NewMsg - -from pyUltroid.dB import DEVLIST -from pyUltroid.dB.antiflood_db import get_flood, get_flood_limit, rem_flood, set_flood -from pyUltroid.fns.admins import admin_check - -from . import Button, Redis, asst, callback, eod, get_string, ultroid_bot, ultroid_cmd - -_check_flood = {} - -if Redis("ANTIFLOOD"): - - @ultroid_bot.on( - NewMsg( - chats=list(get_flood().keys()), - ), - ) - async def flood_checm(event): - count = 1 - chat = (await event.get_chat()).title - if event.chat_id in _check_flood.keys(): - if event.sender_id == list(_check_flood[event.chat_id].keys())[0]: - count = _check_flood[event.chat_id][event.sender_id] - _check_flood[event.chat_id] = {event.sender_id: count + 1} - else: - _check_flood[event.chat_id] = {event.sender_id: count} - else: - _check_flood[event.chat_id] = {event.sender_id: count} - if await admin_check(event, silent=True) or getattr(event.sender, "bot", None): - return - if event.sender_id in DEVLIST: - return - if _check_flood[event.chat_id][event.sender_id] >= int( - get_flood_limit(event.chat_id) - ): - try: - name = event.sender.first_name - await event.client.edit_permissions( - event.chat_id, event.sender_id, send_messages=False - ) - del _check_flood[event.chat_id] - await event.reply(f"#AntiFlood\n\n{get_string('antiflood_3')}") - await asst.send_message( - int(Redis("LOG_CHANNEL")), - f"#Antiflood\n\n`Muted `[{name}](tg://user?id={event.sender_id})` in {chat}`", - buttons=Button.inline( - "Unmute", data=f"anti_{event.sender_id}_{event.chat_id}" - ), - ) - except BaseException: - pass - - -@callback( - re.compile( - "anti_(.*)", - ), -) -async def unmuting(e): - ino = (e.data_match.group(1)).decode("UTF-8").split("_") - user = int(ino[0]) - chat = int(ino[1]) - user_name = (await ultroid_bot.get_entity(user)).first_name - chat_title = (await ultroid_bot.get_entity(chat)).title - await ultroid_bot.edit_permissions(chat, user, send_messages=True) - await e.edit( - f"#Antiflood\n\n`Unmuted `[{user_name}](tg://user?id={user})` in {chat_title}`" - ) - - -@ultroid_cmd( - pattern="setflood ?(\\d+)", - admins_only=True, -) -async def setflood(e): - input_ = e.pattern_match.group(1).strip() - if not input_: - return await e.eor("`What?`", time=5) - if not input_.isdigit(): - return await e.eor(get_string("com_3"), time=5) - if m := set_flood(e.chat_id, input_): - return await eod(e, get_string("antiflood_4").format(input_)) - - -@ultroid_cmd( - pattern="remflood$", - admins_only=True, -) -async def remove_flood(e): - hmm = rem_flood(e.chat_id) - try: - del _check_flood[e.chat_id] - except BaseException: - pass - if hmm: - return await e.eor(get_string("antiflood_1"), time=5) - await e.eor(get_string("antiflood_2"), time=5) - - -@ultroid_cmd( - pattern="getflood$", - admins_only=True, -) -async def getflood(e): - if ok := get_flood_limit(e.chat_id): - return await e.eor(get_string("antiflood_5").format(ok), time=5) - await e.eor(get_string("antiflood_2"), time=5) diff --git a/plugins/asstcmd.py b/plugins/asstcmd.py deleted file mode 100644 index 02a9945c1c..0000000000 --- a/plugins/asstcmd.py +++ /dev/null @@ -1,100 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_asstcmd") - -import os - -from pyUltroid.dB.asstcmd_db import add_cmd, cmd_reply, list_cmds, rem_cmd -from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button -from telethon import events, utils - -from . import asst, get_string, mediainfo, udB, ultroid_cmd, upload_file - - -@ultroid_cmd(pattern="addcmd( (.*)|$)") -async def ac(e): - wrd = (e.pattern_match.group(1).strip()).lower() - wt = await e.get_reply_message() - if not (wt and wrd): - return await e.eor(get_string("asstcmd_1"), time=5) - if "/" in wrd: - wrd = wrd.replace("/", "") - btn = format_btn(wt.buttons) if wt.buttons else None - if wt and wt.media: - wut = mediainfo(wt.media) - if wut.startswith(("pic", "gif")): - dl = await e.client.download_media(wt.media) - m = upload_file(dl) - os.remove(dl) - elif wut == "video": - if wt.media.document.size > 8 * 1000 * 1000: - return await e.eor(get_string("com_4"), time=5) - dl = await e.client.download_media(wt.media) - m = upload_file(dl) - os.remove(dl) - else: - m = utils.pack_bot_file_id(wt.media) - if wt.text: - txt = wt.text - if not btn: - txt, btn = get_msg_button(wt.text) - add_cmd(wrd, txt, m, btn) - else: - add_cmd(wrd, None, m, btn) - else: - txt = wt.text - if not btn: - txt, btn = get_msg_button(wt.text) - add_cmd(wrd, txt, None, btn) - asst.add_handler( - astcmds, - events.NewMessage( - func=lambda x: x.text.startswith("/") and x.text[1:] in list(list_cmds()) - ), - ) - await e.eor(get_string("asstcmd_4").format(wrd)) - - -@ultroid_cmd(pattern="remcmd( (.*)|$)") -async def rc(e): - wrd = (e.pattern_match.group(1).strip()).lower() - if not wrd: - return await e.eor(get_string("asstcmd_2"), time=5) - wrd = wrd.replace("/", "") - rem_cmd(wrd) - await e.eor(get_string("asstcmd_3").format(wrd)) - - -@ultroid_cmd(pattern="listcmd$") -async def lscmd(e): - if list_cmds(): - ok = get_string("asstcmd_6") - for x in list_cmds(): - ok += f"/{x}" + "\n" - return await e.eor(ok) - return await e.eor(get_string("asstcmd_5")) - - -async def astcmds(e): - xx = (e.text.replace("/", "")).lower().split()[0] - if cmd_reply(xx): - msg, media, bt = cmd_reply(xx) - if bt: - bt = create_tl_btn(bt) - await e.reply(msg, file=media, buttons=bt) - - -if udB.get_key("ASST_CMDS"): - asst.add_handler( - astcmds, - events.NewMessage( - func=lambda x: x.text.startswith("/") and x.text[1:] in list(list_cmds()) - ), - ) diff --git a/plugins/audiotools.py b/plugins/audiotools.py deleted file mode 100644 index 40343bbc8c..0000000000 --- a/plugins/audiotools.py +++ /dev/null @@ -1,163 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -import os -import time -from datetime import datetime as dt - -from pyUltroid.fns.tools import set_attributes - -from . import ( - LOGS, - ULTConfig, - bash, - downloader, - eod, - eor, - genss, - get_help, - get_string, - humanbytes, - mediainfo, - stdr, - time_formatter, - ultroid_cmd -) - -__doc__ = get_help("help_audiotools") - - -@ultroid_cmd(pattern="makevoice$") -async def vnc(e): - if not e.reply_to: - return await eod(e, get_string("audiotools_1")) - r = await e.get_reply_message() - if not mediainfo(r.media).startswith(("audio", "video")): - return await eod(e, get_string("spcltool_1")) - xxx = await e.eor(get_string("com_1")) - file, _ = await e.client.fast_downloader( - r.document, - ) - await xxx.edit(get_string("audiotools_2")) - await bash( - f"ffmpeg -i '{file.name}' -map 0:a -codec:a libopus -b:a 100k -vbr on out.opus" - ) - try: - await e.client.send_message( - e.chat_id, file="out.opus", force_document=False, reply_to=r - ) - except Exception as er: - LOGS.exception(er) - return await xxx.edit("`Failed to convert in Voice...`") - await xxx.delete() - os.remove(file.name) - os.remove("out.opus") - - -@ultroid_cmd(pattern="atrim( (.*)|$)") -async def trim_aud(e): - sec = e.pattern_match.group(1).strip() - if not sec or "-" not in sec: - return await eod(e, get_string("audiotools_3")) - a, b = sec.split("-") - if int(a) >= int(b): - return await eod(e, get_string("audiotools_4")) - vido = await e.get_reply_message() - if vido and vido.media and mediainfo(vido.media).startswith(("video", "audio")): - if hasattr(vido.media, "document"): - vfile = vido.media.document - name = vido.file.name - else: - vfile = vido.media - name = "" - if not name: - name = dt.now().isoformat("_", "seconds") + ".mp4" - xxx = await e.eor(get_string("audiotools_5")) - c_time = time.time() - file = await downloader( - f"resources/downloads/{name}", - vfile, - xxx, - c_time, - f"Downloading {name}...", - ) - - o_size = os.path.getsize(file.name) - d_time = time.time() - diff = time_formatter((d_time - c_time) * 1000) - file_name = (file.name).split("/")[-1] - out = file_name.replace(file_name.split(".")[-1], "_trimmed.aac") - if int(b) > int(await genss(file.name)): - os.remove(file.name) - return await eod(xxx, get_string("audiotools_6")) - ss, dd = stdr(int(a)), stdr(int(b)) - xxx = await xxx.edit( - f"Downloaded `{file.name}` of `{humanbytes(o_size)}` in `{diff}`.\n\nNow Trimming Audio from `{ss}` to `{dd}`..." - ) - cmd = f'ffmpeg -i "{file.name}" -preset ultrafast -ss {ss} -to {dd} -vn -acodec copy "{out}" -y' - await bash(cmd) - os.remove(file.name) - f_time = time.time() - n_file, _ = await e.client.fast_uploader( - out, show_progress=True, event=e, message="Uploading...", to_delete=True - ) - attributes = await set_attributes(out) - - caption = get_string("audiotools_7").format(ss, dd) - await e.client.send_file( - e.chat_id, - n_file, - thumb=ULTConfig.thumb, - caption=caption, - attributes=attributes, - force_document=False, - reply_to=e.reply_to_msg_id, - ) - await xxx.delete() - else: - await e.eor(get_string("audiotools_1"), time=5) - - -@ultroid_cmd(pattern="extractaudio$") -async def ex_aud(e): - reply = await e.get_reply_message() - if not (reply and reply.media and mediainfo(reply.media).startswith("video")): - return await e.eor(get_string("audiotools_8")) - name = reply.file.name or "video.mp4" - vfile = reply.media.document - msg = await e.eor(get_string("com_1")) - c_time = time.time() - file = await downloader( - f"resources/downloads/{name}", - vfile, - msg, - c_time, - f"Downloading {name}...", - ) - - out_file = f"{file.name}.aac" - cmd = f"ffmpeg -i {file.name} -vn -acodec copy {out_file}" - o, err = await bash(cmd) - os.remove(file.name) - attributes = await set_attributes(out_file) - - f_time = time.time() - try: - n_file, _ = await e.client.fast_uploader( - out_file, show_progress=True, event=e, message="Uploading...", to_delete=True - ) - - except FileNotFoundError: - return await eor(msg, get_string("audiotools_9")) - await e.reply( - get_string("audiotools_10"), - file=n_file, - thumb=ULTConfig.thumb, - attributes=attributes, - ) - await msg.delete() diff --git a/plugins/autoban.py b/plugins/autoban.py deleted file mode 100644 index ab002ba64a..0000000000 --- a/plugins/autoban.py +++ /dev/null @@ -1,59 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_autoban") - -from telethon import events - -from pyUltroid.dB.base import KeyManager - -from . import LOGS, asst, ultroid_bot, ultroid_cmd - -Keym = KeyManager("DND_CHATS", cast=list) - - -def join_func(e): - return e.user_joined and Keym.contains(e.chat_id) - - -async def dnd_func(event): - for user in event.users: - try: - await (await event.client.kick_participant(event.chat_id, user)).delete() - except Exception as ex: - LOGS.error("Error in DND:") - LOGS.exception(ex) - await event.delete() - - -@ultroid_cmd( - pattern="autokick (on|off)$", - admins_only=True, - manager=True, - require="ban_users", - fullsudo=True, -) -async def _(event): - match = event.pattern_match.group(1) - if match == "on": - if Keym.contains(event.chat_id): - return await event.eor("`Chat already in do not disturb mode.`", time=3) - Keym.add(event.chat_id) - event.client.add_handler(dnd_func, events.ChatAction(func=join_func)) - await event.eor("`Do not disturb mode activated for this chat.`", time=3) - elif match == "off": - if not Keym.contains(event.chat_id): - return await event.eor("`Chat is not in do not disturb mode.`", time=3) - Keym.remove(event.chat_id) - await event.eor("`Do not disturb mode deactivated for this chat.`", time=3) - - -if Keym.get(): - ultroid_bot.add_handler(dnd_func, events.ChatAction(func=join_func)) - asst.add_handler(dnd_func, events.ChatAction(func=join_func)) diff --git a/plugins/autopic.py b/plugins/autopic.py deleted file mode 100644 index 30a048b61f..0000000000 --- a/plugins/autopic.py +++ /dev/null @@ -1,156 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -import asyncio -import os -import random -from random import shuffle -import aiohttp -import re -from telethon.tl.functions.photos import UploadProfilePhotoRequest -from PIL import Image - -from pyUltroid.fns.helper import download_file, fast_download - -from . import LOGS, get_help, get_string, udB, ultroid_bot, ultroid_cmd - -__doc__ = get_help("help_autopic") - - -async def get_google_images(query: str): - """Extract image URLs from Google Images search results with fallbacks""" - - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - } - - search_url = f"https://www.google.com/search?q={query}&tbm=isch" - - # Domains to exclude - excluded_domains = [ - 'gstatic.com', - 'google.com', - 'googleusercontent.com', - 'ssl.google.com' - ] - - def is_valid_url(url): - return not any(domain in url.lower() for domain in excluded_domains) - - try: - async with aiohttp.ClientSession() as session: - async with session.get(search_url, headers=headers) as response: - html = await response.text() - - # Try to extract from search results div first - img_urls = [] - search_pattern = r'' - search_match = re.search(search_pattern, html, re.DOTALL) - if search_match: - search_content = search_match.group(1) - url_pattern = r'https://[^\"]*?\.(?:jpg|jpeg|png|webp)' - url_matches = re.finditer(url_pattern, search_content, re.IGNORECASE) - for url_match in url_matches: - url = url_match.group(0) - if url not in img_urls and is_valid_url(url): - img_urls.append(url) - - # Fallback to tdeeNb div if no results - if not img_urls: - pattern = r'
]*>(.*?)
' - matches = re.finditer(pattern, html, re.DOTALL) - for match in matches: - div_content = match.group(1) - url_pattern = r'https://[^\"]*?\.(?:jpg|jpeg|png|webp)' - url_matches = re.finditer(url_pattern, div_content, re.IGNORECASE) - for url_match in url_matches: - url = url_match.group(0) - if url not in img_urls and is_valid_url(url): - img_urls.append(url) - - # Fallback to general image search if still no results - if not img_urls: - pattern = r"https://[^\"]*?\.(?:jpg|jpeg|png|webp)" - matches = re.finditer(pattern, html, re.IGNORECASE) - for match in matches: - url = match.group(0) - if url not in img_urls and is_valid_url(url): - img_urls.append(url) - - # Final fallback to data URLs if still no results - if not img_urls: - pattern = r'data:image/(?:jpeg|png|webp);base64,[^\"]*' - matches = re.finditer(pattern, html, re.IGNORECASE) - for match in matches: - url = match.group(0) - if url not in img_urls: - img_urls.append(url) - - return img_urls - - except Exception as e: - print(f"Error fetching Google images: {e}") - return [] - - -@ultroid_cmd(pattern="autopic( (.*)|$)") -async def autopic(e): - search = e.pattern_match.group(1).strip() - if udB.get_key("AUTOPIC") and not search: - udB.del_key("AUTOPIC") - return await e.eor(get_string("autopic_5")) - if not search: - return await e.eor(get_string("autopic_1"), time=5) - e = await e.eor(get_string("com_1")) - images = await get_google_images(search) - if not images: - return await e.eor(get_string("autopic_2").format(search), time=5) - await e.eor(get_string("autopic_3").format(search)) - udB.set_key("AUTOPIC", search) - SLEEP_TIME = udB.get_key("SLEEP_TIME") or 1221 - while True: - for lie in images: - if udB.get_key("AUTOPIC") != search: - return - download_path, stime = await fast_download(lie, "resources/downloads/autopic.jpg") - img = Image.open(download_path) - img.save("resources/downloads/autopic.jpg") - file = await e.client.upload_file("resources/downloads/autopic.jpg") - await e.client(UploadProfilePhotoRequest(file=file)) - os.remove("resources/downloads/autopic.jpg") - await asyncio.sleep(SLEEP_TIME) - - shuffle(images) - - -if search := udB.get_key("AUTOPIC"): - images = {} - sleep = udB.get_key("SLEEP_TIME") or 1221 - - async def autopic_func(): - search = udB.get_key("AUTOPIC") - if images.get(search) is None: - images[search] = await get_google_images(search) - if not images.get(search): - return - img = random.choice(images[search]) - filee, stime = await fast_download(img, "resources/downloads/autopic.jpg") - img = Image.open(filee) - img.save("resources/downloads/autopic.jpg") - file = await ultroid_bot.upload_file("resources/downloads/autopic.jpg") - await ultroid_bot(UploadProfilePhotoRequest(file=file)) - os.remove(filee) - - try: - from apscheduler.schedulers.asyncio import AsyncIOScheduler - - schedule = AsyncIOScheduler() - schedule.add_job(autopic_func, "interval", seconds=sleep) - schedule.start() - except ModuleNotFoundError as er: - LOGS.error(f"autopic: '{er.name}' not installed.") diff --git a/plugins/beautify.py b/plugins/beautify.py deleted file mode 100644 index e8ecf61756..0000000000 --- a/plugins/beautify.py +++ /dev/null @@ -1,197 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_beautify") - - -import os -import random - -from telethon.utils import get_display_name -from urllib.parse import urlencode -from . import Carbon, ultroid_cmd, get_string, inline_mention -from secrets import token_hex - -_colorspath = "resources/colorlist.txt" - -if os.path.exists(_colorspath): - with open(_colorspath, "r") as f: - all_col = f.read().split() -else: - all_col = [] - - -@ultroid_cmd( - pattern="(rc|c)arbon", -) -async def cr_bn(event): - xxxx = await event.eor(get_string("com_1")) - te = event.pattern_match.group(1) - col = random.choice(all_col) if te[0] == "r" else "White" - if event.reply_to_msg_id: - temp = await event.get_reply_message() - if temp.media: - b = await event.client.download_media(temp) - with open(b) as a: - code = a.read() - os.remove(b) - else: - code = temp.message - else: - try: - code = event.text.split(" ", maxsplit=1)[1] - except IndexError: - return await xxxx.eor(get_string("carbon_2")) - xx = await Carbon(code=code, file_name="ultroid_carbon", backgroundColor=col) - if isinstance(xx, dict): - await xxxx.edit(f"`{xx}`") - return - await xxxx.delete() - await event.reply( - f"Carbonised by {inline_mention(event.sender)}", - file=xx, - ) - - -@ultroid_cmd( - pattern="ccarbon( (.*)|$)", -) -async def crbn(event): - match = event.pattern_match.group(1).strip() - if not match: - return await event.eor(get_string("carbon_3")) - msg = await event.eor(get_string("com_1")) - if event.reply_to_msg_id: - temp = await event.get_reply_message() - if temp.media: - b = await event.client.download_media(temp) - with open(b) as a: - code = a.read() - os.remove(b) - else: - code = temp.message - else: - try: - match = match.split(" ", maxsplit=1) - code = match[1] - match = match[0] - except IndexError: - return await msg.eor(get_string("carbon_2")) - xx = await Carbon(code=code, backgroundColor=match) - await msg.delete() - await event.reply( - f"Carbonised by {inline_mention(event.sender)}", - file=xx, - ) - - -RaySoTheme = [ - "meadow", - "breeze", - "raindrop", - "candy", - "crimson", - "falcon", - "sunset", - "noir", - "midnight", - "bitmap", - "ice", - "sand", - "forest", - "mono", -] - - -@ultroid_cmd(pattern="rayso") -async def pass_on(ult): - try: - from playwright.async_api import async_playwright - except ImportError: - await ult.eor( - "`playwright` is not installed!\nPlease install it to use this command.." - ) - return - - proc = await ult.eor(get_string("com_1")) - spli = ult.text.split() - theme, dark, title, text = None, True, get_display_name(ult.chat), None - if len(spli) > 1: - if spli[1] in RaySoTheme: - theme = spli[1] - if len(spli) > 2: - text = " ".join(spli[2:]) - else: - text = " ".join(spli[1:]) - if ult.is_reply: - try: - msg = await ult.get_reply_message() - text = msg.message if not text else text - title = get_display_name(msg.sender) - if not theme and spli[1] in RaySoTheme: - theme = spli[1] - except Exception as sam: - LOGS.exception(sam) - if not text: - await proc.eor("No text to beautify!") - return - if not theme: - theme = random.choice(RaySoTheme) - cleaned_text = "\n".join([line.strip() for line in text.splitlines()]) - name = token_hex(8) + ".png" - data = {"darkMode": dark, "theme": theme, "title": title} - url = f"https://ray.so/#{urlencode(data)}" - async with async_playwright() as play: - try: - browser = await play.chromium.launch() - page = await browser.new_page() - await page.goto(url) - await page.wait_for_load_state("networkidle") - try: - await page.wait_for_selector( - "div[class*='Editor_editor__']", timeout=60000 - ) - editor = await page.query_selector("div[class*='Editor_editor__']") - await editor.focus() - await editor.click() - - for line in cleaned_text.split("\n"): - await page.keyboard.type(line) - await page.keyboard.press("Enter") - - await page.evaluate( - """() => { - const button = document.querySelector('button[aria-label="Export as PNG"]'); - button.click(); - }""" - ) - - async with page.expect_download() as download_info: - download = await download_info.value - await download.save_as(name) - except playwright._impl._errors.TimeoutError: - LOGS.error("Timeout error: Selector not found within 60 seconds.") - await proc.eor("Failed to find the editor within 60 seconds.") - return - except Exception as e: - LOGS.error(f"Error occurred during playwright operation: {e}") - await proc.eor("An error occurred during the operation.") - return - finally: - if os.path.exists(name): - try: - await ult.reply(file=name) - await proc.try_delete() - os.remove(name) - except Exception as e: - LOGS.error(f"Error occurred while replying with the file: {e}") - await proc.eor("Failed to send the file.") - else: - LOGS.error(f"Error: File {name} not found or inaccessible.") - await proc.eor("Failed to save the file.") diff --git a/plugins/blacklist.py b/plugins/blacklist.py deleted file mode 100644 index e621a37e8f..0000000000 --- a/plugins/blacklist.py +++ /dev/null @@ -1,69 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_blacklist") - - -from pyUltroid.dB.blacklist_db import ( - add_blacklist, - get_blacklist, - list_blacklist, - rem_blacklist, -) - -from . import events, get_string, udB, ultroid_bot, ultroid_cmd - - -@ultroid_cmd(pattern="blacklist( (.*)|$)", admins_only=True) -async def af(e): - wrd = e.pattern_match.group(1).strip() - chat = e.chat_id - if not (wrd): - return await e.eor(get_string("blk_1"), time=5) - wrd = e.text[11:] - heh = wrd.split(" ") - for z in heh: - add_blacklist(int(chat), z.lower()) - ultroid_bot.add_handler(blacklist, events.NewMessage(incoming=True)) - await e.eor(get_string("blk_2").format(wrd)) - - -@ultroid_cmd(pattern="remblacklist( (.*)|$)", admins_only=True) -async def rf(e): - wrd = e.pattern_match.group(1).strip() - chat = e.chat_id - if not wrd: - return await e.eor(get_string("blk_3"), time=5) - wrd = e.text[14:] - heh = wrd.split(" ") - for z in heh: - rem_blacklist(int(chat), z.lower()) - await e.eor(get_string("blk_4").format(wrd)) - - -@ultroid_cmd(pattern="listblacklist$", admins_only=True) -async def lsnote(e): - if x := list_blacklist(e.chat_id): - sd = get_string("blk_5") - return await e.eor(sd + x) - await e.eor(get_string("blk_6")) - - -async def blacklist(e): - if x := get_blacklist(e.chat_id): - text = e.text.lower().split() - if any((z in text) for z in x): - try: - await e.delete() - except BaseException: - pass - - -if udB.get_key("BLACKLIST_DB"): - ultroid_bot.add_handler(blacklist, events.NewMessage(incoming=True)) diff --git a/plugins/broadcast.py b/plugins/broadcast.py deleted file mode 100644 index 4eb1d36721..0000000000 --- a/plugins/broadcast.py +++ /dev/null @@ -1,216 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -from . import get_help - -__doc__ = get_help("help_broadcast") - -import asyncio -import io - -from telethon.utils import get_display_name - -from pyUltroid.dB.base import KeyManager - -from . import HNDLR, LOGS, eor, get_string, udB, ultroid_bot, ultroid_cmd - -KeyM = KeyManager("BROADCAST", cast=list) - - -@ultroid_cmd( - pattern="addch( (.*)|$)", - allow_sudo=False, -) -async def broadcast_adder(event): - msgg = event.pattern_match.group(1).strip() - x = await event.eor(get_string("bd_1")) - if msgg == "all": - await x.edit(get_string("bd_2")) - chats = [ - e.entity - for e in await event.client.get_dialogs() - if (e.is_group or e.is_channel) - ] - new = 0 - for i in chats: - try: - if ( - i.broadcast - and (i.creator or i.admin_rights) - and not KeyM.contains(i.id) - ): - new += 1 - cid = f"-100{i.id}" - KeyM.add(int(cid)) - except Exception as Ex: - LOGS.exception(Ex) - await x.edit(get_string("bd_3").format(KeyM.count(), new)) - return - if event.reply_to_msg_id: - previous_message = await event.get_reply_message() - raw_text = previous_message.text - lines = raw_text.split("\n") - length = len(lines) - for line_number in range(1, length - 2): - channel_id = lines[line_number][4:-1] - if not KeyM.contains(channel_id): - KeyM.add(channel_id) - await x.edit(get_string("bd_4")) - await asyncio.sleep(3) - await event.delete() - return - chat_id = event.chat_id - if chat_id == udB.get_key("LOG_CHANNEL"): - return - if KeyM.contains(chat_id): - await x.edit(get_string("bd_6")) - elif xx := KeyM.add(chat_id): - await x.edit(get_string("bd_5")) - else: - await x.edit(get_string("sf_8")) - await asyncio.sleep(3) - await x.delete() - - -@ultroid_cmd( - pattern="remch( (.*)|$)", - allow_sudo=False, -) -async def broadcast_remover(event): - chat_id = event.pattern_match.group(1).strip() or event.chat_id - x = await event.eor(get_string("com_1")) - if chat_id == "all": - await x.edit(get_string("bd_8")) - udB.del_key("BROADCAST") - await x.edit("Database cleared.") - return - if KeyM.contains(chat_id): - KeyM.remove(chat_id) - await x.edit(get_string("bd_7")) - else: - await x.edit(get_string("bd_9")) - await asyncio.sleep(3) - await x.delete() - - -@ultroid_cmd( - pattern="listchannels$", -) -async def list_all(event): - x = await event.eor(get_string("com_1")) - channels = KeyM.get() - num = KeyM.count() - if not channels: - return await eor(x, "No chats were added.", time=5) - msg = "Channels in database:\n" - for channel in channels: - name = "" - try: - name = get_display_name(await event.client.get_entity(channel)) - except ValueError: - name = "" - msg += f"=> **{name}** [`{channel}`]\n" - msg += f"\nTotal {num} channels." - if len(msg) > 4096: - MSG = msg.replace("*", "").replace("`", "") - with io.BytesIO(str.encode(MSG)) as out_file: - out_file.name = "channels.txt" - await event.reply( - "Channels in Database", - file=out_file, - force_document=True, - allow_cache=False, - ) - await x.delete() - else: - await x.edit(msg) - - -@ultroid_cmd( - pattern="forward$", - allow_sudo=False, -) -async def forw(event): - if not event.is_reply: - return await event.eor(get_string("ex_1")) - ultroid_bot = event.client - channels = KeyM.get() - x = await event.eor("Sending...") - if not channels: - return await x.edit(f"Please add channels by using `{HNDLR}add` in them.") - error_count = 0 - sent_count = 0 - previous_message = await event.get_reply_message() - error_count = 0 - for channel in channels: - try: - await ultroid_bot.forward_messages(channel, previous_message) - sent_count += 1 - await x.edit( - f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", - ) - except Exception: - try: - await ultroid_bot.send_message( - udB.get_key("LOG_CHANNEL"), - f"Error in sending at {channel}.", - ) - except Exception as Em: - LOGS.info(Em) - error_count += 1 - await x.edit( - f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", - ) - await x.edit(f"{sent_count} messages sent with {error_count} errors.") - if error_count > 0: - await ultroid_bot.send_message( - udB.get_key("LOG_CHANNEL"), f"{error_count} Errors" - ) - - -@ultroid_cmd( - pattern="broadcast( (.*)|$)", - allow_sudo=False, -) -async def sending(event): - x = await event.eor(get_string("com_1")) - if not event.is_reply: - return await x.edit(get_string("ex_1")) - channels = KeyM.get() - if not channels: - return await x.edit(f"Please add channels by using `{HNDLR}add` in them.") - await x.edit("Sending....") - if event.reply_to_msg_id: - previous_message = await event.get_reply_message() - if previous_message.poll: - return await x.edit(f"Reply `{HNDLR}forward` for polls.") - if previous_message: - error_count = 0 - sent_count = 0 - for channel in channels: - try: - await ultroid_bot.send_message(channel, previous_message) - sent_count += 1 - await x.edit( - f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", - ) - except Exception as error: - await ultroid_bot.send_message( - udB.get_key("LOG_CHANNEL"), - f"Error in sending at {channel}.\n\n{error}", - ) - error_count += 1 - await x.edit( - f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", - ) - await x.edit(f"{sent_count} messages sent with {error_count} errors.") - if error_count > 0: - await ultroid_bot.send_message( - udB.get_key("LOG_CHANNEL"), - f"{error_count} Errors", - ) diff --git a/plugins/button.py b/plugins/button.py deleted file mode 100644 index ef1fbb2a84..0000000000 --- a/plugins/button.py +++ /dev/null @@ -1,54 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_button") - -import os - -from . import upload_file as uf -from telethon.utils import pack_bot_file_id - -from pyUltroid.fns.tools import create_tl_btn, get_msg_button - -from . import HNDLR, get_string, mediainfo, ultroid_cmd -from ._inline import something - - -@ultroid_cmd(pattern="button") -async def butt(event): - media, wut, text = None, None, None - if event.reply_to: - wt = await event.get_reply_message() - if wt.text: - text = wt.text - if wt.media: - wut = mediainfo(wt.media) - if wut and wut.startswith(("pic", "gif")): - dl = await wt.download_media() - media = uf(dl) - elif wut == "video": - if wt.media.document.size > 8 * 1000 * 1000: - return await event.eor(get_string("com_4"), time=5) - dl = await wt.download_media() - media = uf(dl) - os.remove(dl) - else: - media = pack_bot_file_id(wt.media) - try: - text = event.text.split(maxsplit=1)[1] - except IndexError: - if not text: - return await event.eor( - f"**Please give some text in correct format.**\n\n`{HNDLR}help button`", - ) - text, buttons = get_msg_button(text) - if buttons: - buttons = create_tl_btn(buttons) - await something(event, text, media, buttons) - await event.delete() diff --git a/plugins/calculator.py b/plugins/calculator.py deleted file mode 100644 index 3514c2963e..0000000000 --- a/plugins/calculator.py +++ /dev/null @@ -1,153 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -from . import get_help - -__doc__ = get_help("help_calculator") - -import re - -from . import Button, asst, callback, get_string, in_pattern, udB, ultroid_cmd - -CALC = {} - -m = [ - "AC", - "C", - "⌫", - "%", - "7", - "8", - "9", - "+", - "4", - "5", - "6", - "-", - "1", - "2", - "3", - "x", - "00", - "0", - ".", - "÷", -] -tultd = [Button.inline(f"{x}", data=f"calc{x}") for x in m] -lst = list(zip(tultd[::4], tultd[1::4], tultd[2::4], tultd[3::4])) -lst.append([Button.inline("=", data="calc=")]) - - -@ultroid_cmd(pattern="calc") -async def icalc(e): - udB.del_key("calc") - if e.client._bot: - return await e.reply(get_string("calc_1"), buttons=lst) - results = await e.client.inline_query(asst.me.username, "calc") - await results[0].click(e.chat_id, silent=True, hide_via=True) - await e.delete() - - -@in_pattern("calc", owner=True) -async def _(e): - calc = e.builder.article("Calc", text=get_string("calc_1"), buttons=lst) - await e.answer([calc]) - - -@callback(re.compile("calc(.*)"), owner=True) -async def _(e): - x = (e.data_match.group(1)).decode() - user = e.query.user_id - get = None - if x == "AC": - if CALC.get(user): - CALC.pop(user) - await e.edit( - get_string("calc_1"), - buttons=[Button.inline(get_string("calc_2"), data="recalc")], - ) - elif x == "C": - if CALC.get(user): - CALC.pop(user) - await e.answer("cleared") - elif x == "⌫": - if CALC.get(user): - get = CALC[user] - if get: - CALC.update({user: get[:-1]}) - await e.answer(str(get[:-1])) - elif x == "%": - if CALC.get(user): - get = CALC[user] - if get: - CALC.update({user: f"{get}/100"}) - await e.answer(str(f"{get}/100")) - elif x == "÷": - if CALC.get(user): - get = CALC[user] - if get: - CALC.update({user: f"{get}/"}) - await e.answer(str(f"{get}/")) - elif x == "x": - if CALC.get(user): - get = CALC[user] - if get: - CALC.update({user: f"{get}*"}) - await e.answer(str(f"{get}*")) - elif x == "=": - if CALC.get(user): - get = CALC[user] - if get: - if get.endswith(("*", ".", "/", "-", "+")): - get = get[:-1] - out = eval(get) - try: - num = float(out) - await e.answer(f"Answer : {num}", cache_time=0, alert=True) - except BaseException: - CALC.pop(user) - await e.answer(get_string("sf_8"), cache_time=0, alert=True) - await e.answer("None") - else: - if CALC.get(user): - get = CALC[user] - if get: - CALC.update({user: get + x}) - return await e.answer(str(get + x)) - CALC.update({user: x}) - await e.answer(str(x)) - - -@callback("recalc", owner=True) -async def _(e): - m = [ - "AC", - "C", - "⌫", - "%", - "7", - "8", - "9", - "+", - "4", - "5", - "6", - "-", - "1", - "2", - "3", - "x", - "00", - "0", - ".", - "÷", - ] - tultd = [Button.inline(f"{x}", data=f"calc{x}") for x in m] - lst = list(zip(tultd[::4], tultd[1::4], tultd[2::4], tultd[3::4])) - lst.append([Button.inline("=", data="calc=")]) - await e.edit(get_string("calc_1"), buttons=lst) diff --git a/plugins/channelhacks.py b/plugins/channelhacks.py deleted file mode 100644 index 561517a435..0000000000 --- a/plugins/channelhacks.py +++ /dev/null @@ -1,224 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -from . import get_help - -__doc__ = get_help("help_channelhacks") - - -import asyncio -import io - -from telethon.errors.rpcerrorlist import FloodWaitError -from telethon.utils import get_display_name, get_peer_id - -from pyUltroid.dB.base import KeyManager - -from . import LOGS, asst, eor, events, get_string, udB, ultroid_bot, ultroid_cmd - -ERROR = {} -SourceM = KeyManager("CH_SOURCE", cast=list) -DestiM = KeyManager("CH_DESTINATIONS", cast=list) - - -async def autopost_func(e): - if not udB.get_key("AUTOPOST"): - return - x = SourceM.get() - th = await e.get_chat() - if get_peer_id(th) not in x: - return - y = DestiM.get() - for ys in y: - try: - await e.client.send_message(int(ys), e.message) - except Exception as ex: - try: - ERROR[str(ex)] - except KeyError: - ERROR.update({str(ex): ex}) - Error = f"**Error on AUTOPOST**\n\n`{ex}`" - await asst.send_message(udB.get_key("LOG_CHANNEL"), Error) - - -@ultroid_cmd(pattern="shift (.*)") -async def _(e): - x = e.pattern_match.group(1).strip() - z = await e.eor(get_string("com_1")) - a, b = x.split("|") - try: - c = await e.client.parse_id(a) - except Exception: - await z.edit(get_string("cha_1")) - return - try: - d = await e.client.parse_id(b) - except Exception as er: - LOGS.exception(er) - await z.edit(get_string("cha_1")) - return - async for msg in e.client.iter_messages(int(c), reverse=True): - try: - await asyncio.sleep(2) - await e.client.send_message(int(d), msg) - except FloodWaitError as er: - await asyncio.sleep(er.seconds + 5) - await e.client.send_message(int(d), msg) - except BaseException as er: - LOGS.exception(er) - await z.edit("Done") - - -@ultroid_cmd(pattern="asource (.*)") -async def source(e): - if x := e.pattern_match.group(1).strip(): - try: - y = await e.client.parse_id(x) - except Exception as er: - LOGS.exception(er) - return - else: - y = e.chat_id - if not SourceM.contains(y): - SourceM.add(y) - await e.eor(get_string("cha_2")) - ultroid_bot.add_handler(autopost_func, events.NewMessage()) - else: - await e.eor(get_string("cha_3")) - - -@ultroid_cmd(pattern="dsource( (.*)|$)") -async def dd(event): - chat_id = event.pattern_match.group(1).strip() - x = await event.eor(get_string("com_1")) - if chat_id == "all": - await x.edit(get_string("bd_8")) - udB.del_key("CH_SOURCE") - await x.edit(get_string("cha_4")) - return - if chat_id: - try: - y = await event.client.parse_id(chat_id) - except Exception as er: - LOGS.exception(er) - return - else: - y = event.chat_id - if SourceM.contains(y): - SourceM.remove(y) - await eor(x, get_string("cha_5"), time=5) - else: - await eor(x, "Source channel is already removed from database. ", time=3) - - -@ultroid_cmd(pattern="listsource") -async def list_all(event): - x = await event.eor(get_string("com_1")) - num = SourceM.count() - if not num: - return await eor(x, "No chats were added.", time=5) - msg = get_string("cha_8") - channels = SourceM.get() - for channel in channels: - name = "" - try: - name = get_display_name(await event.client.get_entity(int(channel))) - except BaseException: - name = "" - msg += f"\n=> **{name}** [`{channel}`]" - msg += f"\nTotal {num} channels." - if len(msg) > 4096: - MSG = msg.replace("*", "").replace("`", "") - with io.BytesIO(str.encode(MSG)) as out_file: - out_file.name = "channels.txt" - await event.reply( - "Channels in database", - file=out_file, - force_document=True, - allow_cache=False, - ) - await x.delete() - else: - await x.edit(msg) - - -@ultroid_cmd(pattern="adest (.*)") -async def destination(e): - if x := e.pattern_match.group(1).strip(): - try: - y = await e.client.parse_id(x) - except Exception as er: - LOGS.exception(er) - return - else: - y = e.chat_id - if not DestiM.contains(y): - DestiM.add(y) - await e.eor("Destination added succesfully") - else: - await e.eor("Destination channel already added") - - -@ultroid_cmd(pattern="ddest( (.*)|$)") -async def dd(event): - chat_id = event.pattern_match.group(1).strip() - x = await event.eor(get_string("com_1")) - if chat_id == "all": - await x.edit(get_string("bd_8")) - udB.del_key("CH_DESTINATION") - await x.edit("Destinations database cleared.") - return - if chat_id: - try: - y = await event.client.parse_id(chat_id) - except Exception as er: - LOGS.exception(er) - return - else: - y = event.chat_id - if DestiM.contains(y): - DestiM.remove(y) - await eor(x, "Destination removed from database") - else: - await eor(x, "Destination channel is already removed from database. ", time=5) - - -@ultroid_cmd(pattern="listdest") -async def list_all(event): - ultroid_bot = event.client - x = await event.eor(get_string("com_1")) - channels = DestiM.get() - num = len(channels) - if not num: - return await eor(x, "No chats were added.", time=5) - msg = get_string("cha_7") - for channel in channels: - name = "" - try: - name = get_display_name(await ultroid_bot.get_entity(int(channel))) - except BaseException: - name = "" - msg += f"\n=> **{name}** [`{channel}`]" - msg += f"\nTotal {num} channels." - if len(msg) > 4096: - MSG = msg.replace("*", "").replace("`", "") - with io.BytesIO(str.encode(MSG)) as out_file: - out_file.name = "channels.txt" - await ultroid_bot.send_file( - event.chat_id, - out_file, - force_document=True, - allow_cache=False, - caption="Destination channels in database", - reply_to=event, - ) - await x.delete() - else: - await x.edit(msg) - - -if udB.get_key("AUTOPOST"): - ultroid_bot.add_handler(autopost_func, events.NewMessage()) diff --git a/plugins/chatbot.py b/plugins/chatbot.py deleted file mode 100644 index 00c96e03e5..0000000000 --- a/plugins/chatbot.py +++ /dev/null @@ -1,89 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_chatbot") - - -from pyUltroid.fns.tools import get_chatbot_reply - -from . import LOGS, eod, get_string, inline_mention, udB, ultroid_cmd - - -@ultroid_cmd(pattern="repai") -async def im_lonely_chat_with_me(event): - if event.reply_to: - message = (await event.get_reply_message()).message - else: - try: - message = event.text.split(" ", 1)[1] - except IndexError: - return await eod(event, get_string("tban_1"), time=10) - reply_ = await get_chatbot_reply(message=message) - await event.eor(reply_) - - -@ultroid_cmd(pattern="addai") -async def add_chatBot(event): - await chat_bot_fn(event, type_="add") - - -@ultroid_cmd(pattern="remai") -async def rem_chatBot(event): - await chat_bot_fn(event, type_="remov") - - -@ultroid_cmd(pattern="listai") -async def lister(event): - key = udB.get_key("CHATBOT_USERS") or {} - users = key.get(event.chat_id, []) - if not users: - return await event.eor(get_string("chab_2"), time=5) - msg = "**Total List Of AI Enabled Users In This Chat :**\n\n" - for i in users: - try: - user = await event.client.get_entity(int(i)) - user = inline_mention(user) - except BaseException: - user = f"`{i}`" - msg += f"• {user}\n" - await event.eor(msg, link_preview=False) - - -async def chat_bot_fn(event, type_): - if event.reply_to: - user_ = (await event.get_reply_message()).sender - else: - temp = event.text.split(maxsplit=1) - try: - user_ = await event.client.get_entity(await event.client.parse_id(temp[1])) - except BaseException as er: - LOGS.exception(er) - user_ = event.chat if event.is_private else None - if not user_: - return await eod( - event, - get_string("chab_1"), - ) - key = udB.get_key("CHATBOT_USERS") or {} - chat = event.chat_id - user = user_.id - if type_ == "add": - if key.get(chat): - if user not in key[chat]: - key[chat].append(user) - else: - key.update({chat: [user]}) - elif type_ == "remov": - if key.get(chat): - if user in key[chat]: - key[chat].remove(user) - if chat in key and not key[chat]: - del key[chat] - udB.set_key("CHATBOT_USERS", key) - await event.eor(f"**ChatBot:**\n{type_}ed {inline_mention(user_)}") diff --git a/plugins/chats.py b/plugins/chats.py index 0651cd7d72..a0e5f6742b 100644 --- a/plugins/chats.py +++ b/plugins/chats.py @@ -356,6 +356,7 @@ async def _(event): required_string = f"**>> Kicked** `{c} / {p}` **users**\n\n" else: required_string = f"**>> Total** `{p}` **users**\n\n" + required_string += f" `{HNDLR}rmusers deleted` **••** `{d}`\n" required_string += f" `{HNDLR}rmusers empty` **••** `{y}`\n" required_string += f" `{HNDLR}rmusers month` **••** `{m}`\n" diff --git a/plugins/cleanaction.py b/plugins/cleanaction.py deleted file mode 100644 index 361e094762..0000000000 --- a/plugins/cleanaction.py +++ /dev/null @@ -1,48 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_cleanaction") - - -from telethon.utils import get_display_name - -from . import get_string, udB, ultroid_cmd - - -@ultroid_cmd(pattern="addclean$", admins_only=True) -async def _(e): - key = udB.get_key("CLEANCHAT") or [] - if e.chat_id in key: - return await eod(e, get_string("clan_5")) - key.append(e.chat_id) - udB.set_key("CLEANCHAT", key) - await e.eor(get_string("clan_1"), time=5) - - -@ultroid_cmd(pattern="remclean$") -async def _(e): - key = udB.get_key("CLEANCHAT") or [] - if e.chat_id in key: - key.remove(e.chat_id) - udB.set_key("CLEANCHAT", key) - await e.eor(get_string("clan_2"), time=5) - - -@ultroid_cmd(pattern="listclean$") -async def _(e): - if k := udB.get_key("CLEANCHAT"): - o = "" - for x in k: - try: - title = get_display_name(await e.client.get_entity(x)) - except BaseException: - title = get_string("clan_3") - o += f"{x} {title}\n" - return await e.eor(o) - await e.eor(get_string("clan_4"), time=5) diff --git a/plugins/compressor.py b/plugins/compressor.py deleted file mode 100644 index a221d8f161..0000000000 --- a/plugins/compressor.py +++ /dev/null @@ -1,177 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_compressor") - - -import asyncio -import os -import re -import time -from datetime import datetime as dt - -from telethon.errors.rpcerrorlist import MessageNotModifiedError -from telethon.tl.types import DocumentAttributeVideo - -from pyUltroid.fns.tools import metadata - -from . import ( - ULTConfig, - bash, - downloader, - get_string, - humanbytes, - math, - mediainfo, - time_formatter, - ultroid_cmd, - uploader, -) - - -@ultroid_cmd(pattern="compress( (.*)|$)") -async def _(e): - cr = e.pattern_match.group(1).strip() - crf = 27 - to_stream = False - if cr: - k = e.text.split() - if len(k) == 2: - crf = int(k[1]) if k[1].isdigit() else 27 - elif len(k) > 2: - crf = int(k[1]) if k[1].isdigit() else 27 - to_stream = "stream" in k[2] - vido = await e.get_reply_message() - if vido and vido.media and "video" in mediainfo(vido.media): - if hasattr(vido.media, "document"): - vfile = vido.media.document - name = vido.file.name - else: - vfile = vido.media - name = "" - if not name: - name = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" - xxx = await e.eor(get_string("audiotools_5")) - c_time = time.time() - file = await downloader( - f"resources/downloads/{name}", - vfile, - xxx, - c_time, - f"Downloading {name}...", - ) - - o_size = os.path.getsize(file.name) - d_time = time.time() - diff = time_formatter((d_time - c_time) * 1000) - file_name = (file.name).split("/")[-1] - out = file_name.replace(file_name.split(".")[-1], "compressed.mkv") - await xxx.edit( - f"`Downloaded {file.name} of {humanbytes(o_size)} in {diff}.\nNow Compressing...`" - ) - x, y = await bash( - f'mediainfo --fullscan """{file.name}""" | grep "Frame count"' - ) - if y and y.endswith("NOT_FOUND"): - return await xxx.edit(f"ERROR: `{y}`") - total_frames = x.split(":")[1].split("\n")[0] - progress = f"progress-{c_time}.txt" - with open(progress, "w"): - pass - proce = await asyncio.create_subprocess_shell( - f'ffmpeg -hide_banner -loglevel quiet -progress {progress} -i """{file.name}""" -preset ultrafast -vcodec libx265 -crf {crf} -c:a copy """{out}""" -y', - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - while proce.returncode != 0: - await asyncio.sleep(3) - with open(progress, "r+") as fil: - text = fil.read() - frames = re.findall("frame=(\\d+)", text) - size = re.findall("total_size=(\\d+)", text) - speed = 0 - if len(frames): - elapse = int(frames[-1]) - if len(size): - size = int(size[-1]) - per = elapse * 100 / int(total_frames) - time_diff = time.time() - int(d_time) - speed = round(elapse / time_diff, 2) - if int(speed) != 0: - some_eta = ((int(total_frames) - elapse) / speed) * 1000 - text = f"`Compressing {file_name} at {crf} CRF.\n`" - progress_str = "`[{0}{1}] {2}%\n\n`".format( - "".join("●" for _ in range(math.floor(per / 5))), - "".join("" for _ in range(20 - math.floor(per / 5))), - round(per, 2), - ) - - e_size = f"{humanbytes(size)} of ~{humanbytes((size / per) * 100)}" - eta = f"~{time_formatter(some_eta)}" - try: - await xxx.edit( - text - + progress_str - + "`" - + e_size - + "`" - + "\n\n`" - + eta - + "`" - ) - except MessageNotModifiedError: - pass - os.remove(file.name) - c_size = os.path.getsize(out) - f_time = time.time() - difff = time_formatter((f_time - d_time) * 1000) - await xxx.edit( - f"`Compressed {humanbytes(o_size)} to {humanbytes(c_size)} in {difff}\nTrying to Upload...`" - ) - differ = 100 - ((c_size / o_size) * 100) - caption = f"**Original Size: **`{humanbytes(o_size)}`\n" - caption += f"**Compressed Size: **`{humanbytes(c_size)}`\n" - caption += f"**Compression Ratio: **`{differ:.2f}%`\n" - caption += f"\n**Time Taken To Compress: **`{difff}`" - n_file, _ = await e.client.fast_uploader( - out, show_progress=True, event=e, message="Uploading...", to_delete=True - ) - if to_stream: - data = await metadata(out) - width = data["width"] - height = data["height"] - duration = data["duration"] - attributes = [ - DocumentAttributeVideo( - duration=duration, w=width, h=height, supports_streaming=True - ) - ] - await e.client.send_file( - e.chat_id, - n_file, - thumb=ULTConfig.thumb, - caption=caption, - attributes=attributes, - force_document=False, - reply_to=e.reply_to_msg_id, - ) - else: - await e.client.send_file( - e.chat_id, - n_file, - thumb=ULTConfig.thumb, - caption=caption, - force_document=True, - reply_to=e.reply_to_msg_id, - ) - await xxx.delete() - os.remove(out) - os.remove(progress) - else: - await e.eor(get_string("audiotools_8"), time=5) diff --git a/plugins/converter.py b/plugins/converter.py deleted file mode 100644 index 201ff76b96..0000000000 --- a/plugins/converter.py +++ /dev/null @@ -1,196 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_converter") - -import os -import time - -from . import LOGS - -try: - import cv2 -except ImportError: - cv2 = None - -try: - from PIL import Image -except ImportError: - LOGS.info(f"{__file__}: PIL not Installed.") - Image = None - -from . import upload_file as uf - -from . import ( - ULTConfig, - bash, - con, - downloader, - get_paste, - get_string, - udB, - ultroid_cmd, - uploader, -) - -opn = [] - - -@ultroid_cmd( - pattern="thumbnail$", -) -async def _(e): - r = await e.get_reply_message() - if r.photo: - dl = await r.download_media() - elif r.document and r.document.thumbs: - dl = await r.download_media(thumb=-1) - else: - return await e.eor("`Reply to Photo or media with thumb...`") - nn = uf(dl) - os.remove(dl) - udB.set_key("CUSTOM_THUMBNAIL", str(nn)) - await bash(f"wget {nn} -O resources/extras/ultroid.jpg") - await e.eor(get_string("cvt_6").format(nn), link_preview=False) - - -@ultroid_cmd( - pattern="rename( (.*)|$)", -) -async def imak(event): - reply = await event.get_reply_message() - t = time.time() - if not reply: - return await event.eor(get_string("cvt_1")) - inp = event.pattern_match.group(1).strip() - if not inp: - return await event.eor(get_string("cvt_2")) - xx = await event.eor(get_string("com_1")) - if reply.media: - if hasattr(reply.media, "document"): - file = reply.media.document - image = await downloader( - reply.file.name or str(time.time()), - reply.media.document, - xx, - t, - get_string("com_5"), - ) - - file = image.name - else: - file = await event.client.download_media(reply.media) - if os.path.exists(inp): - os.remove(inp) - await bash(f'mv """{file}""" """{inp}"""') - if not os.path.exists(inp) or os.path.exists(inp) and not os.path.getsize(inp): - os.rename(file, inp) - k = time.time() - n_file, _ = await event.client.fast_uploader( - inp, show_progress=True, event=event, message="Uploading...", to_delete=True - ) - await event.reply( - f"`{n_file.name}`", - file=n_file, - force_document=True, - thumb=ULTConfig.thumb, - ) - os.remove(inp) - await xx.delete() - - -conv_keys = { - "img": "png", - "sticker": "webp", - "webp": "webp", - "image": "png", - "webm": "webm", - "gif": "gif", - "json": "json", - "tgs": "tgs", -} - - -@ultroid_cmd( - pattern="convert( (.*)|$)", -) -async def uconverter(event): - xx = await event.eor(get_string("com_1")) - a = await event.get_reply_message() - if a is None: - return await event.eor("`Reply to Photo or media with thumb...`") - input_ = event.pattern_match.group(1).strip() - b = await a.download_media("resources/downloads/") - if not b and (a.document and a.document.thumbs): - b = await a.download_media(thumb=-1) - if not b: - return await xx.edit(get_string("cvt_3")) - try: - convert = conv_keys[input_] - except KeyError: - return await xx.edit(get_string("sts_3").format("gif/img/sticker/webm")) - file = await con.convert(b, outname="ultroid", convert_to=convert) - print(file) - - if file: - await event.client.send_file( - event.chat_id, file, reply_to=event.reply_to_msg_id or event.id - ) - os.remove(file) - else: - await xx.edit("`Failed to convert`") - return - await xx.delete() - -@ultroid_cmd( - pattern="doc( (.*)|$)", -) -async def _(event): - input_str = event.pattern_match.group(1).strip() - if not (input_str and event.is_reply): - return await event.eor(get_string("cvt_1"), time=5) - xx = await event.eor(get_string("com_1")) - a = await event.get_reply_message() - if not a.message: - return await xx.edit(get_string("ex_1")) - with open(input_str, "w") as b: - b.write(str(a.message)) - await xx.edit(f"**Packing into** `{input_str}`") - await event.reply(file=input_str, thumb=ULTConfig.thumb) - await xx.delete() - os.remove(input_str) - - -@ultroid_cmd( - pattern="open( (.*)|$)", -) -async def _(event): - a = await event.get_reply_message() - b = event.pattern_match.group(1).strip() - if not ((a and a.media) or (b and os.path.exists(b))): - return await event.eor(get_string("cvt_7"), time=5) - xx = await event.eor(get_string("com_1")) - rem = None - if not b: - b = await a.download_media() - rem = True - try: - with open(b) as c: - d = c.read() - except UnicodeDecodeError: - return await xx.eor(get_string("cvt_8"), time=5) - try: - await xx.edit(f"```{d}```") - except BaseException: - what, data = await get_paste(d) - await xx.edit( - f"**MESSAGE EXCEEDS TELEGRAM LIMITS**\n\nSo Pasted It On [SPACEBIN]({data['link']})" - ) - if rem: - os.remove(b) diff --git a/plugins/core.py b/plugins/core.py index b93f659a2f..d82fa243d3 100644 --- a/plugins/core.py +++ b/plugins/core.py @@ -12,10 +12,15 @@ import os - +import aiohttp +import base64 +from pathlib import Path from pyUltroid.startup.loader import load_addons +from pyUltroid.state_config import temp_config_store +from pyUltroid.configs import CENTRAL_REPO_URL +import ast -from . import LOGS, async_searcher, eod, get_string, safeinstall, ultroid_cmd, un_plug +from . import LOGS, async_searcher, eod, get_string, safeinstall, ultroid_cmd, un_plug, ultroid_bot @ultroid_cmd(pattern="install", fullsudo=True) @@ -122,3 +127,64 @@ async def get_the_addons_lol(event): get_string("core_18").format(shortname, e), time=3, ) + + +@ultroid_cmd(pattern="publishpg", fullsudo=True) +async def publish_plugin_from_reply(event): + reply = await event.get_reply_message() + if not (reply and reply.document): + return await event.eor("Reply to a plugin file or code to publish.") + file_path = await reply.download_media() + + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + plugin_filename = Path(file_path).name + bot_id = ultroid_bot.me.id + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{bot_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{bot_id}") + if not encoded_init_data or not encoded_hash: + return await event.eor("Authentication data not found. Please authenticate with Ultroid Central first.") + + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + title = plugin_filename.replace('_', ' ').replace('.py', '').title() + try: + docstring = ast.get_docstring(ast.parse(content)) + except Exception: + docstring = None + description = docstring or "Uploaded from Ultroid via publishpg command." + tags = [plugin_filename.replace('.py', '')] + packages = [] + commands = [] + + json_data = { + "title": title, + "description": description, + "tags": tags, + "packages": packages, + "commands": commands, + "is_trusted": False, + "is_official": False, + "plugin_filename": plugin_filename, + "plugin_content": base64.b64encode(content.encode('utf-8')).decode('utf-8') + } + + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + async with aiohttp.ClientSession() as session: + plugin_url = f"{CENTRAL_REPO_URL}/api/v1/plugins" + async with session.post(plugin_url, json=json_data, headers=headers) as response: + status = response.status + resp_text = await response.text() + if status in (200, 201): + await event.eor(f"✅ Successfully published plugin `{title}`.") + else: + await event.eor(f"❌ Failed to publish plugin. Status: {status}\n{resp_text[:200]}") + + Path(file_path).unlink(missing_ok=True) diff --git a/plugins/database.py b/plugins/database.py index 0e6c79e33a..0de790e4b5 100644 --- a/plugins/database.py +++ b/plugins/database.py @@ -13,7 +13,7 @@ import re -from . import Redis, eor, get_string, udB, ultroid_cmd +from . import Redis, eor, get_string, udB, ultroid_cmd, HNDLR @ultroid_cmd(pattern="setdb( (.*)|$)", fullsudo=True) @@ -75,3 +75,72 @@ async def _(ult): await ult.eor(get_string("com_7")) else: await ult.eor("Key not found") + + +@ultroid_cmd(pattern="get($| (.*))", fullsudo=True) +async def get_var(event): + try: + opt = event.text.split(maxsplit=2)[1] + except IndexError: + return await event.eor(f"what to get?\nRead `{HNDLR}help variables`") + x = await event.eor(get_string("com_1")) + if opt != "keys": + try: + varname = event.text.split(maxsplit=2)[2] + except IndexError: + return await eor(x, "Such a var doesn't exist!", time=5) + if opt == "var": + c = 0 + # try redis + val = udB.get_key(varname) + if val is not None: + c += 1 + await x.edit( + f"**Variable** - `{varname}`\n**Value**: `{val}`\n**Type**: Redis Key." + ) + # try env vars + val = os.getenv(varname) + if val is not None: + c += 1 + await x.edit( + f"**Variable** - `{varname}`\n**Value**: `{val}`\n**Type**: Env Var." + ) + + if c == 0: + await eor(x, "Such a var doesn't exist!", time=5) + + elif opt == "type": + c = 0 + # try redis + val = udB.get_key(varname) + if val is not None: + c += 1 + await x.edit(f"**Variable** - `{varname}`\n**Type**: Redis Key.") + # try env vars + val = os.getenv(varname) + if val is not None: + c += 1 + await x.edit(f"**Variable** - `{varname}`\n**Type**: Env Var.") + + if c == 0: + await eor(x, "Such a var doesn't exist!", time=5) + + elif opt == "db": + val = udB.get(varname) + if val is not None: + await x.edit(f"**Key** - `{varname}`\n**Value**: `{val}`") + else: + await eor(x, "No such key!", time=5) + + elif opt == "keys": + keys = sorted(udB.keys()) + msg = "".join( + f"• `{i}`" + "\n" + for i in keys + if not i.isdigit() + and not i.startswith("-") + and not i.startswith("_") + and not i.startswith("GBAN_REASON_") + ) + + await x.edit(f"**List of DB Keys :**\n{msg}") diff --git a/plugins/downloadupload.py b/plugins/downloadupload.py deleted file mode 100644 index d5f5e304f8..0000000000 --- a/plugins/downloadupload.py +++ /dev/null @@ -1,222 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_downloadupload") - -import asyncio -import glob -import os -import time -from datetime import datetime as dt - -from aiohttp.client_exceptions import InvalidURL -from telethon.errors.rpcerrorlist import MessageNotModifiedError - -from pyUltroid.fns.helper import time_formatter -from pyUltroid.fns.tools import get_chat_and_msgid, set_attributes - -from . import ( - LOGS, - ULTConfig, - downloader, - eor, - fast_download, - get_all_files, - get_string, - progress, - time_formatter, - ultroid_cmd, -) - - -@ultroid_cmd( - pattern="download( (.*)|$)", -) -async def down(event): - matched = event.pattern_match.group(1).strip() - msg = await event.eor(get_string("udl_4")) - if not matched: - return await eor(msg, get_string("udl_5"), time=5) - try: - splited = matched.split(" | ") - link = splited[0] - filename = splited[1] - except IndexError: - filename = None - s_time = time.time() - try: - filename, d = await fast_download( - link, - filename, - progress_callback=lambda d, t: asyncio.get_event_loop().create_task( - progress( - d, - t, - msg, - s_time, - f"Downloading from {link}", - ) - ), - ) - except InvalidURL: - return await msg.eor("`Invalid URL provided :(`", time=5) - await msg.eor(f"`{filename}` `downloaded in {time_formatter(d*1000)}.`") - - -@ultroid_cmd( - pattern="dl( (.*)|$)", -) -async def download(event): - match = event.pattern_match.group(1).strip() - if match and "t.me/" in match: - chat, msg = get_chat_and_msgid(match) - if not (chat and msg): - return await event.eor(get_string("gms_1")) - match = "" - ok = await event.client.get_messages(chat, ids=msg) - elif event.reply_to_msg_id: - ok = await event.get_reply_message() - else: - return await event.eor(get_string("cvt_3"), time=8) - xx = await event.eor(get_string("com_1")) - if not (ok and ok.media): - return await xx.eor(get_string("udl_1"), time=5) - s = dt.now() - k = time.time() - if hasattr(ok.media, "document"): - file = ok.media.document - mime_type = file.mime_type - filename = match or ok.file.name - if not filename: - if "audio" in mime_type: - filename = "audio_" + dt.now().isoformat("_", "seconds") + ".ogg" - elif "video" in mime_type: - filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" - try: - result = await downloader( - f"resources/downloads/{filename}", - file, - xx, - k, - f"Downloading {filename}...", - ) - - except MessageNotModifiedError as err: - return await xx.edit(str(err)) - file_name = result.name - else: - d = "resources/downloads/" - file_name = await event.client.download_media( - ok, - d, - progress_callback=lambda d, t: asyncio.get_event_loop().create_task( - progress( - d, - t, - xx, - k, - get_string("com_5"), - ), - ), - ) - e = dt.now() - t = time_formatter(((e - s).seconds) * 1000) - await xx.eor(get_string("udl_2").format(file_name, t)) - - -@ultroid_cmd( - pattern="ul( (.*)|$)", -) -async def _(event): - msg = await event.eor(get_string("com_1")) - match = event.pattern_match.group(1) - if match: - match = match.strip() - if not event.out and match == ".env": - return await event.reply("`You can't do this...`") - stream, force_doc, delete, thumb = ( - False, - True, - False, - ULTConfig.thumb, - ) - if "--stream" in match: - stream = True - force_doc = False - if "--delete" in match: - delete = True - if "--no-thumb" in match: - thumb = None - arguments = ["--stream", "--delete", "--no-thumb"] - if any(item in match for item in arguments): - match = ( - match.replace("--stream", "") - .replace("--delete", "") - .replace("--no-thumb", "") - .strip() - ) - if match.endswith("/"): - match += "*" - results = glob.glob(match) - if not results and os.path.exists(match): - results = [match] - if not results: - try: - await event.reply(file=match) - return await event.try_delete() - except Exception as er: - LOGS.exception(er) - return await msg.eor(get_string("ls1")) - for result in results: - if os.path.isdir(result): - c, s = 0, 0 - for files in get_all_files(result): - attributes = None - if stream: - try: - attributes = await set_attributes(files) - except KeyError as er: - LOGS.exception(er) - try: - file, _ = await event.client.fast_uploader( - files, show_progress=True, event=msg, to_delete=delete - ) - await event.client.send_file( - event.chat_id, - file, - supports_streaming=stream, - force_document=force_doc, - thumb=thumb, - attributes=attributes, - caption=f"`Uploaded` `{files}` `in {time_formatter(_*1000)}`", - reply_to=event.reply_to_msg_id or event, - ) - s += 1 - except (ValueError, IsADirectoryError): - c += 1 - break - attributes = None - if stream: - try: - attributes = await set_attributes(result) - except KeyError as er: - LOGS.exception(er) - file, _ = await event.client.fast_uploader( - result, show_progress=True, event=msg, to_delete=delete - ) - await event.client.send_file( - event.chat_id, - file, - supports_streaming=stream, - force_document=force_doc, - thumb=thumb, - attributes=attributes, - caption=f"`Uploaded` `{result}` `in {time_formatter(_*1000)}`", - ) - await msg.try_delete() diff --git a/plugins/echo.py b/plugins/echo.py deleted file mode 100644 index bda8fff45e..0000000000 --- a/plugins/echo.py +++ /dev/null @@ -1,76 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_echo") - - -from telethon.utils import get_display_name - -from pyUltroid.dB.echo_db import add_echo, check_echo, list_echo, rem_echo - -from . import inline_mention, ultroid_cmd - - -@ultroid_cmd(pattern="addecho( (.*)|$)") -async def echo(e): - r = await e.get_reply_message() - if r: - user = r.sender_id - else: - try: - user = e.text.split()[1] - if user.startswith("@"): - ok = await e.client.get_entity(user) - user = ok.id - else: - user = int(user) - except BaseException: - return await e.eor("Reply To A user.", time=5) - if check_echo(e.chat_id, user): - return await e.eor("Echo already activated for this user.", time=5) - add_echo(e.chat_id, user) - ok = await e.client.get_entity(user) - user = inline_mention(ok) - await e.eor(f"Activated Echo For {user}.") - - -@ultroid_cmd(pattern="remecho( (.*)|$)") -async def rm(e): - r = await e.get_reply_message() - if r: - user = r.sender_id - else: - try: - user = e.text.split()[1] - if user.startswith("@"): - ok = await e.client.get_entity(user) - user = ok.id - else: - user = int(user) - except BaseException: - return await e.eor("Reply To A User.", time=5) - if check_echo(e.chat_id, user): - rem_echo(e.chat_id, user) - ok = await e.client.get_entity(user) - user = f"[{get_display_name(ok)}](tg://user?id={ok.id})" - return await e.eor(f"Deactivated Echo For {user}.") - await e.eor("Echo not activated for this user") - - -@ultroid_cmd(pattern="listecho$") -async def lstecho(e): - if k := list_echo(e.chat_id): - user = "**Activated Echo For Users:**\n\n" - for x in k: - ok = await e.client.get_entity(int(x)) - kk = f"[{get_display_name(ok)}](tg://user?id={ok.id})" - user += f"•{kk}" + "\n" - await e.eor(user) - else: - await e.eor("`List is Empty, For echo`", time=5) diff --git a/plugins/extra.py b/plugins/extra.py deleted file mode 100644 index 7fa9ac8177..0000000000 --- a/plugins/extra.py +++ /dev/null @@ -1,85 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("extra") - -import asyncio - -from . import get_string, ultroid_cmd - - -@ultroid_cmd( - pattern="del$", - manager=True, -) -async def delete_it(delme): - msg_src = await delme.get_reply_message() - if not msg_src: - return - await msg_src.try_delete() - await delme.try_delete() - - -@ultroid_cmd( - pattern="copy$", -) -async def copy(e): - reply = await e.get_reply_message() - if reply: - await reply.reply(reply) - return await e.try_delete() - await e.eor(get_string("ex_1"), time=5) - - -@ultroid_cmd( - pattern="edit", -) -async def editer(edit): - message = edit.text - chat = await edit.get_input_chat() - string = str(message[6:]) - reply = await edit.get_reply_message() - if reply and reply.text: - try: - await reply.edit(string) - await edit.delete() - except BaseException: - pass - else: - i = 1 - async for message in edit.client.iter_messages(chat, from_user="me", limit=2): - if i == 2: - await message.edit(string) - await edit.delete() - break - i += 1 - - -@ultroid_cmd( - pattern="reply$", -) -async def _(e): - if e.reply_to_msg_id: - chat = e.chat_id - try: - msg = (await e.client.get_messages(e.chat_id, limit=1, max_id=e.id))[0] - except IndexError: - return await e.eor( - "`You have previously sent no message to reply again...`", time=5 - ) - except BaseException as er: - return await e.eor(f"**ERROR:** `{er}`") - await asyncio.wait( - [ - e.client.delete_messages(chat, [e.id, msg.id]), - e.client.send_message(chat, msg, reply_to=e.reply_to_msg_id), - ] - ) - else: - await e.try_delete() diff --git a/plugins/fakeaction.py b/plugins/fakeaction.py deleted file mode 100644 index 7e88941959..0000000000 --- a/plugins/fakeaction.py +++ /dev/null @@ -1,36 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_fakeaction") - -import math -import time - -from pyUltroid.fns.admins import ban_time - -from . import asyncio, get_string, ultroid_cmd - - -@ultroid_cmd( - pattern="f(typing|audio|contact|document|game|location|sticker|photo|round|video)( (.*)|$)" -) -async def _(e): - act = e.pattern_match.group(1).strip() - t = e.pattern_match.group(2) - if act in ["audio", "round", "video"]: - act = f"record-{act}" - if t.isdigit(): - t = int(t) - elif t.endswith(("s", "h", "d", "m")): - t = math.ceil((ban_time(t)) - time.time()) - else: - t = 60 - await e.eor(get_string("fka_1").format(str(t)), time=5) - async with e.client.action(e.chat_id, act): - await asyncio.sleep(t) diff --git a/plugins/fileshare.py b/plugins/fileshare.py index 62497d5813..2d47508263 100644 --- a/plugins/fileshare.py +++ b/plugins/fileshare.py @@ -9,12 +9,10 @@ __doc__ = get_help("help_fileshare") -import os +import os, secrets -from pyUltroid.dB.filestore_db import del_stored, get_stored_msg, list_all_stored_msgs -from pyUltroid.fns.tools import get_file_link -from . import HNDLR, asst, get_string, in_pattern, udB, ultroid_bot, ultroid_cmd +from . import HNDLR, asst, get_string, in_pattern, udB, ultroid_bot, ultroid_cmd, LOGS @ultroid_cmd(pattern="store$") @@ -93,3 +91,61 @@ async def file_short(event): text = f"{title}\n\nRead `{HNDLR}help fileshare` to know how to store." return await event.answer([await event.builder.article(title=title, text=text)]) await event.answer(res, switch_pm="• File Store •", switch_pm_param="start") + + +def get_stored(): + return udB.get_key("FILE_STORE") or {} + + +def store_msg(hash, msg_id): + all = get_stored() + all.update({hash: msg_id}) + return udB.set_key("FILE_STORE", all) + + +def list_all_stored_msgs(): + all = get_stored() + return list(all.keys()) + + +def get_stored_msg(hash): + all = get_stored() + if all.get(hash): + return all[hash] + + +def del_stored(hash): + all = get_stored() + all.pop(hash) + return udB.set_key("FILE_STORE", all) + + +async def get_file_link(msg): + from .. import udB + + msg_id = await msg.forward_to(udB.get_key("LOG_CHANNEL")) + await msg_id.reply( + "**Message has been stored to generate a shareable link. Do not delete it.**" + ) + msg_id = msg_id.id + msg_hash = secrets.token_hex(nbytes=8) + store_msg(msg_hash, msg_id) + return msg_hash + + +async def get_stored_file(event, hash): + from .. import udB, asst + + msg_id = get_stored_msg(hash) + if not msg_id: + return + try: + msg = await asst.get_messages(udB.get_key("LOG_CHANNEL"), ids=msg_id) + except Exception as er: + LOGS.warning(f"FileStore, Error: {er}") + return + if not msg_id: + return await asst.send_message( + event.chat_id, "__Message was deleted by owner!__", reply_to=event.id + ) + await asst.send_message(event.chat_id, msg.text, file=msg.media, reply_to=event.id) diff --git a/plugins/filter.py b/plugins/filter.py deleted file mode 100644 index b330769135..0000000000 --- a/plugins/filter.py +++ /dev/null @@ -1,100 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_filter") - -import os -import re - -from telethon.tl.types import User -from telethon.utils import pack_bot_file_id - -from pyUltroid.dB.filter_db import add_filter, get_filter, list_filter, rem_filter -from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button - -from . import events, get_string, mediainfo, udB, ultroid_bot, ultroid_cmd, upload_file -from ._inline import something - - -@ultroid_cmd(pattern="addfilter( (.*)|$)") -async def af(e): - wrd = (e.pattern_match.group(1).strip()).lower() - wt = await e.get_reply_message() - chat = e.chat_id - if not (wt and wrd): - return await e.eor(get_string("flr_1")) - btn = format_btn(wt.buttons) if wt.buttons else None - if wt and wt.media: - wut = mediainfo(wt.media) - if wut.startswith(("pic", "gif")): - dl = await wt.download_media() - m = upload_file(dl) - os.remove(dl) - elif wut == "video": - if wt.media.document.size > 8 * 1000 * 1000: - return await e.eor(get_string("com_4"), time=5) - dl = await wt.download_media() - m = upload_file(dl) - os.remove(dl) - else: - m = pack_bot_file_id(wt.media) - if wt.text: - txt = wt.text - if not btn: - txt, btn = get_msg_button(wt.text) - add_filter(chat, wrd, txt, m, btn) - else: - add_filter(chat, wrd, None, m, btn) - else: - txt = wt.text - if not btn: - txt, btn = get_msg_button(wt.text) - add_filter(chat, wrd, txt, None, btn) - await e.eor(get_string("flr_4").format(wrd)) - ultroid_bot.add_handler(filter_func, events.NewMessage()) - - -@ultroid_cmd(pattern="remfilter( (.*)|$)") -async def rf(e): - wrd = (e.pattern_match.group(1).strip()).lower() - chat = e.chat_id - if not wrd: - return await e.eor(get_string("flr_3")) - rem_filter(int(chat), wrd) - await e.eor(get_string("flr_5").format(wrd)) - - -@ultroid_cmd(pattern="listfilter$") -async def lsnote(e): - if x := list_filter(e.chat_id): - sd = "Filters Found In This Chats Are\n\n" - return await e.eor(sd + x) - await e.eor(get_string("flr_6")) - - -async def filter_func(e): - if isinstance(e.sender, User) and e.sender.bot: - return - xx = (e.text).lower() - chat = e.chat_id - if x := get_filter(chat): - for c in x: - pat = r"( |^|[^\w])" + re.escape(c) + r"( |$|[^\w])" - if re.search(pat, xx): - if k := x.get(c): - msg = k["msg"] - media = k["media"] - if k.get("button"): - btn = create_tl_btn(k["button"]) - return await something(e, msg, media, btn) - await e.reply(msg, file=media) - - -if udB.get_key("FILTERS"): - ultroid_bot.add_handler(filter_func, events.NewMessage()) diff --git a/plugins/fontgen.py b/plugins/fontgen.py deleted file mode 100644 index d686c75f1f..0000000000 --- a/plugins/fontgen.py +++ /dev/null @@ -1,60 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from . import get_help - -__doc__ = get_help("help_fontgen") - -import string - -from . import eod, ultroid_cmd - -_default = string.ascii_letters -Fonts = { - "small caps": "ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴᴏᴘϙʀsᴛᴜᴠᴡxʏᴢABCDEFGHIJKLMNOPQRSTUVWXYZ", - "monospace": "𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉", - "double stroke": "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ", - "script royal": "𝒶𝒷𝒸𝒹𝑒𝒻𝑔𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝑜𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵", -} - - -@ultroid_cmd( - pattern="font( (.*)|$)", -) -async def _(e): - input = e.pattern_match.group(1).strip() - reply = await e.get_reply_message() - if not input: - m = "**Available Fonts**\n\n" - for x in Fonts.keys(): - m += f"• `{x}`\n" - return await e.eor(m, time=5) - if not reply: - try: - _ = input.split(":", maxsplit=1) - font = _[0][:-1] - text = _[1] - except IndexError: - return await eod(e, help) - elif not input: - return await eod(e, "`Give font dude :/`") - else: - font = input - text = reply.message - if font not in Fonts.keys(): - return await e.eor(f"`{font} not in font list`.", time=5) - msg = gen_font(text, Fonts[font]) - await e.eor(msg) - - -def gen_font(text, new_font): - new_font = " ".join(new_font).split() - for q in text: - if q in _default: - new = new_font[_default.index(q)] - text = text.replace(q, new) - return text diff --git a/plugins/forcesubscribe.py b/plugins/forcesubscribe.py deleted file mode 100644 index bc0a01f85f..0000000000 --- a/plugins/forcesubscribe.py +++ /dev/null @@ -1,179 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}fsub ` - Enable ForceSub in Used Chat ! - -• `{i}checkfsub` - Check/Get Active ForceSub Setting of Used Chat. - -• `{i}remfsub` - Remove ForceSub from Used Chat ! - - Note - You Need to be Admin in Both Channel/Chats - in order to Use ForceSubscribe. -""" - -import re - -from telethon.errors.rpcerrorlist import ChatAdminRequiredError, UserNotParticipantError -from telethon.tl.custom import Button -from telethon.tl.functions.channels import GetParticipantRequest -from telethon.tl.functions.messages import ExportChatInviteRequest -from telethon.tl.types import ( - Channel, - ChannelParticipantBanned, - ChannelParticipantLeft, - User, -) - -from pyUltroid.dB.forcesub_db import add_forcesub, get_forcesetting, rem_forcesub - -from . import ( - LOGS, - asst, - callback, - events, - get_string, - in_pattern, - inline_mention, - udB, - ultroid_bot, - ultroid_cmd, -) - -CACHE = {} - - -@ultroid_cmd(pattern="fsub( (.*)|$)", admins_only=True, groups_only=True) -async def addfor(e): - match = e.pattern_match.group(1).strip() - if not match: - return await e.eor(get_string("fsub_1"), time=5) - try: - match = await e.client.parse_id(match) - except BaseException: - return await e.eor(get_string("fsub_2"), time=5) - add_forcesub(e.chat_id, match) - await e.eor("Added ForceSub in This Chat !") - ultroid_bot.add_handler(force_sub, events.NewMessage(incoming=True)) - - -@ultroid_cmd(pattern="remfsub$") -async def remor(e): - res = rem_forcesub(e.chat_id) - if not res: - return await e.eor(get_string("fsub_3"), time=5) - await e.eor("Removed ForceSub...") - - -@ultroid_cmd(pattern="checkfsub$") -async def getfsr(e): - res = get_forcesetting(e.chat_id) - if not res: - return await e.eor("ForceSub is Not Active In This Chat !", time=5) - cha = await e.client.get_entity(int(res)) - await e.eor(f"**ForceSub Status** : `Active`\n- **{cha.title}** `({res})`") - - -@in_pattern("fsub( (.*)|$)", owner=True) -async def fcall(e): - match = e.pattern_match.group(1).strip() - spli = match.split("_") - user = await ultroid_bot.get_entity(int(spli[0])) - cl = await ultroid_bot.get_entity(int(spli[1])) - text = f"Hi {inline_mention(user)}, You Need to Join" - text += f" {cl.title} in order to Chat in this Group." - el = ( - f"https://t.me/{cl.username}" - if cl.username - else (await ultroid_bot(ExportChatInviteRequest(cl))).link - ) - - res = [ - await e.builder.article( - title="forcesub", - text=text, - buttons=[ - [Button.url(text=get_string("fsub_4"), url=el)], - [Button.inline(get_string("fsub_5"), data=f"unm_{match}")], - ], - ) - ] - await e.answer(res) - - -@callback(re.compile("unm_(.*)")) -async def diesoon(e): - match = (e.data_match.group(1)).decode("UTF-8") - spli = match.split("_") - if e.sender_id != int(spli[0]): - return await e.answer(get_string("fsub_7"), alert=True) - try: - values = await ultroid_bot(GetParticipantRequest(int(spli[1]), int(spli[0]))) - if isinstance(values.participant, ChannelParticipantLeft) or ( - isinstance(values.participant, ChannelParticipantBanned) and values.left - ): - raise UserNotParticipantError("") - except UserNotParticipantError: - return await e.answer( - "Please Join That Channel !\nThen Click This Button !", alert=True - ) - await ultroid_bot.edit_permissions( - e.chat_id, int(spli[0]), send_messages=True, until_date=None - ) - await e.edit(get_string("fsub_8")) - - -async def force_sub(ult): - if not udB.get_key("FORCESUB"): - return - user = await ult.get_sender() - joinchat = get_forcesetting(ult.chat_id) - if (not joinchat) or (isinstance(user, User) and user.bot): - return - if CACHE.get(ult.chat_id): - if CACHE[ult.chat_id].get(user.id): - CACHE[ult.chat_id].update({user.id: CACHE[ult.chat_id][user.id] + 1}) - else: - CACHE[ult.chat_id].update({user.id: 1}) - else: - CACHE.update({ult.chat_id: {user.id: 1}}) - count = CACHE[ult.chat_id][user.id] - if count == 11: - CACHE[ult.chat_id][user.id] = 1 - return - if count in range(2, 11): - return - try: - await ultroid_bot.get_permissions(int(joinchat), user.id) - return - except UserNotParticipantError: - pass - if isinstance(user, Channel): - try: - await ultroid_bot.edit_permissions( - ult.chat_id, user.id, view_messages=False - ) - return - except BaseException as er: - LOGS.exception(er) - try: - await ultroid_bot.edit_permissions(ult.chat_id, user.id, send_messages=False) - except ChatAdminRequiredError: - return - except Exception as e: - await ult.delete() - LOGS.info(e) - res = await ultroid_bot.inline_query(asst.me.username, f"fsub {user.id}_{joinchat}") - await res[0].click(ult.chat_id, reply_to=ult.id) - - -if udB.get_key("FORCESUB"): - ultroid_bot.add_handler(force_sub, events.NewMessage(incoming=True)) diff --git a/plugins/gdrive.py b/plugins/gdrive.py deleted file mode 100644 index bafffbd293..0000000000 --- a/plugins/gdrive.py +++ /dev/null @@ -1,232 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - -• `{i}gdul ` - Reply to file to upload on Google Drive. - Add file name to upload on Google Drive. - -• `{i}gdown | ` - Download from Gdrive link or file id. - -• `{i}gdsearch ` - Search file name on Google Drive and get link. - -• `{i}gdlist` - List all GDrive files. - -• `{i}gdfolder` - Link to your Google Drive Folder. - If added then all files will be uploaded in this folder. -""" - -import os -import time - -from telethon.tl.types import Message - -from pyUltroid.fns.gDrive import GDriveManager -from pyUltroid.fns.helper import time_formatter - -from . import ULTConfig, asst, eod, eor, get_string, ultroid_cmd - - -@ultroid_cmd( - pattern="gdown( (.*)|$)", - fullsudo=True, -) -async def gdown(event): - GDrive = GDriveManager() - match = event.pattern_match.group(1).strip() - if not match: - return await eod(event, "`Give file id or Gdrive link to download from!`") - filename = match.split(" | ")[1].strip() if " | " in match else None - eve = await event.eor(get_string("com_1")) - _start = time.time() - status, response = await GDrive._download_file(eve, match, filename) - if not status: - return await eve.edit(response) - await eve.edit( - f"`Downloaded ``{response}`` in {time_formatter((time.time() - _start)*1000)}`" - ) - - -@ultroid_cmd( - pattern="gdlist$", - fullsudo=True, -) -async def files(event): - GDrive = GDriveManager() - if not os.path.exists(GDrive.token_file): - return await event.eor(get_string("gdrive_6").format(asst.me.username)) - eve = await event.eor(get_string("com_1")) - msg = "" - if files := GDrive._list_files: - msg += f"{len(files.keys())} files found in gdrive.\n\n" - for _ in files: - msg += f"> [{files[_]}]({_})\n" - else: - msg += "Nothing in Gdrive" - if len(msg) < 4096: - await eve.edit(msg, link_preview=False) - else: - with open("drive-files.txt", "w") as f: - f.write( - msg.replace("[", "File Name: ") - .replace("](", "\n» Link: ") - .replace(")\n", "\n\n") - ) - try: - await eve.delete() - except BaseException: - pass - await event.client.send_file( - event.chat_id, - "drive-files.txt", - thumb=ULTConfig.thumb, - reply_to=event, - ) - os.remove("drive-files.txt") - - -@ultroid_cmd( - pattern="gdul( (.*)|$)", - fullsudo=True, -) -async def _(event): - GDrive = GDriveManager() - if not os.path.exists(GDrive.token_file): - return await eod(event, get_string("gdrive_6").format(asst.me.username)) - input_file = event.pattern_match.group(1).strip() or await event.get_reply_message() - if not input_file: - return await eod(event, "`Reply to file or give its location.`") - mone = await event.eor(get_string("com_1")) - if isinstance(input_file, Message): - location = "resources/downloads" - if input_file.photo: - filename = await input_file.download_media(location) - else: - filename = input_file.file.name - if not filename: - filename = str(round(time.time())) - filename = f"{location}/{filename}" - try: - filename, downloaded_in = await event.client.fast_downloader( - file=input_file.media.document, - filename=filename, - show_progress=True, - event=mone, - message=get_string("com_5"), - ) - filename = filename.name - except Exception as e: - return await eor(mone, str(e), time=10) - await mone.edit( - f"`Downloaded to ``{filename}`.`", - ) - else: - filename = input_file.strip() - if not os.path.exists(filename): - return await eod( - mone, - "File Not found in local server. Give me a file path :((", - time=5, - ) - folder_id = None - if os.path.isdir(filename): - files = os.listdir(filename) - if not files: - return await eod( - mone, "`Requested directory is empty. Can't create empty directory.`" - ) - folder_id = GDrive.create_directory(filename) - c = 0 - for files in sorted(files): - file = f"{filename}/{files}" - if not os.path.isdir(file): - try: - await GDrive._upload_file(mone, path=file, folder_id=folder_id) - c += 1 - except Exception as e: - return await mone.edit( - f"Exception occurred while uploading to gDrive {e}" - ) - return await mone.edit( - f"`Uploaded `[{filename}](https://drive.google.com/folderview?id={folder_id})` with {c} files.`" - ) - try: - g_drive_link = await GDrive._upload_file( - mone, - filename, - ) - await mone.edit( - get_string("gdrive_7").format(filename.split("/")[-1], g_drive_link) - ) - except Exception as e: - await mone.edit(f"Exception occurred while uploading to gDrive {e}") - - -@ultroid_cmd( - pattern="gdsearch( (.*)|$)", - fullsudo=True, -) -async def _(event): - GDrive = GDriveManager() - if not os.path.exists(GDrive.token_file): - return await event.eor(get_string("gdrive_6").format(asst.me.username)) - input_str = event.pattern_match.group(1).strip() - if not input_str: - return await event.eor("`Give filename to search on GDrive...`") - eve = await event.eor(f"`Searching for {input_str} in G-Drive...`") - files = GDrive.search(input_str) - msg = "" - if files: - msg += ( - f"{len(files.keys())} files with {input_str} in title found in GDrive.\n\n" - ) - for _ in files: - msg += f"> [{files[_]}]({_})\n" - else: - msg += f"`No files with title {input_str}`" - if len(msg) < 4096: - await eve.eor(msg, link_preview=False) - else: - with open("drive-files.txt", "w") as f: - f.write( - msg.replace("[", "File Name: ") - .replace("](", "\n» Link: ") - .replace(")\n", "\n\n") - ) - try: - await eve.delete() - except BaseException: - pass - await event.client.send_file( - event.chat_id, - f"{input_str}.txt", - thumb=ULTConfig.thumb, - reply_to=event, - ) - os.remove(f"{input_str}.txt") - - -@ultroid_cmd( - pattern="gdfolder$", - fullsudo=True, -) -async def _(event): - GDrive = GDriveManager() - if not os.path.exists(GDrive.token_file): - return await event.eor(get_string("gdrive_6").format(asst.me.username)) - if GDrive.folder_id: - await event.eor( - "`Your G-Drive Folder link : `\n" - + GDrive._create_folder_link(GDrive.folder_id) - ) - else: - await eod(event, "Set FOLDERID from your Assistant bot's Settings ") diff --git a/plugins/giftools.py b/plugins/giftools.py deleted file mode 100644 index 5c8d0d2283..0000000000 --- a/plugins/giftools.py +++ /dev/null @@ -1,128 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - -•`{i}invertgif` - Make Gif Inverted(negative). - -•`{i}bwgif` - Make Gif black and white - -•`{i}rvgif` - Reverse a gif - -•`{i}vtog` - Reply To Video , It will Create Gif - Video to Gif - -•`{i}gif ` - Send video regarding to query. -""" -import os -import random -import time -from datetime import datetime as dt - -from . import HNDLR, LOGS, bash, downloader, get_string, mediainfo, ultroid_cmd - - -@ultroid_cmd(pattern="(bw|invert)gif$") -async def igif(e): - match = e.pattern_match.group(1).strip() - a = await e.get_reply_message() - if not (a and a.media): - return await e.eor("`Reply To gif only`", time=5) - wut = mediainfo(a.media) - if "gif" not in wut: - return await e.eor("`Reply To Gif Only`", time=5) - xx = await e.eor(get_string("com_1")) - z = await a.download_media() - if match == "bw": - cmd = f'ffmpeg -i "{z}" -vf format=gray ult.gif -y' - else: - cmd = f'ffmpeg -i "{z}" -vf lutyuv="y=negval:u=negval:v=negval" ult.gif -y' - try: - await bash(cmd) - await e.client.send_file(e.chat_id, "ult.gif", supports_streaming=True) - os.remove(z) - os.remove("ult.gif") - await xx.delete() - except Exception as er: - LOGS.info(er) - - -@ultroid_cmd(pattern="rvgif$") -async def reverse_gif(event): - a = await event.get_reply_message() - if not (a and a.media) and "video" not in mediainfo(a.media): - return await event.eor("`Reply To Video only`", time=5) - msg = await event.eor(get_string("com_1")) - file = await a.download_media() - await bash(f'ffmpeg -i "{file}" -vf reverse -af areverse reversed.mp4 -y') - await event.respond("- **Reversed Video/GIF**", file="reversed.mp4") - await msg.delete() - os.remove(file) - os.remove("reversed.mp4") - - -@ultroid_cmd(pattern="gif( (.*)|$)") -async def gifs(ult): - get = ult.pattern_match.group(1).strip() - xx = random.randint(0, 5) - n = 0 - if ";" in get: - try: - n = int(get.split(";")[-1]) - except IndexError: - pass - if not get: - return await ult.eor(f"`{HNDLR}gif `") - m = await ult.eor(get_string("com_2")) - gifs = await ult.client.inline_query("gif", get) - if not n: - await gifs[xx].click( - ult.chat_id, reply_to=ult.reply_to_msg_id, silent=True, hide_via=True - ) - else: - for x in range(n): - await gifs[x].click( - ult.chat_id, reply_to=ult.reply_to_msg_id, silent=True, hide_via=True - ) - await m.delete() - - -@ultroid_cmd(pattern="vtog$") -async def vtogif(e): - a = await e.get_reply_message() - if not (a and a.media): - return await e.eor("`Reply To video only`", time=5) - wut = mediainfo(a.media) - if "video" not in wut: - return await e.eor("`Reply To Video Only`", time=5) - xx = await e.eor(get_string("com_1")) - dur = a.media.document.attributes[0].duration - tt = time.time() - if int(dur) < 120: - z = await a.download_media() - await bash( - f'ffmpeg -i {z} -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ult.gif -y' - ) - else: - filename = a.file.name - if not filename: - filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" - vid = await downloader(filename, a.media.document, xx, tt, get_string("com_5")) - z = vid.name - await bash( - f'ffmpeg -ss 3 -t 100 -i {z} -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ult.gif' - ) - - await e.client.send_file(e.chat_id, "ult.gif", support_stream=True) - os.remove(z) - os.remove("ult.gif") - await xx.delete() diff --git a/plugins/glitch.py b/plugins/glitch.py deleted file mode 100644 index e4ee4df41b..0000000000 --- a/plugins/glitch.py +++ /dev/null @@ -1,42 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -•`{i}glitch ` - gives a glitchy gif. -""" -import os - -from . import bash, get_string, mediainfo, ultroid_cmd - - -@ultroid_cmd(pattern="glitch$") -async def _(e): - try: - import glitch_me # ignore :pylint - except ModuleNotFoundError: - await bash( - "pip install -e git+https://github.com/1Danish-00/glitch_me.git#egg=glitch_me" - ) - reply = await e.get_reply_message() - if not reply or not reply.media: - return await e.eor(get_string("cvt_3")) - xx = await e.eor(get_string("glitch_1")) - wut = mediainfo(reply.media) - if wut.startswith(("pic", "sticker")): - ok = await reply.download_media() - elif reply.document and reply.document.thumbs: - ok = await reply.download_media(thumb=-1) - else: - return await xx.eor(get_string("com_4")) - cmd = f"glitch_me gif --line_count 200 -f 10 -d 50 '{ok}' ult.gif" - await bash(cmd) - await e.reply(file="ult.gif", force_document=False) - await xx.delete() - os.remove(ok) - os.remove("ult.gif") diff --git a/plugins/globaltools.py b/plugins/globaltools.py deleted file mode 100644 index a81744abd9..0000000000 --- a/plugins/globaltools.py +++ /dev/null @@ -1,753 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}gban ` -• `{i}ungban` - Ban/Unban Globally. - -• `{i}gstat ` - Check if user is GBanned. - -• `{i}listgban` : List all GBanned users. - -• `{i}gmute` | `{i}ungmute` - Mute/UnMute Globally. - -• `{i}gkick ` `Globally Kick User` -• `{i}gcast ` `Globally Send msg in all grps` - -• `{i}gadmincast ` `Globally broadcast in your admin chats` -• `{i}gucast ` `Globally send msg in all pm users` - -• `{i}gblacklist ` - globally promote user where you are admin - - Set whether To promote only in groups/channels/all. - Eg- `gpromote group boss` ~ promotes user in all grps. - `gpromote @username all sar` ~ promote the user in all group & channel -• `{i}gdemote` - `demote user globally` -""" -import asyncio -import os - -from telethon.errors.rpcerrorlist import ChatAdminRequiredError, FloodWaitError -from telethon.tl.functions.channels import EditAdminRequest -from telethon.tl.functions.contacts import BlockRequest, UnblockRequest -from telethon.tl.types import ChatAdminRights, User - -from pyUltroid.dB import DEVLIST -from pyUltroid.dB.base import KeyManager -from pyUltroid.dB.gban_mute_db import ( - gban, - gmute, - is_gbanned, - is_gmuted, - list_gbanned, - ungban, - ungmute, -) -from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button - -from . import ( - HNDLR, - LOGS, - NOSPAM_CHAT, - OWNER_NAME, - eod, - eor, - get_string, - inline_mention, - ultroid_bot, - ultroid_cmd, -) -from ._inline import something - -_gpromote_rights = ChatAdminRights( - add_admins=False, - invite_users=True, - change_info=False, - ban_users=True, - delete_messages=True, - pin_messages=True, -) - -_gdemote_rights = ChatAdminRights( - add_admins=False, - invite_users=False, - change_info=False, - ban_users=False, - delete_messages=False, - pin_messages=False, -) - -keym = KeyManager("GBLACKLISTS", cast=list) - - -@ultroid_cmd(pattern="gpromote( (.*)|$)", fullsudo=True) -async def _(e): - x = e.pattern_match.group(1).strip() - ultroid_bot = e.client - if not x: - return await e.eor(get_string("schdl_2"), time=5) - user = await e.get_reply_message() - if user: - ev = await e.eor("`Promoting Replied User Globally`") - ok = e.text.split() - key = "all" - if len(ok) > 1 and (("group" in ok[1]) or ("channel" in ok[1])): - key = ok[1] - rank = ok[2] if len(ok) > 2 else "AdMin" - c = 0 - user.id = user.peer_id.user_id if e.is_private else user.from_id.user_id - async for x in e.client.iter_dialogs(): - if ( - "group" in key.lower() - and x.is_group - or "group" not in key.lower() - and "channel" in key.lower() - and x.is_channel - ): - try: - await e.client( - EditAdminRequest( - x.id, - user.id, - _gpromote_rights, - rank, - ), - ) - c += 1 - except BaseException: - pass - elif ( - ("group" not in key.lower() or x.is_group) - and ( - "group" in key.lower() - or "channel" not in key.lower() - or x.is_channel - ) - and ( - "group" in key.lower() - or "channel" in key.lower() - or x.is_group - or x.is_channel - ) - ): - try: - await e.client( - EditAdminRequest( - x.id, - user.id, - _gpromote_rights, - rank, - ), - ) - c += 1 - except Exception as er: - LOGS.info(er) - await eor(ev, f"Promoted The Replied Users in Total : {c} {key} chats") - else: - k = e.text.split() - if not k[1]: - return await eor( - e, "`Give someone's username/id or replied to user.", time=5 - ) - user = k[1] - if user.isdigit(): - user = int(user) - try: - name = await e.client.get_entity(user) - except BaseException: - return await e.eor(f"`No User Found Regarding {user}`", time=5) - ev = await e.eor(f"`Promoting {name.first_name} globally.`") - key = "all" - if len(k) > 2 and (("group" in k[2]) or ("channel" in k[2])): - key = k[2] - rank = k[3] if len(k) > 3 else "AdMin" - c = 0 - async for x in e.client.iter_dialogs(): - if ( - "group" in key.lower() - and x.is_group - or "group" not in key.lower() - and "channel" in key.lower() - and x.is_channel - or "group" not in key.lower() - and "channel" not in key.lower() - and (x.is_group or x.is_channel) - ): - try: - await ultroid_bot( - EditAdminRequest( - x.id, - user, - _gpromote_rights, - rank, - ), - ) - c += 1 - except BaseException: - pass - await eor(ev, f"Promoted {name.first_name} in Total : {c} {key} chats.") - - -@ultroid_cmd(pattern="gdemote( (.*)|$)", fullsudo=True) -async def _(e): - x = e.pattern_match.group(1).strip() - ultroid_bot = e.client - if not x: - return await e.eor(get_string("schdl_2"), time=5) - user = await e.get_reply_message() - if user: - user.id = user.peer_id.user_id if e.is_private else user.from_id.user_id - ev = await e.eor("`Demoting Replied User Globally`") - ok = e.text.split() - key = "all" - if len(ok) > 1 and (("group" in ok[1]) or ("channel" in ok[1])): - key = ok[1] - rank = "Not AdMin" - c = 0 - async for x in e.client.iter_dialogs(): - if ( - "group" in key.lower() - and x.is_group - or "group" not in key.lower() - and "channel" in key.lower() - and x.is_channel - or "group" not in key.lower() - and "channel" not in key.lower() - and (x.is_group or x.is_channel) - ): - try: - await ultroid_bot( - EditAdminRequest( - x.id, - user.id, - _gdemote_rights, - rank, - ), - ) - c += 1 - except BaseException: - pass - await eor(ev, f"Demoted The Replied Users in Total : {c} {key} chats") - else: - k = e.text.split() - if not k[1]: - return await eor( - e, "`Give someone's username/id or replied to user.", time=5 - ) - user = k[1] - if user.isdigit(): - user = int(user) - try: - name = await ultroid_bot.get_entity(user) - except BaseException: - return await e.eor(f"`No User Found Regarding {user}`", time=5) - ev = await e.eor(f"`Demoting {name.first_name} globally.`") - key = "all" - if len(k) > 2 and (("group" in k[2]) or ("channel" in k[2])): - key = k[2] - rank = "Not AdMin" - c = 0 - async for x in ultroid_bot.iter_dialogs(): - if ( - "group" in key.lower() - and x.is_group - or "group" not in key.lower() - and "channel" in key.lower() - and x.is_channel - or "group" not in key.lower() - and "channel" not in key.lower() - and (x.is_group or x.is_channel) - ): - try: - await ultroid_bot( - EditAdminRequest( - x.id, - user, - _gdemote_rights, - rank, - ), - ) - c += 1 - except BaseException: - pass - await eor(ev, f"Demoted {name.first_name} in Total : {c} {key} chats.") - - -@ultroid_cmd(pattern="ungban( (.*)|$)", fullsudo=True) -async def _(e): - xx = await e.eor("`UnGbanning...`") - match = e.pattern_match.group(1).strip() - peer = None - if e.reply_to_msg_id: - userid = (await e.get_reply_message()).sender_id - elif match: - try: - userid = int(match) - except ValueError: - userid = match - try: - userid = (await e.client.get_entity(userid)).id - except Exception as er: - return await xx.edit(f"Failed to get User...\nError: {er}") - elif e.is_private: - userid = e.chat_id - else: - return await xx.eor("`Reply to some msg or add their id.`", time=5) - if not is_gbanned(userid): - return await xx.edit("`User/Channel is not Gbanned...`") - try: - if not peer: - peer = await e.client.get_entity(userid) - name = inline_mention(peer) - except BaseException: - userid = int(userid) - name = str(userid) - chats = 0 - if e.client._dialogs: - dialog = e.client._dialogs - else: - dialog = await e.client.get_dialogs() - e.client._dialogs.extend(dialog) - for ggban in dialog: - if ggban.is_group or ggban.is_channel: - try: - await e.client.edit_permissions(ggban.id, userid, view_messages=True) - chats += 1 - except FloodWaitError as fw: - LOGS.info( - f"[FLOOD_WAIT_ERROR] : on Ungban\nSleeping for {fw.seconds+10}" - ) - await asyncio.sleep(fw.seconds + 10) - try: - await e.client.edit_permissions( - ggban.id, userid, view_messages=True - ) - chats += 1 - except BaseException as er: - LOGS.exception(er) - except (ChatAdminRequiredError, ValueError): - pass - except BaseException as er: - LOGS.exception(er) - ungban(userid) - if isinstance(peer, User): - await e.client(UnblockRequest(userid)) - await xx.edit( - f"`Ungbaned` {name} in {chats} `chats.\nRemoved from gbanwatch.`", - ) - - -@ultroid_cmd(pattern="gban( (.*)|$)", fullsudo=True) -async def _(e): - xx = await e.eor("`Gbanning...`") - reason = "" - if e.reply_to_msg_id: - userid = (await e.get_reply_message()).sender_id - try: - reason = e.text.split(" ", maxsplit=1)[1] - except IndexError: - pass - elif e.pattern_match.group(1).strip(): - usr = e.text.split(maxsplit=2)[1] - try: - userid = await e.client.parse_id(usr) - except ValueError: - userid = usr - try: - reason = e.text.split(maxsplit=2)[2] - except IndexError: - pass - elif e.is_private: - userid = e.chat_id - try: - reason = e.text.split(" ", maxsplit=1)[1] - except IndexError: - pass - else: - return await xx.eor("`Reply to some msg or add their id.`", time=5) - user = None - try: - user = await e.client.get_entity(userid) - name = inline_mention(user) - except BaseException: - userid = int(userid) - name = str(userid) - chats = 0 - if userid == ultroid_bot.uid: - return await xx.eor("`I can't gban myself.`", time=3) - elif userid in DEVLIST: - return await xx.eor("`I can't gban my Developers.`", time=3) - elif is_gbanned(userid): - return await eod( - xx, - "`User is already gbanned and added to gbanwatch.`", - time=4, - ) - if e.client._dialogs: - dialog = e.client._dialogs - else: - dialog = await e.client.get_dialogs() - e.client._dialogs.extend(dialog) - for ggban in dialog: - if ggban.is_group or ggban.is_channel: - try: - await e.client.edit_permissions(ggban.id, userid, view_messages=False) - chats += 1 - except FloodWaitError as fw: - LOGS.info( - f"[FLOOD_WAIT_ERROR] : on GBAN Command\nSleeping for {fw.seconds+10}" - ) - await asyncio.sleep(fw.seconds + 10) - try: - await e.client.edit_permissions( - ggban.id, userid, view_messages=False - ) - chats += 1 - except BaseException as er: - LOGS.exception(er) - except (ChatAdminRequiredError, ValueError): - pass - except BaseException as er: - LOGS.exception(er) - gban(userid, reason) - if isinstance(user, User): - await e.client(BlockRequest(userid)) - gb_msg = f"**#Gbanned** {name} `in {chats} chats and added to gbanwatch!`" - if reason: - gb_msg += f"\n**Reason** : {reason}" - await xx.edit(gb_msg) - - -@ultroid_cmd(pattern="g(admin|)cast( (.*)|$)", fullsudo=True) -async def gcast(event): - text, btn, reply = "", None, None - if xx := event.pattern_match.group(2): - msg, btn = get_msg_button(event.text.split(maxsplit=1)[1]) - elif event.is_reply: - reply = await event.get_reply_message() - msg = reply.text - if reply.buttons: - btn = format_btn(reply.buttons) - else: - msg, btn = get_msg_button(msg) - else: - return await eor( - event, "`Give some text to Globally Broadcast or reply a message..`" - ) - - kk = await event.eor("`Globally Broadcasting Msg...`") - er = 0 - done = 0 - err = "" - if event.client._dialogs: - dialog = event.client._dialogs - else: - dialog = await event.client.get_dialogs() - event.client._dialogs.extend(dialog) - for x in dialog: - if x.is_group: - chat = x.entity.id - if ( - not keym.contains(chat) - and int(f"-100{str(chat)}") not in NOSPAM_CHAT - and ( - ( - event.text[2:7] != "admin" - or (x.entity.admin_rights or x.entity.creator) - ) - ) - ): - try: - if btn: - bt = create_tl_btn(btn) - await something( - event, - msg, - reply.media if reply else None, - bt, - chat=chat, - reply=False, - ) - else: - await event.client.send_message( - chat, msg, file=reply.media if reply else None - ) - done += 1 - except FloodWaitError as fw: - await asyncio.sleep(fw.seconds + 10) - try: - if btn: - bt = create_tl_btn(btn) - await something( - event, - msg, - reply.media if reply else None, - bt, - chat=chat, - reply=False, - ) - else: - await event.client.send_message( - chat, msg, file=reply.media if reply else None - ) - done += 1 - except Exception as rr: - err += f"• {rr}\n" - er += 1 - except BaseException as h: - err += f"• {str(h)}" + "\n" - er += 1 - text += f"Done in {done} chats, error in {er} chat(s)" - if err != "": - open("gcast-error.log", "w+").write(err) - text += f"\nYou can do `{HNDLR}ul gcast-error.log` to know error report." - await kk.edit(text) - - -@ultroid_cmd(pattern="gucast( (.*)|$)", fullsudo=True) -async def gucast(event): - msg, btn, reply = "", None, None - if xx := event.pattern_match.group(1).strip(): - msg, btn = get_msg_button(event.text.split(maxsplit=1)[1]) - elif event.is_reply: - reply = await event.get_reply_message() - msg = reply.text - if reply.buttons: - btn = format_btn(reply.buttons) - else: - msg, btn = get_msg_button(msg) - else: - return await eor( - event, "`Give some text to Globally Broadcast or reply a message..`" - ) - kk = await event.eor("`Globally Broadcasting Msg...`") - er = 0 - done = 0 - if event.client._dialogs: - dialog = event.client._dialogs - else: - dialog = await event.client.get_dialogs() - event.client._dialogs.extend(dialog) - for x in dialog: - if x.is_user and not x.entity.bot: - chat = x.id - if not keym.contains(chat): - try: - if btn: - bt = create_tl_btn(btn) - await something( - event, - msg, - reply.media if reply else None, - bt, - chat=chat, - reply=False, - ) - else: - await event.client.send_message( - chat, msg, file=reply.media if reply else None - ) - done += 1 - except BaseException: - er += 1 - await kk.edit(f"Done in {done} chats, error in {er} chat(s)") - - -@ultroid_cmd(pattern="gkick( (.*)|$)", fullsudo=True) -async def gkick(e): - xx = await e.eor("`Gkicking...`") - if e.reply_to_msg_id: - userid = (await e.get_reply_message()).sender_id - elif e.pattern_match.group(1).strip(): - userid = await e.client.parse_id(e.pattern_match.group(1).strip()) - elif e.is_private: - userid = e.chat_id - else: - return await xx.edit("`Reply to some msg or add their id.`", time=5) - name = (await e.client.get_entity(userid)).first_name - chats = 0 - if userid == ultroid_bot.uid: - return await xx.eor("`I can't gkick myself.`", time=3) - if userid in DEVLIST: - return await xx.eor("`I can't gkick my Developers.`", time=3) - if e.client._dialogs: - dialog = e.client._dialogs - else: - dialog = await e.client.get_dialogs() - e.client._dialogs.extend(dialog) - for gkick in dialog: - if gkick.is_group or gkick.is_channel: - try: - await e.client.kick_participant(gkick.id, userid) - chats += 1 - except BaseException: - pass - await xx.edit(f"`Gkicked` [{name}](tg://user?id={userid}) `in {chats} chats.`") - - -@ultroid_cmd(pattern="gmute( (.*)|$)", fullsudo=True) -async def _(e): - xx = await e.eor("`Gmuting...`") - if e.reply_to_msg_id: - userid = (await e.get_reply_message()).sender_id - elif e.pattern_match.group(1).strip(): - userid = await e.client.parse_id(e.pattern_match.group(1).strip()) - elif e.is_private: - userid = e.chat_id - else: - return await xx.eor("`Reply to some msg or add their id.`", tome=5, time=5) - name = await e.client.get_entity(userid) - chats = 0 - if userid == ultroid_bot.uid: - return await xx.eor("`I can't gmute myself.`", time=3) - if userid in DEVLIST: - return await xx.eor("`I can't gmute my Developers.`", time=3) - if is_gmuted(userid): - return await xx.eor("`User is already gmuted.`", time=4) - if e.client._dialogs: - dialog = e.client._dialogs - else: - dialog = await e.client.get_dialogs() - e.client._dialogs.extend(dialog) - for onmute in dialog: - if onmute.is_group: - try: - await e.client.edit_permissions(onmute.id, userid, send_messages=False) - chats += 1 - except BaseException: - pass - gmute(userid) - await xx.edit(f"`Gmuted` {inline_mention(name)} `in {chats} chats.`") - - -@ultroid_cmd(pattern="ungmute( (.*)|$)", fullsudo=True) -async def _(e): - xx = await e.eor("`UnGmuting...`") - if e.reply_to_msg_id: - userid = (await e.get_reply_message()).sender_id - elif e.pattern_match.group(1).strip(): - userid = await e.client.parse_id(e.pattern_match.group(1).strip()) - elif e.is_private: - userid = e.chat_id - else: - return await xx.eor("`Reply to some msg or add their id.`", time=5) - name = (await e.client.get_entity(userid)).first_name - chats = 0 - if not is_gmuted(userid): - return await xx.eor("`User is not gmuted.`", time=3) - if e.client._dialogs: - dialog = e.client._dialogs - else: - dialog = await e.client.get_dialogs() - e.client._dialogs.extend(dialog) - for hurr in dialog: - if hurr.is_group: - try: - await e.client.edit_permissions(hurr.id, userid, send_messages=True) - chats += 1 - except BaseException: - pass - ungmute(userid) - await xx.edit(f"`Ungmuted` {inline_mention(name)} `in {chats} chats.`") - - -@ultroid_cmd( - pattern="listgban$", -) -async def list_gengbanned(event): - users = list_gbanned() - x = await event.eor(get_string("com_1")) - msg = "" - if not users: - return await x.edit("`You haven't GBanned anyone!`") - for i in users: - try: - name = await event.client.get_entity(int(i)) - except BaseException: - name = i - msg += f"User: {inline_mention(name, html=True)}\n" - reason = users[i] - msg += f"Reason: {reason}\n\n" if reason is not None else "\n" - gbanned_users = f"List of users GBanned by {OWNER_NAME}:\n\n{msg}" - if len(gbanned_users) > 4096: - with open("gbanned.txt", "w") as f: - f.write( - gbanned_users.replace("", "") - .replace("", "") - .replace("", "") - ) - await x.reply( - file="gbanned.txt", - message=f"List of users GBanned by {inline_mention(ultroid_bot.me)}", - ) - os.remove("gbanned.txt") - await x.delete() - else: - await x.edit(gbanned_users, parse_mode="html") - - -@ultroid_cmd( - pattern="gstat( (.*)|$)", -) -async def gstat_(e): - xx = await e.eor(get_string("com_1")) - if e.is_private: - userid = (await e.get_chat()).id - elif e.reply_to_msg_id: - userid = (await e.get_reply_message()).sender_id - elif e.pattern_match.group(1).strip(): - try: - userid = await e.client.parse_id(e.pattern_match.group(1).strip()) - except Exception as err: - return await xx.eor(f"{err}", time=10) - else: - return await xx.eor("`Reply to some msg or add their id.`", time=5) - name = (await e.client.get_entity(userid)).first_name - msg = f"**{name} is " - is_banned = is_gbanned(userid) - reason = list_gbanned().get(userid) - if is_banned: - msg += "Globally Banned" - msg += f" with reason** `{reason}`" if reason else ".**" - else: - msg += "not Globally Banned.**" - await xx.edit(msg) - - -@ultroid_cmd(pattern="gblacklist$") -async def blacklist_(event): - await gblacker(event, "add") - - -@ultroid_cmd(pattern="ungblacklist$") -async def ungblacker(event): - await gblacker(event, "remove") - - -async def gblacker(event, type_): - try: - chat_id = int(event.text.split(maxsplit=1)[1]) - try: - chat_id = (await event.client.get_entity(chat_id)).id - except Exception as e: - return await event.eor(f"**ERROR**\n`{str(e)}`") - except IndexError: - chat_id = event.chat_id - if type_ == "add": - keym.add(chat_id) - elif type_ == "remove": - keym.remove(chat_id) - await event.eor(f"Global Broadcasts: \n{type_}ed {chat_id}") diff --git a/plugins/greetings.py b/plugins/greetings.py deleted file mode 100644 index 27053eff30..0000000000 --- a/plugins/greetings.py +++ /dev/null @@ -1,201 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - ----- Welcomes ---- -• `{i}setwelcome ` - Set welcome message in the current chat. - -• `{i}clearwelcome` - Delete the welcome in the current chat. - -• `{i}getwelcome` - Get the welcome message in the current chat. - ----- GoodByes ---- -• `{i}setgoodbye ` - Set goodbye message in the current chat. - -• `{i}cleargoodbye` - Delete the goodbye in the current chat. - -• `{i}getgoodbye` - Get the goodbye message in the current chat. - -• `{i}thankmembers on/off` - Send a thank you sticker on hitting a members count of 100*x in your groups. -""" -import os - -from . import upload_file as uf -from telethon.utils import pack_bot_file_id - -from pyUltroid.dB.greetings_db import ( - add_goodbye, - add_thanks, - add_welcome, - delete_goodbye, - delete_welcome, - get_goodbye, - get_welcome, - must_thank, - remove_thanks, -) -from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button - -from . import HNDLR, eor, get_string, mediainfo, ultroid_cmd -from ._inline import something - -Note = "\n\nNote: `{mention}`, `{group}`, `{count}`, `{name}`, `{fullname}`, `{username}`, `{userid}` can be used as formatting parameters.\n\n" - - -@ultroid_cmd(pattern="setwelcome", groups_only=True) -async def setwel(event): - x = await event.eor(get_string("com_1")) - r = await event.get_reply_message() - btn = format_btn(r.buttons) if (r and r.buttons) else None - try: - text = event.text.split(maxsplit=1)[1] - except IndexError: - text = r.text if r else None - if r and r.media: - wut = mediainfo(r.media) - if wut.startswith(("pic", "gif")): - dl = await r.download_media() - m = uf(dl) - os.remove(dl) - elif wut == "video": - if r.media.document.size > 8 * 1000 * 1000: - return await eor(x, get_string("com_4"), time=5) - dl = await r.download_media() - m = uf(dl) - os.remove(dl) - elif wut == "web": - m = None - else: - m = pack_bot_file_id(r.media) - if r.text: - txt = r.text - if not btn: - txt, btn = get_msg_button(r.text) - add_welcome(event.chat_id, txt, m, btn) - else: - add_welcome(event.chat_id, None, m, btn) - await eor(x, get_string("grt_1")) - elif text: - if not btn: - txt, btn = get_msg_button(text) - add_welcome(event.chat_id, txt, None, btn) - await eor(x, get_string("grt_1")) - else: - await eor(x, get_string("grt_3"), time=5) - - -@ultroid_cmd(pattern="clearwelcome$", groups_only=True) -async def clearwel(event): - if not get_welcome(event.chat_id): - return await event.eor(get_string("grt_4"), time=5) - delete_welcome(event.chat_id) - await event.eor(get_string("grt_5"), time=5) - - -@ultroid_cmd(pattern="getwelcome$", groups_only=True) -async def listwel(event): - wel = get_welcome(event.chat_id) - if not wel: - return await event.eor(get_string("grt_4"), time=5) - msgg, med = wel["welcome"], wel["media"] - if wel.get("button"): - btn = create_tl_btn(wel["button"]) - return await something(event, msgg, med, btn) - await event.reply(f"**Welcome Note in this chat**\n\n`{msgg}`", file=med) - await event.delete() - - -@ultroid_cmd(pattern="setgoodbye", groups_only=True) -async def setgb(event): - x = await event.eor(get_string("com_1")) - r = await event.get_reply_message() - btn = format_btn(r.buttons) if (r and r.buttons) else None - try: - text = event.text.split(maxsplit=1)[1] - except IndexError: - text = r.text if r else None - if r and r.media: - wut = mediainfo(r.media) - if wut.startswith(("pic", "gif")): - dl = await r.download_media() - m = uf(dl) - os.remove(dl) - elif wut == "video": - if r.media.document.size > 8 * 1000 * 1000: - return await eor(x, get_string("com_4"), time=5) - dl = await r.download_media() - m = uf(dl) - os.remove(dl) - elif wut == "web": - m = None - else: - m = pack_bot_file_id(r.media) - if r.text: - txt = r.text - if not btn: - txt, btn = get_msg_button(r.text) - add_goodbye(event.chat_id, txt, m, btn) - else: - add_goodbye(event.chat_id, None, m, btn) - await eor(x, "`Goodbye note saved`") - elif text: - if not btn: - txt, btn = get_msg_button(text) - add_goodbye(event.chat_id, txt, None, btn) - await eor(x, "`Goodbye note saved`") - else: - await eor(x, get_string("grt_7"), time=5) - - -@ultroid_cmd(pattern="cleargoodbye$", groups_only=True) -async def clearwgb(event): - if not get_goodbye(event.chat_id): - return await event.eor(get_string("grt_6"), time=5) - delete_goodbye(event.chat_id) - await event.eor("`Goodbye Note Deleted`", time=5) - - -@ultroid_cmd(pattern="getgoodbye$", groups_only=True) -async def listgd(event): - wel = get_goodbye(event.chat_id) - if not wel: - return await event.eor(get_string("grt_6"), time=5) - msgg = wel["goodbye"] - med = wel["media"] - if wel.get("button"): - btn = create_tl_btn(wel["button"]) - return await something(event, msgg, med, btn) - await event.reply(f"**Goodbye Note in this chat**\n\n`{msgg}`", file=med) - await event.delete() - - -@ultroid_cmd(pattern="thankmembers (on|off)", groups_only=True) -async def thank_set(event): - type_ = event.pattern_match.group(1).strip() - if not type_ or type_ == "": - await eor( - event, - f"**Current Chat Settings:**\n**Thanking Members:** `{must_thank(event.chat_id)}`\n\nUse `{HNDLR}thankmembers on` or `{HNDLR}thankmembers off` to toggle current settings!", - ) - return - chat = event.chat_id - if type_.lower() == "on": - add_thanks(chat) - elif type_.lower() == "off": - remove_thanks(chat) - await eor( - event, - f"**Done! Thank you members has been turned** `{type_.lower()}` **for this chat**!", - ) diff --git a/plugins/imagetools.py b/plugins/imagetools.py deleted file mode 100644 index 22fd011823..0000000000 --- a/plugins/imagetools.py +++ /dev/null @@ -1,292 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}border ` - To create border around that media.. - Ex - `{i}border 12,22,23` - - `{i}border 12,22,23 ; width (in number)` - -• `{i}grey ` - To make it black nd white. - -• `{i}color ` - To make it Colorfull. - -• `{i}toon ` - To make it toon. - -• `{i}danger ` - To make it look Danger. - -• `{i}negative ` - To make negative image. - -• `{i}blur ` - To make it blurry. - -• `{i}quad ` - create a Vortex. - -• `{i}mirror ` - To create mirror pic. - -• `{i}flip ` - To make it flip. - -• `{i}sketch ` - To draw its sketch. - -• `{i}blue ` - just cool. - -• `{i}csample ` - example : `{i}csample red` - `{i}csample #ffffff` - -• `{i}pixelator ` - Create a Pixelated Image.. -""" -import os - -from . import LOGS, con - -try: - import cv2 -except ImportError: - LOGS.error(f"{__file__}: OpenCv not Installed.") - -import numpy as np - -try: - from PIL import Image -except ImportError: - Image = None - LOGS.info(f"{__file__}: PIL not Installed.") - -from . import upload_file as upf -from telethon.errors.rpcerrorlist import ( - ChatSendMediaForbiddenError, - MessageDeleteForbiddenError, -) - -from . import ( - Redis, - async_searcher, - download_file, - get_string, - requests, - udB, - ultroid_cmd, -) - - -@ultroid_cmd(pattern="color$") -async def _(event): - reply = await event.get_reply_message() - if not (reply and reply.media): - return await event.eor("`Reply To a Black and White Image`") - xx = await event.eor("`Coloring image 🎨🖌️...`") - image = await reply.download_media() - img = cv2.VideoCapture(image) - ret, frame = img.read() - cv2.imwrite("ult.jpg", frame) - if udB.get_key("DEEP_API"): - key = Redis("DEEP_API") - else: - key = "quickstart-QUdJIGlzIGNvbWluZy4uLi4K" - r = requests.post( - "https://api.deepai.org/api/colorizer", - files={"image": open("ult.jpg", "rb")}, - headers={"api-key": key}, - ) - os.remove("ult.jpg") - os.remove(image) - if "status" in r.json(): - return await event.edit( - r.json()["status"] + "\nGet api nd set `{i}setdb DEEP_API key`" - ) - r_json = r.json()["output_url"] - await event.client.send_file(event.chat_id, r_json, reply_to=reply) - await xx.delete() - - -@ultroid_cmd(pattern="(grey|blur|negative|danger|mirror|quad|sketch|flip|toon)$") -async def ult_tools(event): - match = event.pattern_match.group(1) - ureply = await event.get_reply_message() - if not (ureply and (ureply.media)): - await event.eor(get_string("cvt_3")) - return - ultt = await ureply.download_media() - xx = await event.eor(get_string("com_1")) - if ultt.endswith(".tgs"): - xx = await xx.edit(get_string("sts_9")) - file = await con.convert(ultt, convert_to="png", outname="ult") - ult = cv2.imread(file) - if match == "grey": - ultroid = cv2.cvtColor(ult, cv2.COLOR_BGR2GRAY) - elif match == "blur": - ultroid = cv2.GaussianBlur(ult, (35, 35), 0) - elif match == "negative": - ultroid = cv2.bitwise_not(ult) - elif match == "danger": - dan = cv2.cvtColor(ult, cv2.COLOR_BGR2RGB) - ultroid = cv2.cvtColor(dan, cv2.COLOR_HSV2BGR) - elif match == "mirror": - ish = cv2.flip(ult, 1) - ultroid = cv2.hconcat([ult, ish]) - elif match == "flip": - trn = cv2.flip(ult, 1) - ish = cv2.rotate(trn, cv2.ROTATE_180) - ultroid = cv2.vconcat([ult, ish]) - elif match == "quad": - ult = cv2.imread(file) - roid = cv2.flip(ult, 1) - mici = cv2.hconcat([ult, roid]) - fr = cv2.flip(mici, 1) - trn = cv2.rotate(fr, cv2.ROTATE_180) - ultroid = cv2.vconcat([mici, trn]) - elif match == "sketch": - gray_image = cv2.cvtColor(ult, cv2.COLOR_BGR2GRAY) - inverted_gray_image = 255 - gray_image - blurred_img = cv2.GaussianBlur(inverted_gray_image, (21, 21), 0) - inverted_blurred_img = 255 - blurred_img - ultroid = cv2.divide(gray_image, inverted_blurred_img, scale=256.0) - elif match == "toon": - height, width, _ = ult.shape - samples = np.zeros([height * width, 3], dtype=np.float32) - count = 0 - for x in range(height): - for y in range(width): - samples[count] = ult[x][y] - count += 1 - _, labels, centers = cv2.kmeans( - samples, - 12, - None, - (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001), - 5, - cv2.KMEANS_PP_CENTERS, - ) - centers = np.uint8(centers) - ish = centers[labels.flatten()] - ultroid = ish.reshape(ult.shape) - cv2.imwrite("ult.jpg", ultroid) - await ureply.reply( - file="ult.jpg", - force_document=False, - ) - await xx.delete() - os.remove("ult.jpg") - os.remove(file) - - -@ultroid_cmd(pattern="csample (.*)") -async def sampl(ult): - if color := ult.pattern_match.group(1).strip(): - img = Image.new("RGB", (200, 100), f"{color}") - img.save("csample.png") - try: - try: - await ult.delete() - await ult.respond(f"Colour Sample for `{color}` !", file="csample.png") - except MessageDeleteForbiddenError: - await ult.reply(f"Colour Sample for `{color}` !", file="csample.png") - except ChatSendMediaForbiddenError: - await ult.eor("Umm! Sending Media is disabled here!") - - else: - await ult.eor("Wrong Color Name/Hex Code specified!") - - -@ultroid_cmd( - pattern="blue$", -) -async def ultd(event): - ureply = await event.get_reply_message() - xx = await event.eor("`...`") - if not (ureply and (ureply.media)): - await xx.edit(get_string("cvt_3")) - return - ultt = await ureply.download_media() - if ultt.endswith(".tgs"): - await xx.edit(get_string("sts_9")) - file = await con.convert(ultt, convert_to="png", outname="ult") - lnk = upf(file) - r = await async_searcher( - f"https://nekobot.xyz/api/imagegen?type=blurpify&image={lnk}", re_json=True - ) - ms = r.get("message") - if not r["success"]: - return await xx.edit(ms) - await download_file(ms, "ult.png") - img = Image.open("ult.png").convert("RGB") - img.save("ult.webp", "webp") - await event.client.send_file( - event.chat_id, - "ult.webp", - force_document=False, - reply_to=event.reply_to_msg_id, - ) - await xx.delete() - os.remove("ult.png") - os.remove("ult.webp") - os.remove(ultt) - - -@ultroid_cmd(pattern="border( (.*)|$)") -async def ok(event): - hm = await event.get_reply_message() - if not (hm and (hm.photo or hm.sticker)): - return await event.eor("`Reply to Sticker or Photo..`") - col = event.pattern_match.group(1).strip() - wh = 20 - if not col: - col = [255, 255, 255] - else: - try: - if ";" in col: - col_ = col.split(";", maxsplit=1) - wh = int(col_[1]) - col = col_[0] - col = [int(col) for col in col.split(",")[:2]] - except ValueError: - return await event.eor("`Not a Valid Input...`") - okla = await hm.download_media() - img1 = cv2.imread(okla) - constant = cv2.copyMakeBorder(img1, wh, wh, wh, wh, cv2.BORDER_CONSTANT, value=col) - cv2.imwrite("output.png", constant) - await event.client.send_file(event.chat.id, "output.png") - os.remove("output.png") - os.remove(okla) - await event.delete() - - -@ultroid_cmd(pattern="pixelator( (.*)|$)") -async def pixelator(event): - reply_message = await event.get_reply_message() - if not (reply_message and (reply_message.photo or reply_message.sticker)): - return await event.eor("`Reply to a photo`") - hw = 50 - try: - hw = int(event.pattern_match.group(1).strip()) - except (ValueError, TypeError): - pass - msg = await event.eor(get_string("com_1")) - image = await reply_message.download_media() - input_ = cv2.imread(image) - height, width = input_.shape[:2] - w, h = (hw, hw) - temp = cv2.resize(input_, (w, h), interpolation=cv2.INTER_LINEAR) - output = cv2.resize(temp, (width, height), interpolation=cv2.INTER_NEAREST) - cv2.imwrite("output.jpg", output) - await msg.respond("• Pixelated by Ultroid", file="output.jpg") - await msg.delete() - os.remove("output.jpg") - os.remove(image) diff --git a/plugins/locks.py b/plugins/locks.py deleted file mode 100644 index edbdc6b48d..0000000000 --- a/plugins/locks.py +++ /dev/null @@ -1,39 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}lock ` - Lock the Used Setting in Used Group. - -• `{i}unlock ` - UNLOCK the Used Setting in Used Group. -""" -from telethon.tl.functions.messages import EditChatDefaultBannedRightsRequest - -from pyUltroid.fns.admins import lock_unlock - -from . import ultroid_cmd - - -@ultroid_cmd( - pattern="(un|)lock( (.*)|$)", admins_only=True, manager=True, require="change_info" -) -async def un_lock(e): - mat = e.pattern_match.group(2).strip() - if not mat: - return await e.eor("`Give some Proper Input..`", time=5) - lock = e.pattern_match.group(1) == "" - ml = lock_unlock(mat, lock) - if not ml: - return await e.eor("`Incorrect Input`", time=5) - msg = "Locked" if lock else "Unlocked" - try: - await e.client(EditChatDefaultBannedRightsRequest(e.chat_id, ml)) - except Exception as er: - return await e.eor(str(er)) - await e.eor(f"**{msg}** - `{mat}` ! ") diff --git a/plugins/logo.py b/plugins/logo.py deleted file mode 100644 index 551104f6d5..0000000000 --- a/plugins/logo.py +++ /dev/null @@ -1,101 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}logo ` - Generate a logo of the given Text - Or Reply To image , to write ur text on it. - Or Reply To Font File, To write with that font. - -""" -import glob -import os -import random - -from telethon.tl.types import InputMessagesFilterPhotos - -try: - from PIL import Image -except ImportError: - Image = None -from pyUltroid.fns.misc import unsplashsearch -from pyUltroid.fns.tools import LogoHelper - -from . import OWNER_ID, OWNER_NAME, download_file, get_string, mediainfo, ultroid_cmd - - -@ultroid_cmd(pattern="logo( (.*)|$)") -async def logo_gen(event): - xx = await event.eor(get_string("com_1")) - name = event.pattern_match.group(1).strip() - if not name: - return await xx.eor("`Give a name too!`", time=5) - bg_, font_ = None, None - if event.reply_to_msg_id: - temp = await event.get_reply_message() - if temp.media: - if hasattr(temp.media, "document") and ( - ("font" in temp.file.mime_type) - or (".ttf" in temp.file.name) - or (".otf" in temp.file.name) - ): - font_ = await temp.download_media("resources/fonts/") - elif "pic" in mediainfo(temp.media): - bg_ = await temp.download_media() - if not bg_: - SRCH = [ - "background", - "neon", - "anime", - "art", - "bridges", - "streets", - "computer", - "cyberpunk", - "nature", - "abstract", - "exoplanet", - "magic", - "3d render", - ] - res = await unsplashsearch(random.choice(SRCH), limit=1) - bg_, _ = await download_file(res[0], "resources/downloads/logo.png") - newimg = "resources/downloads/unsplash-temp.jpg" - img_ = Image.open(bg_) - img_.save(newimg) - os.remove(bg_) - bg_ = newimg - - if not font_: - fpath_ = glob.glob("resources/fonts/*") - font_ = random.choice(fpath_) - if len(name) <= 8: - strke = 10 - elif len(name) >= 9: - strke = 5 - else: - strke = 20 - name = LogoHelper.make_logo( - bg_, - name, - font_, - fill="white", - stroke_width=strke, - stroke_fill="black", - ) - await xx.edit("`Done!`") - await event.client.send_file( - event.chat_id, - file=name, - caption=f"Logo by [{OWNER_NAME}](tg://user?id={OWNER_ID})", - force_document=True, - ) - os.remove(name) - await xx.delete() - if os.path.exists(bg_): - os.remove(bg_) diff --git a/plugins/mediatools.py b/plugins/mediatools.py index 4e8667b8f5..17ef988232 100644 --- a/plugins/mediatools.py +++ b/plugins/mediatools.py @@ -7,9 +7,6 @@ """ ✘ Commands Available - -• `{i}mediainfo //` - To get info about it. - • `{i}rotate ` Rotate any video/photo/media.. Note : for video it should be angle of 90's diff --git a/plugins/misc.py b/plugins/misc.py deleted file mode 100644 index e82b6702e9..0000000000 --- a/plugins/misc.py +++ /dev/null @@ -1,140 +0,0 @@ -# Ultroid - UserBot -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}eod` - `Get Event of the Today` - -• `{i}pntrst ` - Download and send pinterest pins - -• `{i}gadget ` - Gadget Search from Telegram. - -• `{i}randomuser` - Generate details about a random user. - -• `{i}ascii ` - Convert replied image into html. -""" - -import os -from datetime import datetime as dt - -from bs4 import BeautifulSoup as bs - -try: - from htmlwebshot import WebShot -except ImportError: - WebShot = None -try: - from img2html.converter import Img2HTMLConverter -except ImportError: - Img2HTMLConverter = None - -from . import async_searcher, get_random_user_data, get_string, re, ultroid_cmd - - -@ultroid_cmd(pattern="eod$") -async def diela(e): - m = await e.eor(get_string("com_1")) - li = "https://daysoftheyear.com" - te = "🎊 **Events of the Day**\n\n" - da = dt.now() - month = da.strftime("%b") - li += f"/days/{month}/" + da.strftime("%F").split("-")[2] - ct = await async_searcher(li, re_content=True) - bt = bs(ct, "html.parser", from_encoding="utf-8") - ml = bt.find_all("a", "js-link-target", href=re.compile("daysoftheyear.com/days")) - for eve in ml[:5]: - te += f'• [{eve.text}]({eve["href"]})\n' - await m.edit(te, link_preview=False) - - -@ultroid_cmd( - pattern="pntrst( (.*)|$)", -) -async def pinterest(e): - m = e.pattern_match.group(1).strip() - if not m: - return await e.eor("`Give pinterest link.`", time=3) - soup = await async_searcher( - "https://www.expertstool.com/download-pinterest-video/", - data={"url": m}, - post=True, - ) - try: - _soup = bs(soup, "html.parser").find("table").tbody.find_all("tr") - except BaseException: - return await e.eor("`Wrong link or private pin.`", time=5) - file = _soup[1] if len(_soup) > 1 else _soup[0] - file = file.td.a["href"] - await e.client.send_file(e.chat_id, file, caption=f"Pin:- {m}") - - -@ultroid_cmd(pattern="gadget( (.*)|$)") -async def mobs(e): - mat = e.pattern_match.group(1).strip() - if not mat: - await e.eor("Please Give a Mobile Name to look for.") - query = mat.replace(" ", "%20") - jwala = f"https://gadgets.ndtv.com/search?searchtext={query}" - c = await async_searcher(jwala) - b = bs(c, "html.parser", from_encoding="utf-8") - co = b.find_all("div", "rvw-imgbox") - if not co: - return await e.eor("No Results Found!") - bt = await e.eor(get_string("com_1")) - out = "**📱 Mobile / Gadgets Search**\n\n" - li = co[0].find("a") - imu, title = None, li.find("img")["title"] - cont = await async_searcher(li["href"]) - nu = bs(cont, "html.parser", from_encoding="utf-8") - req = nu.find_all("div", "_pdsd") - imu = nu.find_all( - "img", src=re.compile("https://i.gadgets360cdn.com/products/large/") - ) - if imu: - imu = imu[0]["src"].split("?")[0] + "?downsize=*:420&output-quality=80" - out += f"☑️ **[{title}]({li['href']})**\n\n" - for fp in req: - ty = fp.findNext() - out += f"- **{ty.text}** - `{ty.findNext().text}`\n" - out += "_" - if imu == []: - imu = None - await e.reply(out, file=imu, link_preview=False) - await bt.delete() - - -@ultroid_cmd(pattern="randomuser") -async def _gen_data(event): - x = await event.eor(get_string("com_1")) - msg, pic = await get_random_user_data() - await event.reply(file=pic, message=msg) - await x.delete() - - -@ultroid_cmd( - pattern="ascii( (.*)|$)", -) -async def _(e): - if not Img2HTMLConverter: - return await e.eor("'img2html-converter' not installed!") - if not e.reply_to_msg_id: - return await e.eor(get_string("ascii_1")) - m = await e.eor(get_string("ascii_2")) - img = await (await e.get_reply_message()).download_media() - char = e.pattern_match.group(1).strip() or "■" - converter = Img2HTMLConverter(char=char) - html = converter.convert(img) - shot = WebShot(quality=85) - pic = await shot.create_pic_async(html=html) - await m.delete() - await e.reply(file=pic) - os.remove(pic) - os.remove(img) diff --git a/plugins/mute.py b/plugins/mute.py deleted file mode 100644 index cb7c3d6937..0000000000 --- a/plugins/mute.py +++ /dev/null @@ -1,210 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}mute ` - Mute user in current chat. - -• `{i}unmute ` - Unmute user in current chat. - -• `{i}dmute ` - Mute user in current chat by deleting msgs. - -• `{i}undmute ` - Unmute dmuted user in current chat. - -• `{i}tmute {title}\n" - await uu.edit(a, parse_mode="html") - - except Exception as e: - await uu.edit(f"`Error: {str(e)}`\nTry again later.") - -@ultroid_cmd(pattern="wall( (.*)|$)") -async def wall(event): - inp = event.pattern_match.group(1).strip() - if not inp: - return await event.eor("`Give me something to search..`") - nn = await event.eor(get_string("com_1")) - query = f"hd {inp}" - images = await get_google_images(query) - for z in range(5): - await event.client.send_file(event.chat_id, file=images[z]["original"]) - await nn.delete() - - -@ultroid_cmd(pattern="q( (.*)|$)", manager=True, allow_pm=True) -async def quott_(event): - match = event.pattern_match.group(1).strip() - if not event.is_reply: - return await event.eor("`Reply to Message..`") - msg = await event.eor(get_string("com_1")) - reply = await event.get_reply_message() - replied_to, reply_ = None, None - if match: - spli_ = match.split(maxsplit=1) - if (spli_[0] in ["r", "reply"]) or ( - spli_[0].isdigit() and int(spli_[0]) in range(1, 21) - ): - if spli_[0].isdigit(): - if not event.client._bot: - reply_ = await event.client.get_messages( - event.chat_id, - min_id=event.reply_to_msg_id - 1, - reverse=True, - limit=int(spli_[0]), - ) - else: - id_ = reply.id - reply_ = [] - for msg_ in range(id_, id_ + int(spli_[0])): - msh = await event.client.get_messages(event.chat_id, ids=msg_) - if msh: - reply_.append(msh) - else: - replied_to = await reply.get_reply_message() - try: - match = spli_[1] - except IndexError: - match = None - user = None - if not reply_: - reply_ = reply - if match: - match = match.split(maxsplit=1) - if match: - if match[0].startswith("@") or match[0].isdigit(): - try: - match_ = await event.client.parse_id(match[0]) - user = await event.client.get_entity(match_) - except ValueError: - pass - match = match[1] if len(match) == 2 else None - else: - match = match[0] - if match == "random": - match = choice(all_col) - try: - file = await quotly.create_quotly( - reply_, bg=match, reply=replied_to, sender=user - ) - except Exception as er: - return await msg.edit(str(er)) - message = await reply.reply("Quotly by Ultroid", file=file) - os.remove(file) - await msg.delete() - return message diff --git a/plugins/stickers.py b/plugins/stickers.py new file mode 100644 index 0000000000..1bea5ac772 --- /dev/null +++ b/plugins/stickers.py @@ -0,0 +1,227 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2023 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +❍ Commands Available - + +• `{i}kang ` + Kang the sticker (add to your pack). +""" + +import contextlib +import os +import random, string +from secrets import token_hex + +from telethon import errors +from telethon.errors.rpcerrorlist import StickersetInvalidError +from telethon.tl.functions.messages import GetStickerSetRequest as GetSticker +from telethon.tl.functions.messages import UploadMediaRequest +from telethon.tl.functions.stickers import AddStickerToSetRequest as AddSticker +from telethon.tl.functions.stickers import CreateStickerSetRequest +from telethon.tl.types import ( + DocumentAttributeSticker, + InputPeerSelf, + InputStickerSetEmpty, +) +from telethon.errors.rpcerrorlist import ChatSendInlineForbiddenError +from telethon.tl.types import InputStickerSetItem as SetItem +from telethon.tl.types import InputStickerSetShortName, User +from telethon.utils import get_display_name, get_extension, get_input_document +from telethon.errors import PeerIdInvalidError + +from . import LOGS, asst, fetch, udB, ultroid_cmd, get_string,resize_photo_sticker,quotly + +async def packExists(packId): + source = await fetch(f"https://t.me/addstickers/{packId}") + return ( + not b"""
+ A Telegram user has created the Sticker Set. +
""" + in source + ) + +async def GetUniquePackName(): + packName = f"{random.choice(string.ascii_lowercase)}{token_hex(random.randint(4, 8))}_by_{asst.me.username}" + return await GetUniquePackName() if await packExists(packName) else packName + + +# TODO: simplify if possible + +def getName(sender, packType: str): + title = f"{get_display_name(sender)}'s Kang Pack" + if packType != "static": + title += f" ({packType.capitalize()})" + return title + +async def AddToNewPack(file, emoji, sender_id, title: str): + sn = await GetUniquePackName() + return await asst( + CreateStickerSetRequest( + user_id=sender_id, + title=title, + short_name=sn, + stickers=[SetItem(file, emoji=emoji)], + software="@TeamUltroid", + ) + ) + +async def inline_query_fallback(ult): + try: + result = await ult.client.inline_query(asst.me.username, "startbot") + if result: + await result[0].click(ult.chat_id, hide_via=True) + except (ChatSendInlineForbiddenError): + await ult.eor( + f"Inline mode is disabled in this chat.\n\n" + f"To create or manage your sticker pack, you need to start the assistant bot first.\n\n" + f"Click the button below to start it:\n" + f"[Start Bot](https://t.me/{asst.me.username})", + parse_mode="md" + ) + return + +@ultroid_cmd(pattern="kang", manager=True) +async def kang_func(ult): + """kang (reply message) + Create sticker and add to pack""" + sender = await ult.get_sender() + if not isinstance(sender, User): + return + sender_id = sender.id + if not ult.is_reply: + return await ult.eor("`Reply to a message..`", time=5) + try: + emoji = ult.text.split(maxsplit=1)[1] + except IndexError: + emoji = None + reply = await ult.get_reply_message() + ult = await ult.eor(get_string("com_1")) + type_, dl = "static", None + if reply.sticker: + file = get_input_document(reply.sticker) + if not emoji: + emoji = reply.file.emoji + name = reply.file.name + ext = get_extension(reply.media) + attr = list( + filter( + lambda prop: isinstance(prop, DocumentAttributeSticker), + reply.document.attributes, + ) + ) + inPack = attr and not isinstance(attr[0].stickerset, InputStickerSetEmpty) + with contextlib.suppress(KeyError): + type_ = {".webm": "video", ".tgs": "animated"}[ext] + if type_ or not inPack: + dl = await reply.download_media() + elif reply.photo: + dl = await reply.download_media() + name = "sticker.webp" + image = resize_photo_sticker(dl) + image.save(name, "WEBP") + try: + os.remove(dl) + except: + pass + dl = name + elif reply.text: + try: + reply = await ult.get_reply_message() + replied_to = await reply.get_reply_message() + sender_user = await ult.client.get_entity(reply.sender_id) + quotly_file = await quotly.create_quotly( + reply, bg="black", reply=replied_to, sender=sender_user) + except Exception as er: + return await ult.edit(f"Quotly error: {er}") + message = await reply.reply("Quotly by Ultroid", file=quotly_file) + dl = quotly_file + else: + return await ult.eor("`Reply to sticker or text to add it in your pack...`") + if not emoji: + emoji = "🏵" + if dl: + upl = await asst.upload_file(dl) + file = get_input_document(await asst(UploadMediaRequest(InputPeerSelf(), upl))) + try: + os.remove(dl) + except: + pass + get_ = udB.get_key("STICKERS") or {} + title = getName(sender, type_) + if not get_.get(sender_id) or not get_.get(sender_id, {}).get(type_): + try: + pack = await AddToNewPack(file, emoji, sender.id, title) + except (ValueError, PeerIdInvalidError) as e: + await inline_query_fallback(ult) + return + except Exception as er: + return await ult.eor(str(er)) + sn = pack.set.short_name + if not get_.get(sender_id): + get_.update({sender_id: {type_: [sn]}}) + else: + get_[sender_id].update({type_: [sn]}) + udB.set_key("STICKERS", get_) + return await ult.edit( + f"**Kanged Successfully!\nEmoji :** {emoji}\n**Link :** [Click Here](https://t.me/addstickers/{sn})", + link_preview=False + ) + name = get_[sender_id][type_][-1] + try: + await asst(GetSticker(InputStickerSetShortName(name), hash=0)) + except StickersetInvalidError: + get_[sender_id][type_].remove(name) + try: + await asst( + AddSticker(InputStickerSetShortName(name), SetItem(file, emoji=emoji)) + ) + except (errors.StickerpackStickersTooMuchError, errors.StickersTooMuchError): + try: + pack = await AddToNewPack(file, emoji, sender.id, title) + sn = pack.set.short_name + except (ValueError, PeerIdInvalidError) as e: + await inline_query_fallback(ult) + return + except Exception as er: + return await ult.eor(str(er)) + get_[sender_id][type_].append(pack.set.short_name) + udB.set_key("STICKERS", get_) + return await ult.edit( + f"**Created New Kang Pack!\nEmoji :** {emoji}\n**Link :** [Click Here](https://t.me/addstickers/{sn})", + link_preview=False + ) + except Exception as er: + LOGS.exception(er) + return await ult.edit(str(er)) + await ult.edit( + f"Sticker Added to Pack Successfully\n**Link :** [Click Here](https://t.me/addstickers/{name})", + link_preview=False + ) + + +@ultroid_cmd(pattern="listpack", manager=True) +async def do_magic(ult): + """Get list of sticker packs.""" + ko = udB.get_key("STICKERS") or {} + if not ko.get(ult.sender_id): + return await ult.reply("No Sticker Pack Found!") + al_ = [] + ul = ko[ult.sender_id] + for _ in ul.keys(): + al_.extend(ul[_]) + msg = "• **Stickers Owned by You!**\n\n" + for _ in al_: + try: + pack = await ult.client(GetSticker(InputStickerSetShortName(_), hash=0)) + msg += f"• [{pack.set.title}](https://t.me/addstickers/{_})\n" + except StickersetInvalidError: + for type_ in ["animated", "video", "static"]: + if ul.get(type_) and _ in ul[type_]: + ul[type_].remove(_) + udB.set_key("STICKERS", ko) + await ult.reply(msg) diff --git a/plugins/stickertools.py b/plugins/stickertools.py deleted file mode 100644 index 95839b2da7..0000000000 --- a/plugins/stickertools.py +++ /dev/null @@ -1,531 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -❍ Commands Available - - -• `{i}destroy ` - To destroy the sticker. - -• `{i}tiny ` - To create Tiny stickers. - -• `{i}kang ` - Kang the sticker (add to your pack). - -• `{i}packkang ` - Kang the Complete sticker set (with custom name). - -• `{i}round ` - To extract round sticker. -""" -import glob -import io -import os -import random -from os import remove - -try: - import cv2 -except ImportError: - cv2 = None -try: - import numpy as np -except ImportError: - np = None -try: - from PIL import Image, ImageDraw -except ImportError: - pass - -from telethon.errors import PeerIdInvalidError, YouBlockedUserError -from telethon.tl.functions.messages import UploadMediaRequest -from telethon.tl.types import ( - DocumentAttributeFilename, - DocumentAttributeSticker, - InputPeerSelf, -) -from telethon.utils import get_input_document - -from . import ( - KANGING_STR, - LOGS, - asst, - async_searcher, - bash, - con, - functions, - get_string, - inline_mention, - mediainfo, - ultroid_cmd, - quotly, - types, - udB, -) - - -@ultroid_cmd(pattern="packkang") -async def pack_kangish(_): - _e = await _.get_reply_message() - local = None - try: - cmdtext = _.text.split(maxsplit=1)[1] - except IndexError: - cmdtext = None - if cmdtext and os.path.isdir(cmdtext): - local = True - elif not (_e and _e.sticker and _e.file.mime_type == "image/webp"): - return await _.eor(get_string("sts_4")) - msg = await _.eor(get_string("com_1")) - _packname = cmdtext or f"Ultroid Kang Pack By {_.sender_id}" - typee = None - if not local: - _id = _e.media.document.attributes[1].stickerset.id - _hash = _e.media.document.attributes[1].stickerset.access_hash - _get_stiks = await _.client( - functions.messages.GetStickerSetRequest( - stickerset=types.InputStickerSetID(id=_id, access_hash=_hash), hash=0 - ) - ) - docs = _get_stiks.documents - else: - docs = [] - files = glob.glob(f"{cmdtext}/*") - exte = files[-1] - if exte.endswith(".tgs"): - typee = "anim" - elif exte.endswith(".webm"): - typee = "vid" - count = 0 - for file in files: - if file.endswith((".tgs", ".webm")): - count += 1 - upl = await asst.upload_file(file) - docs.append(await asst(UploadMediaRequest(InputPeerSelf(), upl))) - if count % 5 == 0: - await msg.edit(f"`Uploaded {count} files.`") - - stiks = [] - for i in docs: - x = get_input_document(i) - stiks.append( - types.InputStickerSetItem( - document=x, - emoji=( - random.choice(["😐", "👍", "😂"]) - if local - else (i.attributes[1]).alt - ), - ) - ) - try: - short_name = "ult_" + _packname.replace(" ", "_") + str(_.id) - _r_e_s = await asst( - functions.stickers.CreateStickerSetRequest( - user_id=_.sender_id, - title=_packname, - short_name=f"{short_name}_by_{asst.me.username}", - animated=typee == "anim", - videos=typee == "vid", - stickers=stiks, - ) - ) - except PeerIdInvalidError: - return await msg.eor( - f"Hey {inline_mention(_.sender)} send `/start` to @{asst.me.username} and later try this command again.." - ) - except BaseException as er: - LOGS.exception(er) - return await msg.eor(str(er)) - await msg.eor( - get_string("sts_5").format(f"https://t.me/addstickers/{_r_e_s.set.short_name}"), - ) - - -@ultroid_cmd( - pattern="kang", -) -async def hehe(args): - ultroid_bot = args.client - xx = await args.eor(get_string("com_1")) - user = ultroid_bot.me - username = user.username - username = f"@{username}" if username else user.first_name - message = await args.get_reply_message() - photo = None - is_anim, is_vid = False, False - emoji = None - if not message: - return await xx.eor(get_string("sts_6")) - if message.photo: - photo = io.BytesIO() - photo = await ultroid_bot.download_media(message.photo, photo) - elif message.file and "image" in message.file.mime_type.split("/"): - photo = io.BytesIO() - await ultroid_bot.download_file(message.media.document, photo) - if ( - DocumentAttributeFilename(file_name="sticker.webp") - in message.media.document.attributes - ): - emoji = message.media.document.attributes[1].alt - - elif message.file and "video" in message.file.mime_type.split("/"): - xy = await message.download_media() - if (message.file.duration or 0) <= 10: - is_vid = True - photo = await con.create_webm(xy) - else: - y = cv2.VideoCapture(xy) - heh, lol = y.read() - cv2.imwrite("ult.webp", lol) - photo = "ult.webp" - elif message.file and "tgsticker" in message.file.mime_type: - await ultroid_bot.download_file( - message.media.document, - "AnimatedSticker.tgs", - ) - attributes = message.media.document.attributes - for attribute in attributes: - if isinstance(attribute, DocumentAttributeSticker): - emoji = attribute.alt - is_anim = True - photo = 1 - elif message.message: - photo = await quotly.create_quotly(message) - else: - return await xx.edit(get_string("com_4")) - if not udB.get_key("language") or udB.get_key("language") == "en": - ra = random.choice(KANGING_STR) - else: - ra = get_string("sts_11") - await xx.edit(f"`{ra}`") - if photo: - splat = args.text.split() - pack = 1 - if not emoji: - emoji = "🏵" - if len(splat) == 3: - pack = splat[2] # User sent ultroid_both - emoji = splat[1] - elif len(splat) == 2: - if splat[1].isnumeric(): - pack = int(splat[1]) - else: - emoji = splat[1] - - packname = f"ult_{user.id}_{pack}" - packnick = f"{username}'s Pack {pack}" - cmd = "/newpack" - file = io.BytesIO() - - if is_vid: - packname += "_vid" - packnick += " (Video)" - cmd = "/newvideo" - elif is_anim: - packname += "_anim" - packnick += " (Animated)" - cmd = "/newanimated" - else: - image = con.resize_photo_sticker(photo) - file.name = "sticker.png" - image.save(file, "PNG") - - response = await async_searcher(f"http://t.me/addstickers/{packname}") - htmlstr = response.split("\n") - - if ( - " A Telegram user has created the Sticker Set." - not in htmlstr - ): - async with ultroid_bot.conversation("@Stickers") as conv: - try: - await conv.send_message("/addsticker") - except YouBlockedUserError: - LOGS.info("Unblocking @Stickers for kang...") - await ultroid_bot(functions.contacts.UnblockRequest("stickers")) - await conv.send_message("/addsticker") - await conv.get_response() - await conv.send_message(packname) - x = await conv.get_response() - if x.text.startswith("Alright! Now send me the video sticker."): - await conv.send_file(photo, force_document=True) - x = await conv.get_response() - t = "50" if (is_anim or is_vid) else "120" - while t in x.message: - pack += 1 - packname = f"ult_{user.id}_{pack}" - packnick = f"{username}'s Pack {pack}" - if is_anim: - packname += "_anim" - packnick += " (Animated)" - elif is_vid: - packnick += " (Video)" - packname += "_vid" - await xx.edit(get_string("sts_13").format(pack)) - await conv.send_message("/addsticker") - await conv.get_response() - await conv.send_message(packname) - x = await conv.get_response() - if x.text.startswith("Alright! Now send me the video sticker."): - await conv.send_file(photo, force_document=True) - x = await conv.get_response() - if x.text in ["Invalid pack selected.", "Invalid set selected."]: - await conv.send_message(cmd) - await conv.get_response() - await conv.send_message(packnick) - await conv.get_response() - if is_anim: - await conv.send_file("AnimatedSticker.tgs") - remove("AnimatedSticker.tgs") - else: - if is_vid: - file = photo - else: - file.seek(0) - await conv.send_file(file, force_document=True) - await conv.get_response() - await conv.send_message(emoji) - await conv.get_response() - await conv.send_message("/publish") - if is_anim: - await conv.get_response() - await conv.send_message(f"<{packnick}>") - await conv.get_response() - await conv.send_message("/skip") - await conv.get_response() - await conv.send_message(packname) - await conv.get_response() - await xx.edit( - get_string("sts_7").format(packname), - parse_mode="md", - ) - return - if is_anim: - await conv.send_file("AnimatedSticker.tgs") - remove("AnimatedSticker.tgs") - elif "send me an emoji" not in x.message: - if is_vid: - file = photo - else: - file.seek(0) - await conv.send_file(file, force_document=True) - rsp = await conv.get_response() - if "Sorry, the file type is invalid." in rsp.text: - await xx.edit( - get_string("sts_8"), - ) - return - await conv.send_message(emoji) - await conv.get_response() - await conv.send_message("/done") - await conv.get_response() - await ultroid_bot.send_read_acknowledge(conv.chat_id) - else: - await xx.edit("`Brewing a new Pack...`") - async with ultroid_bot.conversation("Stickers") as conv: - await conv.send_message(cmd) - await conv.get_response() - await conv.send_message(packnick) - await conv.get_response() - if is_anim: - await conv.send_file("AnimatedSticker.tgs") - remove("AnimatedSticker.tgs") - else: - if is_vid: - file = photo - else: - file.seek(0) - await conv.send_file(file, force_document=True) - rsp = await conv.get_response() - if "Sorry, the file type is invalid." in rsp.text: - await xx.edit( - get_string("sts_8"), - ) - return - await conv.send_message(emoji) - await conv.get_response() - await conv.send_message("/publish") - if is_anim: - await conv.get_response() - await conv.send_message(f"<{packnick}>") - - await conv.get_response() - await conv.send_message("/skip") - await conv.get_response() - await conv.send_message(packname) - await conv.get_response() - await ultroid_bot.send_read_acknowledge(conv.chat_id) - await xx.edit( - get_string("sts_12").format(emoji, packname), - parse_mode="md", - ) - try: - os.remove(photo) - except BaseException: - pass - try: - os.remove("AnimatedSticker.tgs") - except BaseException: - pass - try: - os.remove("ult.webp") - except BaseException: - pass - - -@ultroid_cmd( - pattern="round$", -) -async def ultdround(event): - ureply = await event.get_reply_message() - xx = await event.eor(get_string("com_1")) - if not (ureply and (ureply.media)): - await xx.edit(get_string("sts_10")) - return - ultt = await ureply.download_media() - file = await con.convert( - ultt, - convert_to="png", - allowed_formats=["jpg", "jpeg", "png"], - outname="round", - remove_old=True, - ) - img = Image.open(file).convert("RGB") - npImage = np.array(img) - h, w = img.size - alpha = Image.new("L", img.size, 0) - draw = ImageDraw.Draw(alpha) - draw.pieslice([0, 0, h, w], 0, 360, fill=255) - npAlpha = np.array(alpha) - npImage = np.dstack((npImage, npAlpha)) - Image.fromarray(npImage).save("ult.webp") - await event.client.send_file( - event.chat_id, - "ult.webp", - force_document=False, - reply_to=event.reply_to_msg_id, - ) - await xx.delete() - os.remove(file) - os.remove("ult.webp") - - -@ultroid_cmd( - pattern="destroy$", -) -async def ultdestroy(event): - ult = await event.get_reply_message() - if not (ult and ult.media and "animated" in mediainfo(ult.media)): - return await event.eor(get_string("sts_2")) - await event.client.download_media(ult, "ultroid.tgs") - xx = await event.eor(get_string("com_1")) - await bash("lottie_convert.py ultroid.tgs json.json") - with open("json.json") as json: - jsn = json.read() - jsn = ( - jsn.replace("[100]", "[200]") - .replace("[10]", "[40]") - .replace("[-1]", "[-10]") - .replace("[0]", "[15]") - .replace("[1]", "[20]") - .replace("[2]", "[17]") - .replace("[3]", "[40]") - .replace("[4]", "[37]") - .replace("[5]", "[60]") - .replace("[6]", "[70]") - .replace("[7]", "[40]") - .replace("[8]", "[37]") - .replace("[9]", "[110]") - ) - open("json.json", "w").write(jsn) - file = await con.animated_sticker("json.json", "ultroid.tgs") - if file: - await event.client.send_file( - event.chat_id, - file="ultroid.tgs", - force_document=False, - reply_to=event.reply_to_msg_id, - ) - await xx.delete() - os.remove("json.json") - - -@ultroid_cmd( - pattern="tiny$", -) -async def ultiny(event): - reply = await event.get_reply_message() - if not (reply and (reply.media)): - await event.eor(get_string("sts_10")) - return - xx = await event.eor(get_string("com_1")) - ik = await reply.download_media() - im1 = Image.open("resources/extras/ultroid_blank.png") - if ik.endswith(".tgs"): - await con.animated_sticker(ik, "json.json") - with open("json.json") as json: - jsn = json.read() - jsn = jsn.replace("512", "2000") - open("json.json", "w").write(jsn) - await con.animated_sticker("json.json", "ult.tgs") - file = "ult.tgs" - os.remove("json.json") - elif ik.endswith((".gif", ".webm", ".mp4")): - iik = cv2.VideoCapture(ik) - dani, busy = iik.read() - cv2.imwrite("i.png", busy) - fil = "i.png" - im = Image.open(fil) - z, d = im.size - if z == d: - xxx, yyy = 200, 200 - else: - t = z + d - a = z / t - b = d / t - aa = (a * 100) - 50 - bb = (b * 100) - 50 - xxx = 200 + 5 * aa - yyy = 200 + 5 * bb - k = im.resize((int(xxx), int(yyy))) - k.save("k.png", format="PNG", optimize=True) - im2 = Image.open("k.png") - back_im = im1.copy() - back_im.paste(im2, (150, 0)) - back_im.save("o.webp", "WEBP", quality=95) - file = "o.webp" - os.remove(fil) - os.remove("k.png") - else: - im = Image.open(ik) - z, d = im.size - if z == d: - xxx, yyy = 200, 200 - else: - t = z + d - a = z / t - b = d / t - aa = (a * 100) - 50 - bb = (b * 100) - 50 - xxx = 200 + 5 * aa - yyy = 200 + 5 * bb - k = im.resize((int(xxx), int(yyy))) - k.save("k.png", format="PNG", optimize=True) - im2 = Image.open("k.png") - back_im = im1.copy() - back_im.paste(im2, (150, 0)) - back_im.save("o.webp", "WEBP", quality=95) - file = "o.webp" - os.remove("k.png") - if os.path.exists(file): - await event.client.send_file( - event.chat_id, file, reply_to=event.reply_to_msg_id - ) - os.remove(file) - await xx.delete() - os.remove(ik) diff --git a/plugins/tag.py b/plugins/tag.py deleted file mode 100644 index 141e5feac1..0000000000 --- a/plugins/tag.py +++ /dev/null @@ -1,76 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}tagall` - Tag Top 100 Members of chat. - -• `{i}tagadmins` - Tag Admins of that chat. - -• `{i}tagowner` - Tag Owner of that chat - -• `{i}tagbots` - Tag Bots of that chat. - -• `{i}tagrec` - Tag recently Active Members. - -• `{i}tagon` - Tag online Members(work only if privacy off). - -• `{i}tagoff` - Tag Offline Members(work only if privacy off). -""" - -from telethon.tl.types import ChannelParticipantAdmin as admin -from telethon.tl.types import ChannelParticipantCreator as owner -from telethon.tl.types import UserStatusOffline as off -from telethon.tl.types import UserStatusOnline as onn -from telethon.tl.types import UserStatusRecently as rec - -from . import inline_mention, ultroid_cmd - - -@ultroid_cmd( - pattern="tag(on|off|all|bots|rec|admins|owner)( (.*)|$)", - groups_only=True, -) -async def _(e): - okk = e.text - lll = e.pattern_match.group(2) - o = 0 - nn = 0 - rece = 0 - xx = f"{lll}" if lll else "" - lili = await e.client.get_participants(e.chat_id, limit=99) - for bb in lili: - x = bb.status - y = bb.participant - if isinstance(x, onn): - o += 1 - if "on" in okk: - xx += f"\n{inline_mention(bb)}" - elif isinstance(x, off): - nn += 1 - if "off" in okk and not bb.bot and not bb.deleted: - xx += f"\n{inline_mention(bb)}" - elif isinstance(x, rec): - rece += 1 - if "rec" in okk and not bb.bot and not bb.deleted: - xx += f"\n{inline_mention(bb)}" - if isinstance(y, owner): - xx += f"\n꧁{inline_mention(bb)}꧂" - if isinstance(y, admin) and "admin" in okk and not bb.deleted: - xx += f"\n{inline_mention(bb)}" - if "all" in okk and not bb.bot and not bb.deleted: - xx += f"\n{inline_mention(bb)}" - if "bot" in okk and bb.bot: - xx += f"\n{inline_mention(bb)}" - await e.eor(xx) diff --git a/plugins/tools.py b/plugins/tools.py index 9d57810434..00b2172600 100644 --- a/plugins/tools.py +++ b/plugins/tools.py @@ -36,8 +36,12 @@ import glob import io import os -import secrets +import secrets, time, re, asyncio +from telethon.tl import types, functions +from datetime import datetime as dt, timedelta from asyncio.exceptions import TimeoutError as AsyncTimeout +from telethon.utils import get_display_name +from telethon.tl.functions.contacts import UnblockRequest try: import cv2 @@ -62,6 +66,25 @@ from pyUltroid.fns.tools import metadata, translate +from aiohttp.client_exceptions import InvalidURL +from telethon.errors.rpcerrorlist import MessageNotModifiedError + +from pyUltroid.fns.helper import time_formatter +from pyUltroid.fns.tools import get_chat_and_msgid, set_attributes + +from . import ( + LOGS, + ULTConfig, + downloader, + eor, + fast_download, + get_all_files, + get_string, + progress, + time_formatter, + ultroid_cmd, +) + from . import ( HNDLR, LOGS, @@ -368,9 +391,9 @@ async def sangmata(event): try: if user.isdigit(): - userinfo = await ultroid_bot.get_entity(int(user)) + userinfo = await event.client.get_entity(int(user)) else: - userinfo = await ultroid_bot.get_entity(user) + userinfo = await event.client.get_entity(user) except ValueError: userinfo = None if not isinstance(userinfo, types.User): @@ -383,7 +406,7 @@ async def sangmata(event): try: await conv.send_message(f"{userinfo.id}") except YouBlockedUserError: - await catub(unblock("SangMata_beta_bot")) + await event.client(UnblockRequest("SangMata_beta_bot")) await conv.send_message(f"{userinfo.id}") responses = [] while True: @@ -461,3 +484,188 @@ async def webss(event): os.remove(pic) await xx.delete() + + + +@ultroid_cmd( + pattern="dl( (.*)|$)", +) +async def download(event): + match = event.pattern_match.group(1).strip() + xx = await event.eor(get_string("com_1")) + + # Handle web links + if match and ("http://" in match or "https://" in match): + try: + splited = match.split(" | ") + link = splited[0] + filename = splited[1] if len(splited) > 1 else None + s_time = time.time() + try: + filename, d = await fast_download( + link, + filename, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + xx, + s_time, + f"Downloading from {link}", + ) + ), + ) + except InvalidURL: + return await xx.eor("`Invalid URL provided :(`", time=5) + return await xx.eor(f"`{filename}` `downloaded in {time_formatter(d*1000)}.`") + except Exception as e: + LOGS.exception(e) + return await xx.eor(f"`Error: {str(e)}`", time=5) + + # Handle Telegram links + if match and "t.me/" in match: + chat, msg = get_chat_and_msgid(match) + if not (chat and msg): + return await xx.eor(get_string("gms_1")) + match = "" + ok = await event.client.get_messages(chat, ids=msg) + elif event.reply_to_msg_id: + ok = await event.get_reply_message() + else: + return await xx.eor(get_string("cvt_3"), time=8) + + if not (ok and ok.media): + return await xx.eor(get_string("udl_1"), time=5) + + s = dt.now() + k = time.time() + if hasattr(ok.media, "document"): + file = ok.media.document + mime_type = file.mime_type + filename = match or ok.file.name + if not filename: + if "audio" in mime_type: + filename = "audio_" + dt.now().isoformat("_", "seconds") + ".ogg" + elif "video" in mime_type: + filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" + try: + result = await downloader( + f"resources/downloads/{filename}", + file, + xx, + k, + f"Downloading {filename}...", + ) + except MessageNotModifiedError as err: + return await xx.edit(str(err)) + file_name = result.name + else: + d = "resources/downloads/" + file_name = await event.client.download_media( + ok, + d, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + xx, + k, + get_string("com_5"), + ), + ), + ) + e = dt.now() + t = time_formatter(((e - s).seconds) * 1000) + await xx.eor(get_string("udl_2").format(file_name, t)) + + +@ultroid_cmd( + pattern="ul( (.*)|$)", +) +async def _(event): + msg = await event.eor(get_string("com_1")) + match = event.pattern_match.group(1) + if match: + match = match.strip() + if not event.out and match == ".env": + return await event.reply("`You can't do this...`") + stream, force_doc, delete, thumb = ( + False, + True, + False, + ULTConfig.thumb, + ) + if "--stream" in match: + stream = True + force_doc = False + if "--delete" in match: + delete = True + if "--no-thumb" in match: + thumb = None + arguments = ["--stream", "--delete", "--no-thumb"] + if any(item in match for item in arguments): + match = ( + match.replace("--stream", "") + .replace("--delete", "") + .replace("--no-thumb", "") + .strip() + ) + if match.endswith("/"): + match += "*" + results = glob.glob(match) + if not results and os.path.exists(match): + results = [match] + if not results: + try: + await event.reply(file=match) + return await event.try_delete() + except Exception as er: + LOGS.exception(er) + return await msg.eor(get_string("ls1")) + for result in results: + if os.path.isdir(result): + c, s = 0, 0 + for files in get_all_files(result): + attributes = None + if stream: + try: + attributes = await set_attributes(files) + except KeyError as er: + LOGS.exception(er) + try: + file, _ = await event.client.fast_uploader( + files, show_progress=True, event=msg, to_delete=delete + ) + await event.client.send_file( + event.chat_id, + file, + supports_streaming=stream, + force_document=force_doc, + thumb=thumb, + attributes=attributes, + caption=f"`Uploaded` `{files}` `in {time_formatter(_*1000)}`", + reply_to=event.reply_to_msg_id or event, + ) + s += 1 + except (ValueError, IsADirectoryError): + c += 1 + break + attributes = None + if stream: + try: + attributes = await set_attributes(result) + except KeyError as er: + LOGS.exception(er) + file, _ = await event.client.fast_uploader( + result, show_progress=True, event=msg, to_delete=delete + ) + await event.client.send_file( + event.chat_id, + file, + supports_streaming=stream, + force_document=force_doc, + thumb=thumb, + attributes=attributes, + caption=f"`Uploaded` `{result}` `in {time_formatter(_*1000)}`", + ) + await msg.try_delete() diff --git a/plugins/twitter.py b/plugins/twitter.py deleted file mode 100644 index 06613cee2c..0000000000 --- a/plugins/twitter.py +++ /dev/null @@ -1,205 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -""" -✘ Commands Available - - -• `{i}tw ` - Tweet the text. - -• `{i}twr ` - Get tweet details with reply/quote/comment count. - -• `{i}twuser ` - Get user details of the Twitter account. - -• `{i}twl ` - Upload the tweet media to telegram. - -""" - -import os -from twikit import Client -from . import LOGS, eor, get_string, udB, ultroid_cmd - -# Store client globally -twitter_client = None - -# Get path to cookies file -COOKIES_FILE = "resources/auth/twitter_cookies.json" - -async def get_client(): - global twitter_client - if twitter_client: - return twitter_client - - if not all(udB.get_key(key) for key in ["TWITTER_USERNAME", "TWITTER_EMAIL", "TWITTER_PASSWORD"]): - raise Exception("Set TWITTER_USERNAME, TWITTER_EMAIL and TWITTER_PASSWORD in vars first!") - - # Create auth directory if it doesn't exist - os.makedirs(os.path.dirname(COOKIES_FILE), exist_ok=True) - - client = Client() - await client.login( - auth_info_1=udB.get_key("TWITTER_USERNAME"), - auth_info_2=udB.get_key("TWITTER_EMAIL"), - password=udB.get_key("TWITTER_PASSWORD"), - cookies_file=COOKIES_FILE - ) - twitter_client = client - return client - - - -@ultroid_cmd(pattern="tw( (.*)|$)") -async def tweet_cmd(event): - """Post a tweet""" - text = event.pattern_match.group(1).strip() - if not text: - return await event.eor("🚫 `Give some text to tweet!`") - - msg = await event.eor("🕊 `Tweeting...`") - try: - client = await get_client() - tweet = await client.create_tweet(text=text) - await msg.edit(f"✨ **Successfully Posted!**\n\n🔗 https://x.com/{tweet.user.screen_name}/status/{tweet.id}") - except Exception as e: - await msg.edit(f"❌ **Error:**\n`{str(e)}`") - - -@ultroid_cmd(pattern="twdetail( (.*)|$)") -async def twitter_details(event): - """Get tweet details""" - match = event.pattern_match.group(1).strip() - if not match: - return await event.eor("🚫 `Give tweet ID/link to get details!`") - - msg = await event.eor("🔍 `Getting tweet details...`") - try: - client = await get_client() - from urllib.parse import urlparse - parsed_url = urlparse(match) - if parsed_url.hostname in ["twitter.com", "x.com"]: - tweet_id = parsed_url.path.split("/")[-1].split("?")[0] - else: - tweet_id = match - - tweet = await client.get_tweet_by_id(tweet_id) - text = "🐦 **Tweet Details**\n\n" - text += f"📝 **Content:** `{tweet.text}`\n\n" - if hasattr(tweet, "metrics"): - text += f"❤️ **Likes:** `{tweet.metrics.likes}`\n" - text += f"🔄 **Retweets:** `{tweet.metrics.retweets}`\n" - text += f"💬 **Replies:** `{tweet.metrics.replies}`\n" - text += f"👁 **Views:** `{tweet.metrics.views}`\n" - - await msg.edit(text) - except Exception as e: - await msg.edit(f"❌ **Error:**\n`{str(e)}`") - - -@ultroid_cmd(pattern="twuser( (.*)|$)") -async def twitter_user(event): - """Get user details""" - match = event.pattern_match.group(1).strip() - if not match: - return await event.eor("🚫 `Give username to get details!`") - - msg = await event.eor("🔍 `Getting user details...`") - try: - client = await get_client() - user = await client.get_user_by_screen_name(match) - text = "👤 **Twitter User Details**\n\n" - text += f"📛 **Name:** `{user.name}`\n" - text += f"🔖 **Username:** `@{user.screen_name}`\n" - text += f"📝 **Bio:** `{user.description}`\n\n" - text += f"👥 **Followers:** `{user.followers_count}`\n" - text += f"👣 **Following:** `{user.following_count}`\n" - text += f"🐦 **Total Tweets:** `{user.statuses_count}`\n" - text += f"📍 **Location:** `{user.location or 'Not Set'}`\n" - text += f"✅ **Verified:** `{user.verified}`\n" - - if user.profile_image_url: - image_url = user.profile_image_url.replace("_normal.", ".") - await event.client.send_file( - event.chat_id, - file=image_url, - caption=text, - force_document=False - ) - await msg.delete() - else: - await msg.edit(text) - - except Exception as e: - await msg.edit(f"❌ **Error:**\n`{str(e)}`") - - -@ultroid_cmd(pattern="twl( (.*)|$)") -async def twitter_media(event): - """Download tweet media""" - match = event.pattern_match.group(1).strip() - if not match: - return await event.eor("🚫 `Give tweet link to download media!`") - - msg = await event.eor("📥 `Downloading media...`") - try: - client = await get_client() - if "twitter.com" in match or "x.com" in match: - tweet_id = match.split("/")[-1].split("?")[0] - else: - tweet_id = match - - tweet = await client.get_tweet_by_id(tweet_id) - - if not hasattr(tweet, "media"): - return await msg.edit("😕 `No media found in tweet!`") - - # Prepare caption with tweet text - caption = f"🐦 **Tweet by @{tweet.user.screen_name}**\n\n" - caption += f"{tweet.text}\n\n" - if hasattr(tweet, "metrics"): - caption += f"❤️ `{tweet.metrics.likes}` 🔄 `{tweet.metrics.retweets}` 💬 `{tweet.metrics.replies}`" - - media_count = 0 - for media in tweet.media: - if media.type == "photo": - await event.client.send_file( - event.chat_id, - media.url, - caption=caption if media_count == 0 else None # Only add caption to first media - ) - media_count += 1 - elif media.type == "video": - if hasattr(media, "video_info") and isinstance(media.video_info, dict): - variants = media.video_info.get("variants", []) - mp4_variants = [ - v for v in variants - if v.get("content_type") == "video/mp4" and "bitrate" in v - ] - if mp4_variants: - best_video = max(mp4_variants, key=lambda x: x["bitrate"]) - video_caption = caption if media_count == 0 else "" # Only add tweet text to first media - if video_caption: - video_caption += f"\n🎥 Video Quality: {best_video['bitrate']/1000:.0f}kbps" - else: - video_caption = f"🎥 Video Quality: {best_video['bitrate']/1000:.0f}kbps" - - await event.client.send_file( - event.chat_id, - best_video["url"], - caption=video_caption - ) - media_count += 1 - - if media_count > 0: - await msg.edit(f"✅ Successfully downloaded {media_count} media items!") - await msg.delete() - else: - await msg.edit("😕 `No media could be downloaded!`") - except Exception as e: - await msg.edit(f"❌ **Error:**\n`{str(e)}`") diff --git a/plugins/unsplash.py b/plugins/unsplash.py deleted file mode 100644 index 4393009fe7..0000000000 --- a/plugins/unsplash.py +++ /dev/null @@ -1,36 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• {i}unsplash ; - Unsplash Image Search. -""" - -from pyUltroid.fns.misc import unsplashsearch - -from . import asyncio, download_file, get_string, os, ultroid_cmd - - -@ultroid_cmd(pattern="unsplash( (.*)|$)") -async def searchunsl(ult): - match = ult.pattern_match.group(1).strip() - if not match: - return await ult.eor("Give me Something to Search") - num = 5 - if ";" in match: - num = int(match.split(";")[1]) - match = match.split(";")[0] - tep = await ult.eor(get_string("com_1")) - res = await unsplashsearch(match, limit=num) - if not res: - return await ult.eor(get_string("unspl_1"), time=5) - CL = [download_file(rp, f"{match}-{e}.png") for e, rp in enumerate(res)] - imgs = [z[0] for z in (await asyncio.gather(*CL)) if z] - await ult.respond(f"Uploaded {len(imgs)} Images!", file=imgs) - await tep.delete() - [os.remove(img) for img in imgs] diff --git a/plugins/utilities.py b/plugins/utilities.py index f8b62f643f..6aa39dfa9d 100644 --- a/plugins/utilities.py +++ b/plugins/utilities.py @@ -55,7 +55,7 @@ import io import os import pathlib -import time +import time, math from datetime import datetime as dt try: @@ -64,7 +64,6 @@ Image = None from pyUltroid._misc._assistant import asst_cmd -from pyUltroid.dB.gban_mute_db import is_gbanned from pyUltroid.fns.tools import get_chat_and_msgid from . import upload_file as uf @@ -72,6 +71,7 @@ from telethon.errors.rpcerrorlist import ChatForwardsRestrictedError, UserBotError from telethon.errors import MessageTooLongError from telethon.events import NewMessage +from telethon.tl import types, functions from telethon.tl.custom import Dialog from telethon.tl.functions.channels import ( GetAdminedPublicChannelsRequest, @@ -94,6 +94,7 @@ MessageMediaPhoto, MessageMediaDocument, DocumentAttributeVideo, + ) from telethon.utils import get_peer_id @@ -284,28 +285,23 @@ async def _(event): pattern="info( (.*)|$)", manager=True, ) -async def _(event): - if match := event.pattern_match.group(1).strip(): - try: - user = await event.client.parse_id(match) - except Exception as er: - return await event.eor(str(er)) - elif event.is_reply: - rpl = await event.get_reply_message() - user = rpl.sender_id - else: - user = event.chat_id +async def getInfo(event): + user = event.pattern_match.group(1).strip() + if not user: + if event.is_reply: + rpl = await event.get_reply_message() + user = rpl.sender_id + else: + user = event.chat_id xx = await event.eor(get_string("com_1")) try: _ = await event.client.get_entity(user) except Exception as er: return await xx.edit(f"**ERROR :** {er}") - if not isinstance(_, User): + if not isinstance(_, types.User): try: - peer = get_peer_id(_) + get_peer_id(_) photo, capt = await get_chat_info(_, event) - if is_gbanned(peer): - capt += "\n• Is Gbanned: True" if not photo: return await xx.eor(capt, parse_mode="html") await event.client.send_message( @@ -316,7 +312,7 @@ async def _(event): await event.eor("**ERROR ON CHATINFO**\n" + str(er)) return try: - full_user = (await event.client(GetFullUserRequest(user))).full_user + full_user: types.UserFull = (await event.client(GetFullUserRequest(user))).full_user except Exception as er: return await xx.edit(f"ERROR : {er}") user = _ @@ -329,46 +325,42 @@ async def _(event): first_name = first_name.replace("\u2060", "") last_name = user.last_name last_name = ( - last_name.replace("\u2060", "") if last_name else ("Last Name not found") + last_name.replace("\u2060", "") if last_name else ( + "Last Name not found") ) - user_bio = full_user.about - if user_bio is not None: - user_bio = html.escape(full_user.about) + user_bio = html.escape(full_user.about or "") common_chats = full_user.common_chats_count if user.photo: dc_id = user.photo.dc_id else: dc_id = "Need a Profile Picture to check this" - caption = """Exᴛʀᴀᴄᴛᴇᴅ Dᴀᴛᴀ Fʀᴏᴍ Tᴇʟᴇɢʀᴀᴍ's Dᴀᴛᴀʙᴀsᴇ -••Tᴇʟᴇɢʀᴀᴍ ID: {} -••Pᴇʀᴍᴀɴᴇɴᴛ Lɪɴᴋ: Click Here -••Fɪʀsᴛ Nᴀᴍᴇ: {} -••Sᴇᴄᴏɴᴅ Nᴀᴍᴇ: {} -••Bɪᴏ: {} -••Dᴄ ID: {} -••Nᴏ. Oғ PғPs : {} -••Is Rᴇsᴛʀɪᴄᴛᴇᴅ: {} -••Vᴇʀɪғɪᴇᴅ: {} -••Is Pʀᴇᴍɪᴜᴍ: {} -••Is A Bᴏᴛ: {} -••Gʀᴏᴜᴘs Iɴ Cᴏᴍᴍᴏɴ: {} -""".format( - user_id, - user_id, - first_name, - last_name, - user_bio, - dc_id, - user_photos, - user.restricted, - user.verified, - user.premium, - user.bot, - common_chats, - ) - if chk := is_gbanned(user_id): - caption += f"""••Gʟᴏʙᴀʟʟʏ Bᴀɴɴᴇᴅ: True -••Rᴇᴀsᴏɴ: {chk}""" + caption = f"""Exᴛʀᴀᴄᴛᴇᴅ Dᴀᴛᴀ Fʀᴏᴍ Tᴇʟᴇɢʀᴀᴍ's Dᴀᴛᴀʙᴀsᴇ +••Tᴇʟᴇɢʀᴀᴍ ID: {user_id} +••Pᴇʀᴍᴀɴᴇɴᴛ Lɪɴᴋ: Click Here +••Fɪʀsᴛ Nᴀᴍᴇ: {first_name}""" + if not user.bot: + caption += f"\n••Sᴇᴄᴏɴᴅ Nᴀᴍᴇ: {last_name}" + caption += f"""\n••Bɪᴏ: {user_bio} +••Dᴄ ID: {dc_id}""" + if (b_date:= full_user.birthday): + date = f"{b_date.day}-{b_date.month}" + if b_date.year: + date += f"-{b_date.year}" + caption += f"\n••Birthday : {date}" + if full_user.stories: + caption += f"\n••Stories Count : {len(full_user.stories.stories)}" + if user_photos: + caption += f"\n••Nᴏ. Oғ PғPs : {user_photos}" + if not user.bot: + caption += f"\n••Is Rᴇsᴛʀɪᴄᴛᴇᴅ: {user.restricted}" + caption += f"\n••Is Pʀᴇᴍɪᴜᴍ: {user.premium}" + caption += f"""\n••Vᴇʀɪғɪᴇᴅ: {user.verified} +••Is A Bᴏᴛ: {user.bot} +••Gʀᴏᴜᴘs Iɴ Cᴏᴍᴍᴏɴ: {common_chats} +""" + # if chk := is_gbanned(user_id): + # caption += f"""••Gʟᴏʙᴀʟʟʏ Bᴀɴɴᴇᴅ: True + # ••Rᴇᴀsᴏɴ: {chk}""" await event.client.send_message( event.chat_id, caption, @@ -381,6 +373,148 @@ async def _(event): await xx.delete() +async def get_chat_info(chat, event): + if isinstance(chat, types.Channel): + chat_info = await event.client(functions.channels.GetFullChannelRequest(chat)) + elif isinstance(chat, types.Chat): + chat_info = await event.client(functions.messages.GetFullChatRequest(chat)) + else: + return await event.eor("`Use this for Group/Channel.`") + full = chat_info.full_chat + chat_photo = full.chat_photo + broadcast = getattr(chat, "broadcast", False) + chat_type = "Channel" if broadcast else "Group" + chat_title = chat.title + try: + msg_info = await event.client( + functions.messages.GetHistoryRequest( + peer=chat.id, + offset_id=0, + offset_date=None, + add_offset=-0, + limit=0, + max_id=0, + min_id=0, + hash=0, + ) + ) + except Exception as er: + msg_info = None + if not event.client._bot: + LOGS.exception(er) + first_msg_valid = bool( + msg_info and msg_info.messages and msg_info.messages[0].id == 1 + ) + + creator_valid = bool(first_msg_valid and msg_info.users) + creator_id = msg_info.users[0].id if creator_valid else None + creator_firstname = ( + msg_info.users[0].first_name + if creator_valid and msg_info.users[0].first_name is not None + else "Deleted Account" + ) + creator_username = ( + msg_info.users[0].username + if creator_valid and msg_info.users[0].username is not None + else None + ) + created = msg_info.messages[0].date if first_msg_valid else None + if not isinstance(chat.photo, types.ChatPhotoEmpty): + dc_id = chat.photo.dc_id + else: + dc_id = "Null" + + restricted_users = getattr(full, "banned_count", None) + members = getattr(full, "participants_count", chat.participants_count) + admins = getattr(full, "admins_count", None) + banned_users = getattr(full, "kicked_count", None) + members_online = getattr(full, "online_count", 0) + group_stickers = ( + full.stickerset.title if getattr(full, "stickerset", None) else None + ) + messages_viewable = msg_info.count if msg_info else None + messages_sent = getattr(full, "read_inbox_max_id", None) + messages_sent_alt = getattr(full, "read_outbox_max_id", None) + exp_count = getattr(full, "pts", None) + supergroup = "Yes" if getattr(chat, "megagroup", None) else "No" + creator_username = "@{}".format( + creator_username) if creator_username else None + + if admins is None: + try: + participants_admins = await event.client( + functions.channels.GetParticipantsRequest( + channel=chat.id, + filter=types.ChannelParticipantsAdmins(), + offset=0, + limit=0, + hash=0, + ) + ) + admins = participants_admins.count if participants_admins else None + except Exception as e: + LOGS.info(f"Exception: {e}") + caption = "ℹ️ [CHAT INFO]\n" + caption += f"🆔 ID: {chat.id}\n" + if chat_title is not None: + caption += f"📛 {chat_type} name: {chat_title}\n" + if chat.username: + caption += f"🔗 Link: @{chat.username}\n" + else: + caption += f"🗳 {chat_type} type: Private\n" + if creator_username: + caption += f"🖌 Creator: {creator_username}\n" + elif creator_valid: + caption += f'🖌 Creator: {creator_firstname}\n' + if created: + caption += f"🖌 Created: {created.date().strftime('%b %d, %Y')} - {created.time()}\n" + else: + caption += f"🖌 Created: {chat.date.date().strftime('%b %d, %Y')} - {chat.date.time()} ⚠\n" + caption += f"🗡 Data Centre ID: {dc_id}\n" + if exp_count is not None: + chat_level = int((1 + math.sqrt(1 + 7 * exp_count / 14)) / 2) + caption += f"⭐️ {chat_type} level: {chat_level}\n" + if messages_viewable is not None: + caption += f"💬 Viewable messages: {messages_viewable}\n" + if messages_sent: + caption += f"💬 Messages sent: {messages_sent}\n" + elif messages_sent_alt: + caption += f"💬 Messages sent: {messages_sent_alt} ⚠\n" + if members is not None: + caption += f"👥 Members: {members}\n" + if admins: + caption += f"👮 Administrators: {admins}\n" + if full.bot_info: + caption += f"🤖 Bots: {len(full.bot_info)}\n" + if members_online: + caption += f"👀 Currently online: {members_online}\n" + if restricted_users is not None: + caption += f"🔕 Restricted users: {restricted_users}\n" + if banned_users: + caption += f"📨 Banned users: {banned_users}\n" + if group_stickers: + caption += f'📹 {chat_type} stickers: {group_stickers}\n' + if not broadcast: + if getattr(chat, "slowmode_enabled", None): + caption += f"👉 Slow mode: True" + caption += f", 🕐 {full.slowmode_seconds}s\n" + else: + caption += f"🦸‍♂ Supergroup: {supergroup}\n" + if getattr(chat, "restricted", None): + caption += f"🎌 Restricted: {chat.restricted}\n" + rist = chat.restriction_reason[0] + caption += f"> Platform: {rist.platform}\n" + caption += f"> Reason: {rist.reason}\n" + caption += f"> Text: {rist.text}\n\n" + if getattr(chat, "scam", None): + caption += "⚠ Scam: Yes\n" + if getattr(chat, "verified", None): + caption += f"✅ Verified by Telegram: Yes\n\n" + if full.about: + caption += f"🗒 Description: \n{full.about}\n" + return chat_photo, caption + + @ultroid_cmd( pattern="invite( (.*)|$)", groups_only=True, @@ -732,6 +866,8 @@ async def get_thumbnail(file_path, thumbnail_path): except Exception as e: print(f"Error extracting thumbnail: {e}") + + @ultroid_cmd(pattern="getmsg( ?(.*)|$)") async def get_restricted_msg(event): match = event.pattern_match.group(1).strip() @@ -743,14 +879,21 @@ async def get_restricted_msg(event): chat, msg = get_chat_and_msgid(match) if not (chat and msg): return await event.eor( - "Invalid link!\nEg: `https://t.me/TeamUltroid/3` or `https://t.me/c/1313492028/3`" + "Invalid link!\nExamples:\n" + "`https://t.me/TeamUltroid/3`\n" + "`https://t.me/c/1313492028/3`\n" + "`tg://openmessage?user_id=1234567890&message_id=1`" ) try: - message = await event.client.get_messages(chat, ids=msg) + input_entity = await event.client.get_input_entity(chat) + message = await event.client.get_messages(input_entity, ids=msg) except BaseException as er: return await event.eor(f"**ERROR**\n`{er}`") + if not message: + return await event.eor("`Message not found or may not exist.`") + try: await event.client.send_message(event.chat_id, message) await xx.try_delete() diff --git a/plugins/variables.py b/plugins/variables.py deleted file mode 100644 index 5c4bb79090..0000000000 --- a/plugins/variables.py +++ /dev/null @@ -1,94 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}get var ` - Get value of the given variable name. - -• `{i}get type ` - Get variable type. - -• `{i}get db ` - Get db value of the given key. - -• `{i}get keys` - Get all redis keys. -""" - -import os - -from . import eor, get_string, udB, ultroid_cmd, HNDLR - - -@ultroid_cmd(pattern="get($| (.*))", fullsudo=True) -async def get_var(event): - try: - opt = event.text.split(maxsplit=2)[1] - except IndexError: - return await event.eor(f"what to get?\nRead `{HNDLR}help variables`") - x = await event.eor(get_string("com_1")) - if opt != "keys": - try: - varname = event.text.split(maxsplit=2)[2] - except IndexError: - return await eor(x, "Such a var doesn't exist!", time=5) - if opt == "var": - c = 0 - # try redis - val = udB.get_key(varname) - if val is not None: - c += 1 - await x.edit( - f"**Variable** - `{varname}`\n**Value**: `{val}`\n**Type**: Redis Key." - ) - # try env vars - val = os.getenv(varname) - if val is not None: - c += 1 - await x.edit( - f"**Variable** - `{varname}`\n**Value**: `{val}`\n**Type**: Env Var." - ) - - if c == 0: - await eor(x, "Such a var doesn't exist!", time=5) - - elif opt == "type": - c = 0 - # try redis - val = udB.get_key(varname) - if val is not None: - c += 1 - await x.edit(f"**Variable** - `{varname}`\n**Type**: Redis Key.") - # try env vars - val = os.getenv(varname) - if val is not None: - c += 1 - await x.edit(f"**Variable** - `{varname}`\n**Type**: Env Var.") - - if c == 0: - await eor(x, "Such a var doesn't exist!", time=5) - - elif opt == "db": - val = udB.get(varname) - if val is not None: - await x.edit(f"**Key** - `{varname}`\n**Value**: `{val}`") - else: - await eor(x, "No such key!", time=5) - - elif opt == "keys": - keys = sorted(udB.keys()) - msg = "".join( - f"• `{i}`" + "\n" - for i in keys - if not i.isdigit() - and not i.startswith("-") - and not i.startswith("_") - and not i.startswith("GBAN_REASON_") - ) - - await x.edit(f"**List of DB Keys :**\n{msg}") diff --git a/plugins/videotools.py b/plugins/videotools.py deleted file mode 100644 index b25081bd88..0000000000 --- a/plugins/videotools.py +++ /dev/null @@ -1,139 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -•`{i}sample ` - Creates Short sample of video.. - -• `{i}vshots ` - Creates screenshot of video.. - -• `{i}vtrim - in seconds` - Crop a Lengthy video.. -""" - -import glob -import os - -from pyUltroid.fns.tools import set_attributes - -from . import ( - ULTConfig, - bash, - duration_s, - eod, - genss, - get_string, - mediainfo, - stdr, - ultroid_cmd, -) - - -@ultroid_cmd(pattern="sample( (.*)|$)") -async def gen_sample(e): - sec = e.pattern_match.group(1).strip() - stime = int(sec) if sec and sec.isdigit() else 30 - vido = await e.get_reply_message() - if vido and vido.media and "video" in mediainfo(vido.media): - msg = await e.eor(get_string("com_1")) - file, _ = await e.client.fast_downloader( - vido.document, show_progress=True, event=msg - ) - file_name = (file.name).split("/")[-1] - out = file_name.replace(file_name.split(".")[-1], "_sample.mkv") - xxx = await msg.edit(f"Generating Sample of `{stime}` seconds...") - ss, dd = await duration_s(file.name, stime) - cmd = f'ffmpeg -i "{file.name}" -preset ultrafast -ss {ss} -to {dd} -codec copy -map 0 "{out}" -y' - await bash(cmd) - os.remove(file.name) - attributes = await set_attributes(out) - mmmm, _ = await e.client.fast_uploader( - out, show_progress=True, event=xxx, to_delete=True - ) - caption = f"A Sample Video Of `{stime}` seconds" - await e.client.send_file( - e.chat_id, - mmmm, - thumb=ULTConfig.thumb, - caption=caption, - attributes=attributes, - force_document=False, - reply_to=e.reply_to_msg_id, - ) - await xxx.delete() - else: - await e.eor(get_string("audiotools_8"), time=5) - - -@ultroid_cmd(pattern="vshots( (.*)|$)") -async def gen_shots(e): - ss = e.pattern_match.group(1).strip() - shot = int(ss) if ss and ss.isdigit() else 5 - vido = await e.get_reply_message() - if vido and vido.media and "video" in mediainfo(vido.media): - msg = await e.eor(get_string("com_1")) - file, _ = await e.client.fast_downloader( - vido.document, show_progress=True, event=msg - ) - xxx = await msg.edit(f"Generating `{shot}` screenshots...") - await bash("rm -rf ss && mkdir ss") - cmd = f'ffmpeg -i "{file.name}" -vf fps=0.009 -vframes {shot} "ss/pic%01d.png"' - await bash(cmd) - os.remove(file.name) - pic = glob.glob("ss/*") - text = f"Uploaded {len(pic)}/{shot} screenshots" - if not pic: - text = "`Failed to Take Screenshots..`" - pic = None - await e.respond(text, file=pic) - await bash("rm -rf ss") - await xxx.delete() - - -@ultroid_cmd(pattern="vtrim( (.*)|$)") -async def gen_sample(e): - sec = e.pattern_match.group(1).strip() - if not sec or "-" not in sec: - return await eod(e, get_string("audiotools_3")) - a, b = sec.split("-") - if int(a) >= int(b): - return await eod(e, get_string("audiotools_4")) - vido = await e.get_reply_message() - if vido and vido.media and "video" in mediainfo(vido.media): - msg = await e.eor(get_string("audiotools_5")) - file, _ = await e.client.fast_downloader( - vido.document, show_progress=True, event=msg - ) - file_name = (file.name).split("/")[-1] - out = file_name.replace(file_name.split(".")[-1], "_trimmed.mkv") - if int(b) > int(await genss(file.name)): - os.remove(file.name) - return await eod(msg, get_string("audiotools_6")) - ss, dd = stdr(int(a)), stdr(int(b)) - xxx = await msg.edit(f"Trimming Video from `{ss}` to `{dd}`...") - cmd = f'ffmpeg -i "{file.name}" -preset ultrafast -ss {ss} -to {dd} -codec copy -map 0 "{out}" -y' - await bash(cmd) - os.remove(file.name) - attributes = await set_attributes(out) - mmmm, _ = await e.client.fast_uploader( - out, show_progress=True, event=msg, to_delete=True - ) - caption = f"Trimmed Video From `{ss}` To `{dd}`" - await e.client.send_file( - e.chat_id, - mmmm, - thumb=ULTConfig.thumb, - caption=caption, - attributes=attributes, - force_document=False, - reply_to=e.reply_to_msg_id, - ) - await xxx.delete() - else: - await e.eor(get_string("audiotools_8"), time=5) diff --git a/plugins/warn.py b/plugins/warn.py deleted file mode 100644 index 8d3a52562c..0000000000 --- a/plugins/warn.py +++ /dev/null @@ -1,186 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - -•`{i}warn ` - Gives Warn. - -•`{i}resetwarn ` - To reset All Warns. - -•`{i}warns ` - To Get List of Warnings of a user. - -•`{i}setwarn | ` - Set Number in warn count for warnings - After putting " | " mark put action like ban/mute/kick - Its Default 3 kick - Example : `setwarn 5 | mute` - -""" - -from pyUltroid.dB.warn_db import add_warn, reset_warn, warns - -from . import eor, get_string, inline_mention, udB, ultroid_cmd - - -@ultroid_cmd( - pattern="warn( (.*)|$)", - manager=True, - groups_only=True, - admins_only=True, -) -async def warn(e): - ultroid_bot = e.client - reply = await e.get_reply_message() - if len(e.text) > 5 and " " not in e.text[5]: - return - if reply: - user = reply.sender_id - reason = e.text[5:] if e.pattern_match.group(1).strip() else "unknown" - else: - try: - user = e.text.split()[1] - if user.startswith("@"): - ok = await ultroid_bot.get_entity(user) - user = ok.id - else: - user = int(user) - except BaseException: - return await e.eor("Reply To A User", time=5) - try: - reason = e.text.split(maxsplit=2)[-1] - except BaseException: - reason = "unknown" - count, r = warns(e.chat_id, user) - r = f"{r}|$|{reason}" if r else reason - try: - x = udB.get_key("SETWARN") - number, action = int(x.split()[0]), x.split()[1] - except BaseException: - number, action = 3, "kick" - if ("ban" or "kick" or "mute") not in action: - action = "kick" - if count + 1 >= number: - if "ban" in action: - try: - await ultroid_bot.edit_permissions(e.chat_id, user, view_messages=False) - except BaseException: - return await e.eor("`Something Went Wrong.`", time=5) - elif "kick" in action: - try: - await ultroid_bot.kick_participant(e.chat_id, user) - except BaseException: - return await e.eor("`Something Went Wrong.`", time=5) - elif "mute" in action: - try: - await ultroid_bot.edit_permissions( - e.chat_id, user, until_date=None, send_messages=False - ) - except BaseException: - return await e.eor("`Something Went Wrong.`", time=5) - add_warn(e.chat_id, user, count + 1, r) - c, r = warns(e.chat_id, user) - ok = await ultroid_bot.get_entity(user) - user = inline_mention(ok) - r = r.split("|$|") - text = f"User {user} Got {action} Due to {count+1} Warns.\n\n" - for x in range(c): - text += f"•**{x+1}.** {r[x]}\n" - await e.eor(text) - return reset_warn(e.chat_id, ok.id) - add_warn(e.chat_id, user, count + 1, r) - ok = await ultroid_bot.get_entity(user) - user = inline_mention(ok) - await eor( - e, - f"**WARNING :** {count+1}/{number}\n**To :**{user}\n**Be Careful !!!**\n\n**Reason** : {reason}", - ) - - -@ultroid_cmd( - pattern="resetwarn( (.*)|$)", - manager=True, - groups_only=True, - admins_only=True, -) -async def rwarn(e): - reply = await e.get_reply_message() - if reply: - user = reply.sender_id - else: - try: - user = e.text.split()[1] - if user.startswith("@"): - ok = await e.client.get_entity(user) - user = ok.id - else: - user = int(user) - except BaseException: - return await e.eor("Reply To user") - reset_warn(e.chat_id, user) - ok = await e.client.get_entity(user) - user = inline_mention(ok) - await e.eor(f"Cleared All Warns of {user}.") - - -@ultroid_cmd( - pattern="warns( (.*)|$)", - manager=True, - groups_only=True, - admins_only=True, -) -async def twarns(e): - reply = await e.get_reply_message() - if reply: - user = reply.from_id.user_id - else: - try: - user = e.text.split()[1] - if user.startswith("@"): - ok = await e.client.get_entity(user) - user = ok.id - else: - user = int(user) - except BaseException: - return await e.eor("Reply To A User", time=5) - c, r = warns(e.chat_id, user) - if c and r: - ok = await e.client.get_entity(user) - user = inline_mention(ok) - r = r.split("|$|") - text = f"User {user} Got {c} Warns.\n\n" - for x in range(c): - text += f"•**{x+1}.** {r[x]}\n" - await e.eor(text) - else: - await e.eor("`No Warnings`") - - -@ultroid_cmd(pattern="setwarn( (.*)|$)", manager=True) -async def warnset(e): - ok = e.pattern_match.group(1).strip() - if not ok: - return await e.eor("Invalid format. Correct usage: .setwarns |") - if "|" in ok: - try: - number, action = ok.split("|") - number = int(number.strip()) - action = action.strip() - except ValueError: - return await e.eor( - "Invalid format. Correct usage: .setwarns |", time=5 - ) - if action not in ["ban", "mute", "kick"]: - return await e.eor("Only mute / ban / kick options are supported", time=5) - udB.set_key("SETWARN", f"{number} {action}") - await e.eor(f"Done. Your Warn Count is now {number} and Action is {action}") - else: - await e.eor( - "Invalid format. Correct usage: .setwarns |", time=5 - ) diff --git a/plugins/weather.py b/plugins/weather.py deleted file mode 100644 index 26420330ec..0000000000 --- a/plugins/weather.py +++ /dev/null @@ -1,176 +0,0 @@ -# Ultroid ~ UserBot -# Copyright (C) 2023-2024 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -""" -**Get Weather Data using OpenWeatherMap API** -❍ Commands Available - - -• `{i}weather` - Shows the Weather of Cities - -• `{i}air` - Shows the Air Condition of Cities -""" - -import datetime -import time -from datetime import timedelta - -import aiohttp -import pytz - -from . import async_searcher, get_string, udB, ultroid_cmd - - -async def get_timezone(offset_seconds, use_utc=False): - offset = timedelta(seconds=offset_seconds) - hours, remainder = divmod(offset.seconds, 3600) - sign = "+" if offset.total_seconds() >= 0 else "-" - timezone = "UTC" if use_utc else "GMT" - if use_utc: - for m in pytz.all_timezones: - tz = pytz.timezone(m) - now = datetime.datetime.now(tz) - if now.utcoffset() == offset: - return f"{m} ({timezone}{sign}{hours:02d})" - else: - for m in pytz.all_timezones: - tz = pytz.timezone(m) - if m.startswith("Australia/"): - now = datetime.datetime.now(tz) - if now.utcoffset() == offset: - return f"{m} ({timezone}{sign}{hours:02d})" - for m in pytz.all_timezones: - tz = pytz.timezone(m) - now = datetime.datetime.now(tz) - if now.utcoffset() == offset: - return f"{m} ({timezone}{sign}{hours:02d})" - return "Timezone not found" - -async def getWindinfo(speed: str, degree: str) -> str: - dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] - ix = round(degree / (360.00 / len(dirs))) - kmph = str(float(speed) * 3.6) + " km/h" - return f"[{dirs[ix % len(dirs)]}] {kmph}" - -async def get_air_pollution_data(latitude, longitude, api_key): - url = f"http://api.openweathermap.org/data/2.5/air_pollution?lat={latitude}&lon={longitude}&appid={api_key}" - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - data = await response.json() - if "list" in data: - air_pollution = data["list"][0] - return air_pollution - else: - return None - - -@ultroid_cmd(pattern="weather ?(.*)") -async def weather(event): - if event.fwd_from: - return - msg = await event.eor(get_string("com_1")) - x = udB.get_key("OPENWEATHER_API") - if x is None: - await event.eor( - "No API found. Get One from [Here](https://api.openweathermap.org)\nAnd Add it in OPENWEATHER_API Redis Key", - time=8, - ) - return - input_str = event.pattern_match.group(1) - if not input_str: - await event.eor("No Location was Given...", time=5) - return - elif input_str == "butler": - await event.eor("search butler,au for australila", time=5) - sample_url = f"https://api.openweathermap.org/data/2.5/weather?q={input_str}&APPID={x}&units=metric" - try: - response_api = await async_searcher(sample_url, re_json=True) - if response_api["cod"] == 200: - country_time_zone = int(response_api["timezone"]) - tz = f"{await get_timezone(country_time_zone)}" - sun_rise_time = int(response_api["sys"]["sunrise"]) + country_time_zone - sun_set_time = int(response_api["sys"]["sunset"]) + country_time_zone - await msg.edit( - f"{response_api['name']}, {response_api['sys']['country']}\n\n" - f"╭────────────────•\n" - f"╰➢ **𝖶𝖾𝖺𝗍𝗁𝖾𝗋:** {response_api['weather'][0]['description']}\n" - f"╰➢ **𝖳𝗂𝗆𝖾𝗓𝗈𝗇𝖾:** {tz}\n" - f"╰➢ **𝖲𝗎𝗇𝗋𝗂𝗌𝖾:** {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(sun_rise_time))}\n" - f"╰➢ **𝖲𝗎𝗇𝗌𝖾𝗍:** {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(sun_set_time))}\n" - f"╰➢ **𝖶𝗂𝗇𝖽:** {await getWindinfo(response_api['wind']['speed'], response_api['wind']['deg'])}\n" - f"╰➢ **𝖳𝖾𝗆𝗉𝖾𝗋𝖺𝗍𝗎𝗋𝖾:** {response_api['main']['temp']}°C\n" - f"╰➢ **𝖥𝖾𝖾𝗅𝗌 𝗅𝗂𝗄𝖾:** {response_api['main']['feels_like']}°C\n" - f"╰➢ **𝖬𝗂𝗇𝗂𝗆𝗎𝗆:** {response_api['main']['temp_min']}°C\n" - f"╰➢ **𝖬𝖺𝗑𝗂𝗆𝗎𝗆:** {response_api['main']['temp_max']}°C\n" - f"╰➢ **𝖯𝗋𝖾𝗌𝗌𝗎𝗋𝖾:** {response_api['main']['pressure']} hPa\n" - f"╰➢ **𝖧𝗎𝗆𝗂𝖽𝗂𝗍𝗒:** {response_api['main']['humidity']}%\n" - f"╰➢ **𝖵𝗂𝗌𝗂𝖻𝗂𝗅𝗂𝗍𝗒:** {response_api['visibility']} m\n" - f"╰➢ **𝖢𝗅𝗈𝗎𝖽𝗌:** {response_api['clouds']['all']}%\n" - f"╰────────────────•\n\n" - ) - else: - await msg.edit(response_api["message"]) - except Exception as e: - await event.eor(f"An unexpected error occurred: {str(e)}", time=5) - - -@ultroid_cmd(pattern="air ?(.*)") -async def air_pollution(event): - if event.fwd_from: - return - msg = await event.eor(get_string("com_1")) - x = udB.get_key("OPENWEATHER_API") - if x is None: - await event.eor( - "No API found. Get One from [Here](https://api.openweathermap.org)\nAnd Add it in OPENWEATHER_API Redis Key", - time=8, - ) - return - input_str = event.pattern_match.group(1) - if not input_str: - await event.eor("`No Location was Given...`", time=5) - return - if input_str.lower() == "perth": - geo_url = f"https://geocode.xyz/perth%20au?json=1" - else: - geo_url = f"https://geocode.xyz/{input_str}?json=1" - geo_data = await async_searcher(geo_url, re_json=True) - try: - longitude = geo_data["longt"] - latitude = geo_data["latt"] - except KeyError as e: - LOGS.info(e) - await event.eor("`Unable to find coordinates for the given location.`", time=5) - return - try: - city = geo_data["standard"]["city"] - prov = geo_data["standard"]["prov"] - except KeyError as e: - LOGS.info(e) - await event.eor("`Unable to find city for the given coordinates.`", time=5) - return - air_pollution_data = await get_air_pollution_data(latitude, longitude, x) - if air_pollution_data is None: - await event.eor( - "`Unable to fetch air pollution data for the given location.`", time=5 - ) - return - await msg.edit( - f"{city}, {prov}\n\n" - f"╭────────────────•\n" - f"╰➢ **𝖠𝖰𝖨:** {air_pollution_data['main']['aqi']}\n" - f"╰➢ **𝖢𝖺𝗋𝖻𝗈𝗇 𝖬𝗈𝗇𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['co']}µg/m³\n" - f"╰➢ **𝖭𝗈𝗂𝗍𝗋𝗈𝗀𝖾𝗇 𝖬𝗈𝗇𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['no']}µg/m³\n" - f"╰➢ **𝖭𝗂𝗍𝗋𝗈𝗀𝖾𝗇 𝖣𝗂𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['no2']}µg/m³\n" - f"╰➢ **𝖮𝗓𝗈𝗇𝖾:** {air_pollution_data['components']['o3']}µg/m³\n" - f"╰➢ **𝖲𝗎𝗅𝗉𝗁𝗎𝗋 𝖣𝗂𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['so2']}µg/m³\n" - f"╰➢ **𝖠𝗆𝗆𝗈𝗇𝗂𝖺:** {air_pollution_data['components']['nh3']}µg/m³\n" - f"╰➢ **𝖥𝗂𝗇𝖾 𝖯𝖺𝗋𝗍𝗂𝖼𝗅𝖾𝗌 (PM₂.₅):** {air_pollution_data['components']['pm2_5']}\n" - f"╰➢ **𝖢𝗈𝖺𝗋𝗌𝖾 𝖯𝖺𝗋𝗍𝗂𝖼𝗅𝖾𝗌 (PM₁₀):** {air_pollution_data['components']['pm10']}\n" - f"╰────────────────•\n\n" - ) diff --git a/plugins/webupload.py b/plugins/webupload.py deleted file mode 100644 index d319fa277a..0000000000 --- a/plugins/webupload.py +++ /dev/null @@ -1,67 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -""" -✘ Commands Available - - -• `{i}webupload` - Upload files on another server. -""" - -import os - -from pyUltroid.fns.tools import _webupload_cache - -from . import Button, asst, get_string, ultroid_cmd - - -@ultroid_cmd(pattern="webupload( (.*)|$)") -async def _(event): - xx = await event.eor(get_string("com_1")) - match = event.pattern_match.group(1).strip() - if event.chat_id not in _webupload_cache: - _webupload_cache.update({int(event.chat_id): {}}) - if match: - if not os.path.exists(match): - return await xx.eor("File doesn't exist.") - _webupload_cache[event.chat_id][event.id] = match - elif event.reply_to_msg_id: - reply = await event.get_reply_message() - if reply.photo: - file = await reply.download_media("resources/downloads/") - _webupload_cache[int(event.chat_id)][int(event.id)] = file - else: - file, _ = await event.client.fast_downloader( - reply.document, show_progress=True, event=xx - ) - _webupload_cache[int(event.chat_id)][int(event.id)] = file.name - else: - return await xx.eor("Reply to file or give file path...") - if not event.client._bot: - results = await event.client.inline_query( - asst.me.username, f"fl2lnk {event.chat_id}:{event.id}" - ) - await results[0].click(event.chat_id, reply_to=event.reply_to_msg_id) - await xx.delete() - - else: - __cache = f"{event.chat_id}:{event.id}" - buttons = [ - [ - Button.inline("catbox", data=f"flcatbox//{__cache}"), - Button.inline("transfer", data=f"fltransfer//{__cache}"), - ], - [ - Button.inline("filebin", data=f"flfilebin//{__cache}"), - Button.inline("0x0.st", data=f"fl0x0.st//{__cache}"), - ], - [ - Button.inline("file.io", data=f"flfile.io//{__cache}"), - Button.inline("siasky", data=f"flsiasky//{__cache}"), - ], - ] - await xx.edit("Choose Server to Upload File...", buttons=buttons) diff --git a/plugins/words.py b/plugins/words.py deleted file mode 100644 index 22e5f8d73f..0000000000 --- a/plugins/words.py +++ /dev/null @@ -1,115 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}meaning ` - Get the meaning of the word. - -• `{i}synonym ` - Get all synonyms. - -• `{i}antonym ` - Get all antonyms. - -• `{i}ud ` - Fetch word defenition from urbandictionary. -""" -import io - -from pyUltroid.fns.misc import get_synonyms_or_antonyms -from pyUltroid.fns.tools import async_searcher - -from . import get_string, ultroid_cmd - - -@ultroid_cmd(pattern="meaning( (.*)|$)", manager=True) -async def mean(event): - wrd = event.pattern_match.group(1).strip() - if not wrd: - return await event.eor(get_string("wrd_4")) - url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{wrd}" - out = await async_searcher(url, re_json=True) - try: - return await event.eor(f'**{out["title"]}**') - except (KeyError, TypeError): - pass - defi = out[0]["meanings"][0]["definitions"][0] - ex = defi["example"] if defi.get("example") else "None" - text = get_string("wrd_1").format(wrd, defi["definition"], ex) - if defi.get("synonyms"): - text += ( - f"\n\n• **{get_string('wrd_5')} :**" - + "".join(f" {a}," for a in defi["synonyms"])[:-1][:10] - ) - if defi.get("antonyms"): - text += ( - f"\n\n**{get_string('wrd_6')} :**" - + "".join(f" {a}," for a in defi["antonyms"])[:-1][:10] - ) - if len(text) > 4096: - with io.BytesIO(str.encode(text)) as fle: - fle.name = f"{wrd}-meanings.txt" - await event.reply( - file=fle, - force_document=True, - caption=f"Meanings of {wrd}", - ) - await event.delete() - else: - await event.eor(text) - - -@ultroid_cmd( - pattern="(syno|anto)nym", -) -async def mean(event): - task = event.pattern_match.group(1) + "nyms" - try: - wrd = event.text.split(maxsplit=1)[1] - except IndexError: - return await event.eor("Give Something to search..") - try: - ok = await get_synonyms_or_antonyms(wrd, task) - x = get_string("wrd_2" if task == "synonyms" else "wrd_3").format(wrd) - for c, i in enumerate(ok, start=1): - x += f"**{c}.** `{i}`\n" - if len(x) > 4096: - with io.BytesIO(str.encode(x)) as fle: - fle.name = f"{wrd}-{task}.txt" - await event.client.send_file( - event.chat_id, - fle, - force_document=True, - allow_cache=False, - caption=f"{task} of {wrd}", - reply_to=event.reply_to_msg_id, - ) - await event.delete() - else: - await event.eor(x) - except Exception as e: - await event.eor( - get_string("wrd_7" if task == "synonyms" else "wrd_8").format(e) - ) - - -@ultroid_cmd(pattern="ud (.*)") -async def _(event): - word = event.pattern_match.group(1).strip() - if not word: - return await event.eor(get_string("autopic_1")) - out = await async_searcher( - "http://api.urbandictionary.com/v0/define", params={"term": word}, re_json=True - ) - try: - out = out["list"][0] - except IndexError: - return await event.eor(get_string("autopic_2").format(word)) - await event.eor( - get_string("wrd_1").format(out["word"], out["definition"], out["example"]), - ) diff --git a/plugins/writer.py b/plugins/writer.py deleted file mode 100644 index 9b9ea3d4e7..0000000000 --- a/plugins/writer.py +++ /dev/null @@ -1,86 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -""" -✘ Commands Available - - -• `{i}write ` - It will write on a paper. - -• `{i}image ` - Write a image from html or any text. -""" - -import os - -from htmlwebshot import WebShot -from PIL import Image, ImageDraw, ImageFont - -from . import async_searcher, eod, get_string, text_set, ultroid_cmd - - -@ultroid_cmd(pattern="gethtml( (.*)|$)") -async def ghtml(e): - if txt := e.pattern_match.group(1).strip(): - link = e.text.split(maxsplit=1)[1] - else: - return await eod(e, "`Either reply to any file or give any text`") - k = await async_searcher(link) - with open("file.html", "w+") as f: - f.write(k) - await e.reply(file="file.html") - - -@ultroid_cmd(pattern="image( (.*)|$)") -async def f2i(e): - txt = e.pattern_match.group(1).strip() - html = None - if txt: - html = e.text.split(maxsplit=1)[1] - elif e.reply_to: - r = await e.get_reply_message() - if r.media: - html = await e.client.download_media(r.media) - elif r.text: - html = r.text - if not html: - return await eod(e, "`Either reply to any file or give any text`") - html = html.replace("\n", "
") - shot = WebShot(quality=85) - css = "body {background: white;} p {color: red;}" - pic = await shot.create_pic_async(html=html, css=css) - await e.reply(file=pic, force_document=True) - os.remove(pic) - if os.path.exists(html): - os.remove(html) - - -@ultroid_cmd(pattern="write( (.*)|$)") -async def writer(e): - if e.reply_to: - reply = await e.get_reply_message() - text = reply.message - elif e.pattern_match.group(1).strip(): - text = e.text.split(maxsplit=1)[1] - else: - return await eod(e, get_string("writer_1")) - k = await e.eor(get_string("com_1")) - img = Image.open("resources/extras/template.jpg") - draw = ImageDraw.Draw(img) - font = ImageFont.truetype("resources/fonts/assfont.ttf", 30) - x, y = 150, 140 - lines = text_set(text) - bbox = font.getbbox("hg") - line_height = bbox[3] - bbox[1] - for line in lines: - draw.text((x, y), line, fill=(1, 22, 55), font=font) - y = y + line_height - 5 - file = "ult.jpg" - img.save(file) - await e.reply(file=file) - os.remove(file) - await k.delete() diff --git a/plugins/youtube.py b/plugins/youtube.py deleted file mode 100644 index fc129c0d48..0000000000 --- a/plugins/youtube.py +++ /dev/null @@ -1,85 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - - -• `{i}yta <(youtube/any) link>` - Download audio from the link. - -• `{i}ytv <(youtube/any) link>` - Download video from the link. - -• `{i}ytsa <(youtube) search query>` - Search and download audio from youtube. - -• `{i}ytsv <(youtube) search query>` - Search and download video from youtube. -""" -from pyUltroid.fns.ytdl import download_yt, get_yt_link - -from . import get_string, requests, ultroid_cmd - - -@ultroid_cmd( - pattern="yt(a|v|sa|sv) ?(.*)", -) -async def download_from_youtube_(event): - ytd = { - "prefer_ffmpeg": True, - "addmetadata": True, - "geo-bypass": True, - "nocheckcertificate": True, - } - opt = event.pattern_match.group(1).strip() - xx = await event.eor(get_string("com_1")) - if opt == "a": - ytd["format"] = "bestaudio" - ytd["outtmpl"] = "%(id)s.m4a" - url = event.pattern_match.group(2) - if not url: - return await xx.eor(get_string("youtube_1")) - try: - requests.get(url) - except BaseException: - return await xx.eor(get_string("youtube_2")) - elif opt == "v": - ytd["format"] = "best" - ytd["outtmpl"] = "%(id)s.mp4" - ytd["postprocessors"] = [{"key": "FFmpegMetadata"}] - url = event.pattern_match.group(2) - if not url: - return await xx.eor(get_string("youtube_3")) - try: - requests.get(url) - except BaseException: - return await xx.eor(get_string("youtube_4")) - elif opt == "sa": - ytd["format"] = "bestaudio" - ytd["outtmpl"] = "%(id)s.m4a" - try: - query = event.text.split(" ", 1)[1] - except IndexError: - return await xx.eor(get_string("youtube_5")) - url = get_yt_link(query) - if not url: - return await xx.edit(get_string("unspl_1")) - await xx.eor(get_string("youtube_6")) - elif opt == "sv": - ytd["format"] = "best" - ytd["outtmpl"] = "%(id)s.mp4" - ytd["postprocessors"] = [{"key": "FFmpegMetadata"}] - try: - query = event.text.split(" ", 1)[1] - except IndexError: - return await xx.eor(get_string("youtube_7")) - url = get_yt_link(query) - if not url: - return await xx.edit(get_string("unspl_1")) - await xx.eor(get_string("youtube_8")) - else: - return - await download_yt(xx, url, ytd) diff --git a/plugins/ziptools.py b/plugins/ziptools.py deleted file mode 100644 index ab2674c191..0000000000 --- a/plugins/ziptools.py +++ /dev/null @@ -1,172 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . -""" -✘ Commands Available - -• `{i}zip ` - zip the replied file - To set password on zip: `{i}zip ` reply to file - -• `{i}unzip ` - unzip the replied file. - -• `{i}azip ` - add file to batch for batch upload zip - -• `{i}dozip` - upload batch zip the files u added from `{i}azip` - To set Password: `{i}dozip ` - -""" -import os -import time - -from . import ( - HNDLR, - ULTConfig, - asyncio, - bash, - downloader, - get_all_files, - get_string, - ultroid_cmd, - uploader, -) - - -@ultroid_cmd(pattern="zip( (.*)|$)") -async def zipp(event): - reply = await event.get_reply_message() - t = time.time() - if not reply: - await event.eor(get_string("zip_1")) - return - xx = await event.eor(get_string("com_1")) - if reply.media: - if hasattr(reply.media, "document"): - file = reply.media.document - image = await downloader( - reply.file.name, reply.media.document, xx, t, get_string("com_5") - ) - file = image.name - else: - file = await event.download_media(reply) - inp = file.replace(file.split(".")[-1], "zip") - if event.pattern_match.group(1).strip(): - await bash( - f"zip -r --password {event.pattern_match.group(1).strip()} {inp} {file}" - ) - else: - await bash(f"zip -r {inp} {file}") - k = time.time() - n_file, _ = await event.client.fast_uploader( - inp, show_progress=True, event=event, message="Uploading...", to_delete=True - ) - await event.client.send_file( - event.chat_id, - n_file, - force_document=True, - thumb=ULTConfig.thumb, - caption=f"`{n_file.name}`", - reply_to=reply, - ) - os.remove(inp) - os.remove(file) - await xx.delete() - - -@ultroid_cmd(pattern="unzip( (.*)|$)") -async def unzipp(event): - reply = await event.get_reply_message() - file = event.pattern_match.group(1).strip() - t = time.time() - if not ((reply and reply.media) or file): - await event.eor(get_string("zip_1")) - return - xx = await event.eor(get_string("com_1")) - if reply.media: - if not hasattr(reply.media, "document"): - return await xx.edit(get_string("zip_3")) - file = reply.media.document - if not reply.file.name.endswith(("zip", "rar", "exe")): - return await xx.edit(get_string("zip_3")) - image = await downloader( - reply.file.name, reply.media.document, xx, t, get_string("com_5") - ) - file = image.name - if os.path.isdir("unzip"): - await bash("rm -rf unzip") - os.mkdir("unzip") - await bash(f"7z x {file} -aoa -ounzip") - await asyncio.sleep(4) - ok = get_all_files("unzip") - for x in ok: - k = time.time() - n_file, _ = await event.client.fast_uploader( - x, show_progress=True, event=event, message="Uploading...", to_delete=True - ) - await event.client.send_file( - event.chat_id, - n_file, - force_document=True, - thumb=ULTConfig.thumb, - caption=f"`{n_file.name}`", - ) - await xx.delete() - - -@ultroid_cmd(pattern="addzip$") -async def azipp(event): - reply = await event.get_reply_message() - t = time.time() - if not (reply and reply.media): - await event.eor(get_string("zip_1")) - return - xx = await event.eor(get_string("com_1")) - if not os.path.isdir("zip"): - os.mkdir("zip") - if reply.media: - if hasattr(reply.media, "document"): - file = reply.media.document - image = await downloader( - f"zip/{reply.file.name}", - reply.media.document, - xx, - t, - get_string("com_5"), - ) - - file = image.name - else: - file = await event.download_media(reply.media, "zip/") - await xx.edit( - f"Downloaded `{file}` succesfully\nNow Reply To Other Files To Add And Zip all at once" - ) - - -@ultroid_cmd(pattern="dozip( (.*)|$)") -async def do_zip(event): - if not os.path.isdir("zip"): - return await event.eor(get_string("zip_2").format(HNDLR)) - xx = await event.eor(get_string("com_1")) - if event.pattern_match.group(1).strip(): - await bash( - f"zip -r --password {event.pattern_match.group(1).strip()} ultroid.zip zip/*" - ) - else: - await bash("zip -r ultroid.zip zip/*") - k = time.time() - xxx = await uploader("ultroid.zip", "ultroid.zip", k, xx, get_string("com_6")) - await event.client.send_file( - event.chat_id, - xxx, - force_document=True, - thumb=ULTConfig.thumb, - ) - await bash("rm -rf zip") - os.remove("ultroid.zip") - await xx.delete() diff --git a/pyUltroid/__init__.py b/pyUltroid/__init__.py index c20629b7f1..9ff6aa373f 100644 --- a/pyUltroid/__init__.py +++ b/pyUltroid/__init__.py @@ -6,8 +6,9 @@ # . import os -import sys +import sys, asyncio import telethonpatch +from datetime import datetime from .version import __version__ run_as_module = __package__ in sys.argv or sys.argv[0] == "-m" @@ -36,10 +37,11 @@ class ULTConfig: exit() start_time = time.time() + _loop = asyncio.get_event_loop() _ult_cache = {} _ignore_eval = [] - udB = UltroidDB() + udB = _loop.run_until_complete(UltroidDB()) update_envs() LOGS.info(f"Connecting to {udB.name}...") @@ -99,7 +101,7 @@ class ULTConfig: DUAL_HNDLR = udB.get_key("DUAL_HNDLR") or "/" SUDO_HNDLR = udB.get_key("SUDO_HNDLR") or HNDLR else: - print("pyUltroid 2022 © TeamUltroid") + print(f"pyUltroid {datetime.now().year} © TeamUltroid") from logging import getLogger diff --git a/pyUltroid/__main__.py b/pyUltroid/__main__.py index 9de436fbbd..cd1575e4e8 100644 --- a/pyUltroid/__main__.py +++ b/pyUltroid/__main__.py @@ -6,12 +6,17 @@ # . from . import * +from logging import getLogger +from pyUltroid.web.server import ultroid_server + +logger = getLogger(__name__) def main(): import os import sys import time + import asyncio from .fns.helper import bash, time_formatter, updater from .startup.funcs import ( @@ -21,14 +26,10 @@ def main(): plug, ready, startup_stuff, + user_sync_workflow, ) from .startup.loader import load_other_plugins - try: - from apscheduler.schedulers.asyncio import AsyncIOScheduler - except ImportError: - AsyncIOScheduler = None - # Option to Auto Update On Restarts.. if ( udB.get_key("UPDATE_ON_RESTART") @@ -48,22 +49,17 @@ def main(): LOGS.info("Initialising...") - ultroid_bot.run_in_loop(autopilot()) + # Execute critical startup functions immediately + ultroid_bot.run_in_loop(user_sync_workflow()) - pmbot = udB.get_key("PMBOT") - manager = udB.get_key("MANAGER") - addons = udB.get_key("ADDONS") or Var.ADDONS - vcbot = udB.get_key("VCBOT") or Var.VCBOT - if HOSTED_ON == "okteto": - vcbot = False - - if (HOSTED_ON == "termux" or udB.get_key("LITE_DEPLOY")) and udB.get_key( - "EXCLUDE_OFFICIAL" - ) is None: - _plugins = "autocorrect autopic audiotools compressor forcesubscribe fedutils gdrive glitch instagram nsfwfilter nightmode pdftools profanityfilter writer youtube" - udB.set_key("EXCLUDE_OFFICIAL", _plugins) - - load_other_plugins(addons=addons, pmbot=pmbot, manager=manager, vcbot=vcbot) + # Load plugins first to ensure core functionality is available + # Store background tasks for later handling + background_tasks = load_other_plugins( + addons=udB.get_key("ADDONS") or Var.ADDONS or udB.get_key("INCLUDE_ALL"), + pmbot=udB.get_key("PMBOT"), + manager=udB.get_key("MANAGER"), + vcbot=udB.get_key("VCBOT") or Var.VCBOT, + ) suc_msg = """ ---------------------------------------------------------------------- @@ -71,22 +67,22 @@ def main(): ---------------------------------------------------------------------- """ - # for channel plugins + # Schedule non-critical tasks as background tasks to improve startup time plugin_channels = udB.get_key("PLUGIN_CHANNEL") - # Customize Ultroid Assistant... - ultroid_bot.run_in_loop(customize()) - - # Load Addons from Plugin Channels. + # These operations are moved to background tasks to reduce startup time + asst.loop.create_task(autopilot()) + if not USER_MODE and not udB.get_key("DISABLE_AST_PLUGINS"): + asst.loop.create_task(customize()) if plugin_channels: - ultroid_bot.run_in_loop(plug(plugin_channels)) - - # Send/Ignore Deploy Message.. + asst.loop.create_task(plug(plugin_channels)) if not udB.get_key("LOG_OFF"): - ultroid_bot.run_in_loop(ready()) + asst.loop.create_task(ready()) + asst.loop.create_task(WasItRestart(udB)) - # Edit Restarting Message (if It's restarting) - ultroid_bot.run_in_loop(WasItRestart(udB)) + if Var.START_WEB: + logger.info("Starting web server as a background task...") + asst.loop.create_task(ultroid_server.start()) try: cleanup_cache() @@ -99,7 +95,47 @@ def main(): LOGS.info(suc_msg) +def run_indefinitely(max_wait: int = 3600 * 3): # 3 hours + """Run the assistant indefinitely with connection error handling and timeout. + + Args: + max_wait: Maximum time in seconds to keep retrying on connection errors. + Defaults to 3 hours. + """ + start_time = 0 + retry_count = 0 + backoff = 10 # Initial backoff time in seconds + + while True: + # Check if max wait time exceeded + if start_time and (time.time() - start_time) > max_wait: + logger.error(f"Max wait time of {max_wait} seconds reached, exiting") + exit(1) + + try: + # Attempt to run the assistant + asst.run() + except ConnectionError as er: + # Track first connection error + if not start_time: + start_time = time.time() + + retry_count += 1 + wait_time = min(backoff * retry_count, 300) # Cap at 5 minutes + + logger.error( + f"ConnectionError: {er}, attempt {retry_count}, " + f"waiting {wait_time} seconds" + ) + time.sleep(wait_time) + continue + + except Exception as er: + logger.exception(f"Fatal error occurred: {er}") + exit(1) + + if __name__ == "__main__": main() - asst.run() + run_indefinitely() diff --git a/pyUltroid/_misc/_decorators.py b/pyUltroid/_misc/_decorators.py index 94cea82976..b292f7c634 100644 --- a/pyUltroid/_misc/_decorators.py +++ b/pyUltroid/_misc/_decorators.py @@ -91,13 +91,13 @@ async def wrapp(ult): if fullsudo and ult.sender_id not in SUDO_M.fullsudos: return await eod(ult, get_string("py_d2"), time=15) chat = ult.chat - if hasattr(chat, "title"): - if ( - "#noub" in chat.title.lower() - and not (chat.admin_rights or chat.creator) - and not (ult.sender_id in DEVLIST) - ): - return + # if hasattr(chat, "title"): + # if ( + # "#noub" in chat.title.lower() + # and not (chat.admin_rights or chat.creator) + # and not (ult.sender_id in DEVLIST) + # ): + # return if ult.is_private and (groups_only or admins_only): return await eod(ult, get_string("py_d3")) elif admins_only and not (chat.admin_rights or chat.creator): diff --git a/pyUltroid/_misc/_wrappers.py b/pyUltroid/_misc/_wrappers.py index 6625ec4d84..ed96baf117 100644 --- a/pyUltroid/_misc/_wrappers.py +++ b/pyUltroid/_misc/_wrappers.py @@ -52,7 +52,7 @@ async def eod(event, text=None, **kwargs): async def _try_delete(event): try: return await event.delete() - except (MessageDeleteForbiddenError): + except MessageDeleteForbiddenError: pass except BaseException as er: from . import LOGS diff --git a/pyUltroid/configs.py b/pyUltroid/configs.py index ddf6f05a71..ecbb1cf895 100644 --- a/pyUltroid/configs.py +++ b/pyUltroid/configs.py @@ -53,3 +53,13 @@ class Var: DATABASE_URL = config("DATABASE_URL", default=None) # for MONGODB users MONGO_URI = config("MONGO_URI", default=None) + + START_WEB = config("START_WEB", default=False, cast=bool) + RENDER_WEB = config("RENDER_WEB", default=True, cast=bool) + PORT = config("PORT", default=8000, cast=int) + MINIAPP_URL = config("MINIAPP_URL", default=f"http://localhost:{PORT}") + + +# CENTRAL_REPO_URL = "http://localhost:8055" +CENTRAL_REPO_URL = "https://central.ultroid.org" +ADMIN_BOT_USERNAME = "UltroidBot" diff --git a/pyUltroid/dB/afk_db.py b/pyUltroid/dB/afk_db.py deleted file mode 100644 index e6da109fd9..0000000000 --- a/pyUltroid/dB/afk_db.py +++ /dev/null @@ -1,33 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from datetime import datetime as dt - -from .. import udB - - -def get_stuff(): - return udB.get_key("AFK_DB") or [] - - -def add_afk(msg, media_type, media): - time = dt.now().strftime("%b %d %Y %I:%M:%S%p") - udB.set_key("AFK_DB", [msg, media_type, media, time]) - return - - -def is_afk(): - afk = get_stuff() - if afk: - start_time = dt.strptime(afk[3], "%b %d %Y %I:%M:%S%p") - afk_since = str(dt.now().replace(microsecond=0) - start_time) - return afk[0], afk[1], afk[2], afk_since - return False - - -def del_afk(): - return udB.del_key("AFK_DB") diff --git a/pyUltroid/dB/antiflood_db.py b/pyUltroid/dB/antiflood_db.py deleted file mode 100644 index 7c68d25f2a..0000000000 --- a/pyUltroid/dB/antiflood_db.py +++ /dev/null @@ -1,32 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -from .. import udB - - -def get_flood(): - return udB.get_key("ANTIFLOOD") or {} - - -def set_flood(chat_id, limit): - omk = get_flood() - omk.update({chat_id: limit}) - return udB.set_key("ANTIFLOOD", omk) - - -def get_flood_limit(chat_id): - omk = get_flood() - if chat_id in omk.keys(): - return omk[chat_id] - - -def rem_flood(chat_id): - omk = get_flood() - if chat_id in omk.keys(): - del omk[chat_id] - return udB.set_key("ANTIFLOOD", omk) diff --git a/pyUltroid/dB/asstcmd_db.py b/pyUltroid/dB/asstcmd_db.py deleted file mode 100644 index 9faa4f9cd2..0000000000 --- a/pyUltroid/dB/asstcmd_db.py +++ /dev/null @@ -1,39 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -from .. import udB - - -def get_stuff(): - return udB.get_key("ASST_CMDS") or {} - - -def add_cmd(cmd, msg, media, button): - ok = get_stuff() - ok.update({cmd: {"msg": msg, "media": media, "button": button}}) - return udB.set_key("ASST_CMDS", ok) - - -def rem_cmd(cmd): - ok = get_stuff() - if ok.get(cmd): - ok.pop(cmd) - return udB.set_key("ASST_CMDS", ok) - - -def cmd_reply(cmd): - ok = get_stuff() - if ok.get(cmd): - okk = ok[cmd] - return okk["msg"], okk["media"], okk["button"] if ok.get("button") else None - return - - -def list_cmds(): - ok = get_stuff() - return ok.keys() diff --git a/pyUltroid/dB/blacklist_chat_db.py b/pyUltroid/dB/blacklist_chat_db.py deleted file mode 100644 index e8bd874037..0000000000 --- a/pyUltroid/dB/blacklist_chat_db.py +++ /dev/null @@ -1,15 +0,0 @@ -from .. import udB - - -def add_black_chat(chat_id): - chat = udB.get_key("BLACKLIST_CHATS") or [] - if chat_id not in chat: - chat.append(chat_id) - return udB.set_key("BLACKLIST_CHATS", chat) - - -def rem_black_chat(chat_id): - chat = udB.get_key("BLACKLIST_CHATS") or [] - if chat_id in chat: - chat.remove(chat_id) - return udB.set_key("BLACKLIST_CHATS", chat) diff --git a/pyUltroid/dB/blacklist_db.py b/pyUltroid/dB/blacklist_db.py deleted file mode 100644 index f7d9d24098..0000000000 --- a/pyUltroid/dB/blacklist_db.py +++ /dev/null @@ -1,44 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stuff(): - return udB.get_key("BLACKLIST_DB") or {} - - -def add_blacklist(chat, word): - ok = get_stuff() - if ok.get(chat): - for z in word.split(): - if z not in ok[chat]: - ok[chat].append(z) - else: - ok.update({chat: [word]}) - return udB.set_key("BLACKLIST_DB", ok) - - -def rem_blacklist(chat, word): - ok = get_stuff() - if ok.get(chat) and word in ok[chat]: - ok[chat].remove(word) - return udB.set_key("BLACKLIST_DB", ok) - - -def list_blacklist(chat): - ok = get_stuff() - if ok.get(chat): - txt = "".join(f"👉`{z}`\n" for z in ok[chat]) - if txt: - return txt - - -def get_blacklist(chat): - ok = get_stuff() - if ok.get(chat): - return ok[chat] diff --git a/pyUltroid/dB/echo_db.py b/pyUltroid/dB/echo_db.py deleted file mode 100644 index 638a64ae49..0000000000 --- a/pyUltroid/dB/echo_db.py +++ /dev/null @@ -1,43 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stuff(): - return udB.get_key("ECHO") or {} - - -def add_echo(chat, user): - x = get_stuff() - if k := x.get(int(chat)): - if user not in k: - k.append(int(user)) - x.update({int(chat): k}) - else: - x.update({int(chat): [int(user)]}) - return udB.set_key("ECHO", x) - - -def rem_echo(chat, user): - x = get_stuff() - if k := x.get(int(chat)): - if user in k: - k.remove(int(user)) - x.update({int(chat): k}) - return udB.set_key("ECHO", x) - - -def check_echo(chat, user): - x = get_stuff() - if (k := x.get(int(chat))) and int(user) in k: - return True - - -def list_echo(chat): - x = get_stuff() - return x.get(int(chat)) diff --git a/pyUltroid/dB/filestore_db.py b/pyUltroid/dB/filestore_db.py deleted file mode 100644 index 90ed5de099..0000000000 --- a/pyUltroid/dB/filestore_db.py +++ /dev/null @@ -1,35 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stored(): - return udB.get_key("FILE_STORE") or {} - - -def store_msg(hash, msg_id): - all = get_stored() - all.update({hash: msg_id}) - return udB.set_key("FILE_STORE", all) - - -def list_all_stored_msgs(): - all = get_stored() - return list(all.keys()) - - -def get_stored_msg(hash): - all = get_stored() - if all.get(hash): - return all[hash] - - -def del_stored(hash): - all = get_stored() - all.pop(hash) - return udB.set_key("FILE_STORE", all) diff --git a/pyUltroid/dB/filter_db.py b/pyUltroid/dB/filter_db.py deleted file mode 100644 index f70772affa..0000000000 --- a/pyUltroid/dB/filter_db.py +++ /dev/null @@ -1,47 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stuff(): - return udB.get_key("FILTERS") or {} - - -def add_filter(chat, word, msg, media, button): - ok = get_stuff() - if ok.get(chat): - ok[chat].update({word: {"msg": msg, "media": media, "button": button}}) - else: - ok.update({chat: {word: {"msg": msg, "media": media, "button": button}}}) - udB.set_key("FILTERS", ok) - - -def rem_filter(chat, word): - ok = get_stuff() - if ok.get(chat) and ok[chat].get(word): - ok[chat].pop(word) - udB.set_key("FILTERS", ok) - - -def rem_all_filter(chat): - ok = get_stuff() - if ok.get(chat): - ok.pop(chat) - udB.set_key("FILTERS", ok) - - -def get_filter(chat): - ok = get_stuff() - if ok.get(chat): - return ok[chat] - - -def list_filter(chat): - ok = get_stuff() - if ok.get(chat): - return "".join(f"👉 `{z}`\n" for z in ok[chat]) diff --git a/pyUltroid/dB/forcesub_db.py b/pyUltroid/dB/forcesub_db.py deleted file mode 100644 index 10b271543d..0000000000 --- a/pyUltroid/dB/forcesub_db.py +++ /dev/null @@ -1,35 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -from .. import udB - - -def get_chats(): - return udB.get_key("FORCESUB") or {} - - -def add_forcesub(chat_id, chattojoin): - omk = get_chats() - omk.update({chat_id: chattojoin}) - return udB.set_key("FORCESUB", omk) - - -def get_forcesetting(chat_id): - omk = get_chats() - if chat_id in omk.keys(): - return omk[chat_id] - - -def rem_forcesub(chat_id): - omk = get_chats() - if chat_id in omk.keys(): - try: - del omk[chat_id] - return udB.set_key("FORCESUB", omk) - except KeyError: - return False diff --git a/pyUltroid/dB/gban_mute_db.py b/pyUltroid/dB/gban_mute_db.py deleted file mode 100644 index 0cd8a8dc99..0000000000 --- a/pyUltroid/dB/gban_mute_db.py +++ /dev/null @@ -1,52 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def list_gbanned(): - return udB.get_key("GBAN") or {} - - -def gban(user, reason): - ok = list_gbanned() - ok.update({int(user): reason or "No Reason. "}) - return udB.set_key("GBAN", ok) - - -def ungban(user): - ok = list_gbanned() - if ok.get(int(user)): - del ok[int(user)] - return udB.set_key("GBAN", ok) - - -def is_gbanned(user): - ok = list_gbanned() - if ok.get(int(user)): - return ok[int(user)] - - -def gmute(user): - ok = list_gmuted() - ok.append(int(user)) - return udB.set_key("GMUTE", ok) - - -def ungmute(user): - ok = list_gmuted() - if user in ok: - ok.remove(int(user)) - return udB.set_key("GMUTE", ok) - - -def is_gmuted(user): - return int(user) in list_gmuted() - - -def list_gmuted(): - return udB.get_key("GMUTE") or [] diff --git a/pyUltroid/dB/greetings_db.py b/pyUltroid/dB/greetings_db.py deleted file mode 100644 index 37d3bf6d45..0000000000 --- a/pyUltroid/dB/greetings_db.py +++ /dev/null @@ -1,66 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stuff(key=None): - return udB.get_key(key) or {} - - -def add_welcome(chat, msg, media, button): - ok = get_stuff("WELCOME") - ok.update({chat: {"welcome": msg, "media": media, "button": button}}) - return udB.set_key("WELCOME", ok) - - -def get_welcome(chat): - ok = get_stuff("WELCOME") - return ok.get(chat) - - -def delete_welcome(chat): - ok = get_stuff("WELCOME") - if ok.get(chat): - ok.pop(chat) - return udB.set_key("WELCOME", ok) - - -def add_goodbye(chat, msg, media, button): - ok = get_stuff("GOODBYE") - ok.update({chat: {"goodbye": msg, "media": media, "button": button}}) - return udB.set_key("GOODBYE", ok) - - -def get_goodbye(chat): - ok = get_stuff("GOODBYE") - return ok.get(chat) - - -def delete_goodbye(chat): - ok = get_stuff("GOODBYE") - if ok.get(chat): - ok.pop(chat) - return udB.set_key("GOODBYE", ok) - - -def add_thanks(chat): - x = get_stuff("THANK_MEMBERS") - x.update({chat: True}) - return udB.set_key("THANK_MEMBERS", x) - - -def remove_thanks(chat): - x = get_stuff("THANK_MEMBERS") - if x.get(chat): - x.pop(chat) - return udB.set_key("THANK_MEMBERS", x) - - -def must_thank(chat): - x = get_stuff("THANK_MEMBERS") - return x.get(chat) diff --git a/pyUltroid/dB/mute_db.py b/pyUltroid/dB/mute_db.py deleted file mode 100644 index 1390d1c60c..0000000000 --- a/pyUltroid/dB/mute_db.py +++ /dev/null @@ -1,34 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_muted(): - return udB.get_key("MUTE") or {} - - -def mute(chat, id): - ok = get_muted() - if ok.get(chat): - if id not in ok[chat]: - ok[chat].append(id) - else: - ok.update({chat: [id]}) - return udB.set_key("MUTE", ok) - - -def unmute(chat, id): - ok = get_muted() - if ok.get(chat) and id in ok[chat]: - ok[chat].remove(id) - return udB.set_key("MUTE", ok) - - -def is_muted(chat, id): - ok = get_muted() - return bool(ok.get(chat) and id in ok[chat]) diff --git a/pyUltroid/dB/notes_db.py b/pyUltroid/dB/notes_db.py deleted file mode 100644 index a2443dbb00..0000000000 --- a/pyUltroid/dB/notes_db.py +++ /dev/null @@ -1,47 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stuff(): - return udB.get_key("NOTE") or {} - - -def add_note(chat, word, msg, media, button): - ok = get_stuff() - if ok.get(int(chat)): - ok[int(chat)].update({word: {"msg": msg, "media": media, "button": button}}) - else: - ok.update({int(chat): {word: {"msg": msg, "media": media, "button": button}}}) - udB.set_key("NOTE", ok) - - -def rem_note(chat, word): - ok = get_stuff() - if ok.get(int(chat)) and ok[int(chat)].get(word): - ok[int(chat)].pop(word) - return udB.set_key("NOTE", ok) - - -def rem_all_note(chat): - ok = get_stuff() - if ok.get(int(chat)): - ok.pop(int(chat)) - return udB.set_key("NOTE", ok) - - -def get_notes(chat, word): - ok = get_stuff() - if ok.get(int(chat)) and ok[int(chat)].get(word): - return ok[int(chat)][word] - - -def list_note(chat): - ok = get_stuff() - if ok.get(int(chat)): - return "".join(f"👉 #{z}\n" for z in ok[chat]) diff --git a/pyUltroid/dB/nsfw_db.py b/pyUltroid/dB/nsfw_db.py deleted file mode 100644 index 9a157da368..0000000000 --- a/pyUltroid/dB/nsfw_db.py +++ /dev/null @@ -1,51 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - - -from .. import udB - - -def get_stuff(key="NSFW"): - return udB.get_key(key) or {} - - -def nsfw_chat(chat, action): - x = get_stuff() - x.update({chat: action}) - return udB.set_key("NSFW", x) - - -def rem_nsfw(chat): - x = get_stuff() - if x.get(chat): - x.pop(chat) - return udB.set_key("NSFW", x) - - -def is_nsfw(chat): - x = get_stuff() - if x.get(chat): - return x[chat] - - -def profan_chat(chat, action): - x = get_stuff("PROFANITY") - x.update({chat: action}) - return udB.set_key("PROFANITY", x) - - -def rem_profan(chat): - x = get_stuff("PROFANITY") - if x.get(chat): - x.pop(chat) - return udB.set_key("PROFANITY", x) - - -def is_profan(chat): - x = get_stuff("PROFANITY") - if x.get(chat): - return x[chat] diff --git a/pyUltroid/dB/snips_db.py b/pyUltroid/dB/snips_db.py deleted file mode 100644 index 1d945f72c6..0000000000 --- a/pyUltroid/dB/snips_db.py +++ /dev/null @@ -1,36 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_all_snips(): - return udB.get_key("SNIP") or {} - - -def add_snip(word, msg, media, button): - ok = get_all_snips() - ok.update({word: {"msg": msg, "media": media, "button": button}}) - udB.set_key("SNIP", ok) - - -def rem_snip(word): - ok = get_all_snips() - if ok.get(word): - ok.pop(word) - udB.set_key("SNIP", ok) - - -def get_snips(word): - ok = get_all_snips() - if ok.get(word): - return ok[word] - return False - - -def list_snip(): - return "".join(f"👉 ${z}\n" for z in get_all_snips()) diff --git a/pyUltroid/dB/warn_db.py b/pyUltroid/dB/warn_db.py deleted file mode 100644 index 622f986435..0000000000 --- a/pyUltroid/dB/warn_db.py +++ /dev/null @@ -1,39 +0,0 @@ -# Ultroid - UserBot -# Copyright (C) 2021-2025 TeamUltroid -# -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > -# PLease read the GNU Affero General Public License in -# . - -from .. import udB - - -def get_stuff(): - return udB.get_key("WARNS") or {} - - -def add_warn(chat, user, count, reason): - x = get_stuff() - try: - x[chat].update({user: [count, reason]}) - except BaseException: - x.update({chat: {user: [count, reason]}}) - return udB.set_key("WARNS", x) - - -def warns(chat, user): - x = get_stuff() - try: - count, reason = x[chat][user][0], x[chat][user][1] - return count, reason - except BaseException: - return 0, None - - -def reset_warn(chat, user): - x = get_stuff() - try: - x[chat].pop(user) - return udB.set_key("WARNS", x) - except BaseException: - return diff --git a/pyUltroid/database/base.py b/pyUltroid/database/base.py new file mode 100644 index 0000000000..5209162b7d --- /dev/null +++ b/pyUltroid/database/base.py @@ -0,0 +1,59 @@ +import ast + + +class BaseDatabase: + def __init__(self, *args, **kwargs): + self._cache = {} + + def get_key(self, key): + if key in self._cache: + return self._cache[key] + value = self._get_data(key) + self._cache.update({key: value}) + return value + + def re_cache(self): + self._cache.clear() + for key in self.keys(): + self._cache.update({key: self.get_key(key)}) + + def ping(self): + return 1 + + @property + def usage(self): + return 0 + + def keys(self): + return [] + + def del_key(self, key): + if key in self._cache: + del self._cache[key] + self.delete(key) + return True + + def _get_data(self, key=None, data=None): + if key: + data = self.get(str(key)) + if data and isinstance(data, str): + try: + data = ast.literal_eval(data) + except BaseException: + pass + return data + + def set_key(self, key, value, cache_only=False): + value = self._get_data(data=value) + self._cache[key] = value + if cache_only: + return + return self.set(str(key), str(value)) + + def rename(self, key1, key2): + _ = self.get_key(key1) + if _: + self.del_key(key1) + self.set_key(key2, _) + return 0 + return 1 diff --git a/pyUltroid/database/mongo.py b/pyUltroid/database/mongo.py new file mode 100644 index 0000000000..a701a73ddc --- /dev/null +++ b/pyUltroid/database/mongo.py @@ -0,0 +1,46 @@ +from .base import BaseDatabase +from pymongo import MongoClient + + +class MongoDB(BaseDatabase): + def __init__(self, key, dbname="UltroidDB"): + self.dB = MongoClient(key, serverSelectionTimeoutMS=5000) + self.db = self.dB[dbname] + super().__init__() + + def __repr__(self): + return f"" + + @property + def name(self): + return "Mongo" + + @property + def usage(self): + return self.db.command("dbstats")["dataSize"] + + def ping(self): + if self.dB.server_info(): + return True + + def keys(self): + return self.db.list_collection_names() + + def set(self, key, value): + if key in self.keys(): + self.db[key].replace_one({"_id": key}, {"value": str(value)}) + else: + self.db[key].insert_one({"_id": key, "value": str(value)}) + return True + + def delete(self, key): + self.db.drop_collection(key) + + def get(self, key): + if x := self.db[key].find_one({"_id": key}): + return x["value"] + + def flushall(self): + self.dB.drop_database("UltroidDB") + self._cache.clear() + return True diff --git a/pyUltroid/database/redis.py b/pyUltroid/database/redis.py new file mode 100644 index 0000000000..1e27dcc7d1 --- /dev/null +++ b/pyUltroid/database/redis.py @@ -0,0 +1,71 @@ +from .base import BaseDatabase +from redis import Redis +import os +from logging import getLogger +from typing import Optional + +logger = getLogger(__name__) + + +class RedisDB(BaseDatabase): + def __init__( + self, + host="localhost", + port=6379, + password="", + platform="", + client: Optional[Redis] = None, + *args, + **kwargs, + ): + if not client: + self.db = self._setup_client(host, port, password, platform, **kwargs) + else: + self.db = client + self.set = self.db.set + self.get = self.db.get + self.keys = self.db.keys + self.delete = self.db.delete + super().__init__() + + @property + def name(self): + return "Redis" + + @property + def usage(self): + return sum(self.db.memory_usage(x) for x in self.keys()) + + def _setup_client(self, host, port, password, platform, **kwargs): + if host and ":" in host: + spli_ = host.split(":") + host = spli_[0] + port = int(spli_[-1]) + if host.startswith("http"): + logger.error("Your REDIS_URI should not start with http !") + import sys + + sys.exit() + elif not host or not port: + logger.error("Port Number not found") + import sys + + sys.exit() + + kwargs["host"] = host + kwargs["password"] = password + kwargs["port"] = port + + if platform.lower() == "qovery" and not host: + var, hash_, host, password = "", "", "", "" + for vars_ in os.environ: + if vars_.startswith("QOVERY_REDIS_") and vars.endswith("_HOST"): + var = vars_ + if var: + hash_ = var.split("_", maxsplit=2)[1].split("_")[0] + if hash: + kwargs["host"] = os.environ.get(f"QOVERY_REDIS_{hash_}_HOST") + kwargs["port"] = os.environ.get(f"QOVERY_REDIS_{hash_}_PORT") + kwargs["password"] = os.environ.get(f"QOVERY_REDIS_{hash_}_PASSWORD") + + return Redis(**kwargs) diff --git a/pyUltroid/exceptions.py b/pyUltroid/exceptions.py index bdba015f0e..76c56d582e 100644 --- a/pyUltroid/exceptions.py +++ b/pyUltroid/exceptions.py @@ -10,13 +10,10 @@ """ -class pyUltroidError(Exception): - ... +class pyUltroidError(Exception): ... -class DependencyMissingError(ImportError): - ... +class DependencyMissingError(ImportError): ... -class RunningAsFunctionLibError(pyUltroidError): - ... +class RunningAsFunctionLibError(pyUltroidError): ... diff --git a/pyUltroid/fns/FastTelethon.py b/pyUltroid/fns/FastTelethon.py index d400a20b45..4140c06b83 100644 --- a/pyUltroid/fns/FastTelethon.py +++ b/pyUltroid/fns/FastTelethon.py @@ -173,7 +173,7 @@ async def _cleanup(self) -> None: def _get_connection_count( file_size: int, ) -> int: - full_size = 100 * (1024 ** 2) + full_size = 100 * (1024**2) if file_size > full_size: return 20 return math.ceil((file_size / full_size) * 20) @@ -283,7 +283,7 @@ async def init_upload( connection_count = connection_count or self._get_connection_count(file_size) part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 part_count = (file_size + part_size - 1) // part_size - is_large = file_size > 10 * (1024 ** 2) + is_large = file_size > 10 * (1024**2) await self._init_upload(connection_count, file_id, part_count, is_large) return part_size, part_count, is_large diff --git a/pyUltroid/fns/helper.py b/pyUltroid/fns/helper.py index 105208bd13..74df2cda39 100644 --- a/pyUltroid/fns/helper.py +++ b/pyUltroid/fns/helper.py @@ -14,7 +14,8 @@ from traceback import format_exc from urllib.parse import unquote from urllib.request import urlretrieve - +from PIL import Image +import requests from .. import run_as_module if run_as_module: @@ -351,49 +352,6 @@ async def downloader(filename, file, event, taime, msg): # @buddhhu -async def async_searcher( - url: str, - post: bool = False, - head: bool = False, - headers: dict = None, - evaluate=None, - object: bool = False, - re_json: bool = False, - re_content: bool = False, - *args, - **kwargs, -): - if aiohttp_client: - async with aiohttp_client(headers=headers) as client: - method = client.head if head else (client.post if post else client.get) - data = await method(url, *args, **kwargs) - if evaluate: - return await evaluate(data) - if re_json: - return await data.json() - if re_content: - return await data.read() - if head or object: - return data - return await data.text() - # elif requests: - # method = requests.head if head else (requests.post if post else requests.get) - # data = method(url, headers=headers, *args, **kwargs) - # if re_json: - # return data.json() - # if re_content: - # return data.content - # if head or object: - # return data - # return data.text - else: - raise DependencyMissingError("install 'aiohttp' to use this.") - - -# ~~~~~~~~~~~~~~~~~~~~DDL Downloader~~~~~~~~~~~~~~~~~~~~ -# @buddhhu @new-dev0 - - async def download_file(link, name, validate=False): """for files, without progress callback with aiohttp""" @@ -617,3 +575,45 @@ async def shutdown(ult): ) else: sys.exit() + + +def resize_photo_sticker(photo): + """Resize the given photo to 512x512 (for creating telegram sticker).""" + image = Image.open(photo) + if (image.width and image.height) < 512: + size1 = image.width + size2 = image.height + if image.width > image.height: + scale = 512 / size1 + size1new = 512 + size2new = size2 * scale + else: + scale = 512 / size2 + size1new = size1 * scale + size2new = 512 + size1new = math.floor(size1new) + size2new = math.floor(size2new) + sizenew = (size1new, size2new) + image = image.resize(sizenew) + else: + maxsize = (512, 512) + image.thumbnail(maxsize) + return image + + +def fetch_sync(url, re_json=False, evaluate=None, method="GET", *args, **kwargs): + methods = {"POST": requests.post, "HEAD": requests.head, "GET": requests.get} + method = "POST" if kwargs.pop("post", False) else "GET" + output = requests.request(method, url, *args, **kwargs) + + if callable(evaluate): + return evaluate(output) + elif re_json: + # type: ignore + if "application/json" in output.headers.get("content-type", ""): + return output.json() + return output.text + return output.content + + +async_searcher = fetch = run_async(fetch_sync) diff --git a/pyUltroid/fns/misc.py b/pyUltroid/fns/misc.py index 85369b1b60..4cb8fa1a67 100644 --- a/pyUltroid/fns/misc.py +++ b/pyUltroid/fns/misc.py @@ -62,6 +62,7 @@ uploader = CatboxUploader() + async def randomchannel( tochat, channel, range1, range2, caption=None, client=ultroid_bot ): @@ -195,7 +196,7 @@ async def unsplashsearch(query, limit=None, shuf=True): all_ = res.find_all("img", srcset=re.compile("images.unsplash.com/photo")) if shuf: shuffle(all_) - return list(map(lambda e: e['src'], all_[:limit])) + return list(map(lambda e: e["src"], all_[:limit])) # ---------------- Random User Gen ---------------- diff --git a/pyUltroid/fns/tools.py b/pyUltroid/fns/tools.py index 3c23f21864..ca7a0081bf 100644 --- a/pyUltroid/fns/tools.py +++ b/pyUltroid/fns/tools.py @@ -39,8 +39,6 @@ from telethon import Button from telethon.tl.types import DocumentAttributeAudio, DocumentAttributeVideo -if run_as_module: - from ..dB.filestore_db import get_stored_msg, store_msg try: import numpy as np @@ -118,7 +116,7 @@ async def metadata(file): raise DependencyMissingError( f"'{_}' is not installed!\nInstall it to use this command." ) - + data = {} _info = json.loads(out)["media"] if not _info: @@ -391,61 +389,48 @@ def make_logo(imgpath, text, funt, **args): async def get_paste(data: str, extension: str = "txt"): try: url = "https://spaceb.in/api/" - res = await async_searcher(url, json={"content": data, "extension": extension}, post=True, re_json=True) + res = await async_searcher( + url, json={"content": data, "extension": extension}, post=True, re_json=True + ) return True, { "link": f"https://spaceb.in/{res['payload']['id']}", - "raw": f"https://spaceb.in/{res['payload']['id']}/raw" + "raw": f"https://spaceb.in/{res['payload']['id']}/raw", } except Exception: try: url = "https://dpaste.org/api/" data = { - 'format': 'json', - 'content': data.encode('utf-8'), - 'lexer': extension, - 'expires': '604800', # expire in week + "format": "json", + "content": data.encode("utf-8"), + "lexer": extension, + "expires": "604800", # expire in week } res = await async_searcher(url, data=data, post=True, re_json=True) - return True, { - "link": res["url"], - "raw": f'{res["url"]}/raw' - } + return True, {"link": res["url"], "raw": f'{res["url"]}/raw'} except Exception as e: LOGS.info(e) - return None, { - "link": None, - "raw": None, - "error": str(e) - } + return None, {"link": None, "raw": None, "error": str(e)} + # https://stackoverflow.com/a/74563494 async def get_google_images(query): """Get image results from Google Custom Search API. - + Args: query (str): Search query string - + Returns: list: List of dicts containing image info (title, link, source, thumbnail, original) """ LOGS.info(f"Searching Google Images for: {query}") - + # Google Custom Search API credentials google_keys = [ - { - "key": "AIzaSyAj75v6vHWLJdJaYcj44tLz7bdsrh2g7Y0", - "cx": "712a54749d99a449e" - }, - { - "key": "AIzaSyDFQQwPLCzcJ9FDao-B7zDusBxk8GoZ0HY", - "cx": "001bbd139705f44a6" - }, - { - "key": "AIzaSyD0sRNZUa8-0kq9LAREDAFKLNO1HPmikRU", - "cx": "4717c609c54e24250" - } + {"key": "AIzaSyAj75v6vHWLJdJaYcj44tLz7bdsrh2g7Y0", "cx": "712a54749d99a449e"}, + {"key": "AIzaSyDFQQwPLCzcJ9FDao-B7zDusBxk8GoZ0HY", "cx": "001bbd139705f44a6"}, + {"key": "AIzaSyD0sRNZUa8-0kq9LAREDAFKLNO1HPmikRU", "cx": "4717c609c54e24250"}, ] key_index = random.randint(0, len(google_keys) - 1) GOOGLE_API_KEY = google_keys[key_index]["key"] @@ -460,35 +445,39 @@ async def get_google_images(query): "&searchType=image" "&num=10" # Number of results ) - + # Make API request response = await async_searcher(url, re_json=True) print("response") if not response or "items" not in response: LOGS.error("No results from Google Custom Search API") return [] - + # Process results google_images = [] for item in response["items"]: try: - google_images.append({ - "title": item.get("title", ""), - "link": item.get("contextLink", ""), # Page containing image - "source": item.get("displayLink", ""), - "thumbnail": item.get("image", {}).get("thumbnailLink", item["link"]), - "original": item["link"] # Original image URL - }) + google_images.append( + { + "title": item.get("title", ""), + "link": item.get("contextLink", ""), # Page containing image + "source": item.get("displayLink", ""), + "thumbnail": item.get("image", {}).get( + "thumbnailLink", item["link"] + ), + "original": item["link"], # Original image URL + } + ) except Exception as e: LOGS.warning(f"Failed to process image result: {str(e)}") continue - + # Randomize results order random.shuffle(google_images) - + LOGS.info(f"Found {len(google_images)} images for query: {query}") return google_images - + except Exception as e: LOGS.exception(f"Error in get_google_images: {str(e)}") return [] @@ -507,6 +496,7 @@ async def get_chatbot_reply(message): except Exception: LOGS.info(f"**ERROR:**`{format_exc()}`") + def check_filename(filroid): if os.path.exists(filroid): no = 1 @@ -676,37 +666,6 @@ async def Carbon( return file -async def get_file_link(msg): - from .. import udB - - msg_id = await msg.forward_to(udB.get_key("LOG_CHANNEL")) - await msg_id.reply( - "**Message has been stored to generate a shareable link. Do not delete it.**" - ) - msg_id = msg_id.id - msg_hash = secrets.token_hex(nbytes=8) - store_msg(msg_hash, msg_id) - return msg_hash - - -async def get_stored_file(event, hash): - from .. import udB, asst - - msg_id = get_stored_msg(hash) - if not msg_id: - return - try: - msg = await asst.get_messages(udB.get_key("LOG_CHANNEL"), ids=msg_id) - except Exception as er: - LOGS.warning(f"FileStore, Error: {er}") - return - if not msg_id: - return await asst.send_message( - event.chat_id, "__Message was deleted by owner!__", reply_to=event.id - ) - await asst.send_message(event.chat_id, msg.text, file=msg.media, reply_to=event.id) - - def translate(text, lang_tgt="en", lang_src="auto", timeout=60, detect=False): pattern = r'(?s)class="(?:t0|result-container)">(.*?)<' escaped_text = quote(text.encode("utf8")) @@ -724,7 +683,6 @@ def translate(text, lang_tgt="en", lang_src="auto", timeout=60, detect=False): return (text, None) if detect else text - def cmd_regex_replace(cmd): return ( cmd.replace("$", "") @@ -744,8 +702,7 @@ def cmd_regex_replace(cmd): # ------------------------# -class LottieException(Exception): - ... +class LottieException(Exception): ... class TgConverter: @@ -762,7 +719,7 @@ async def animated_sticker(file, out_path="sticker.tgs", throw=False, remove=Fal ) else: er, out = await bash(f"lottie_convert.py '{file}' '{out_path}'") - + if er: LOGS.error(f"Error in animated_sticker conversion: {er}") if throw: @@ -806,7 +763,7 @@ def resize_photo_sticker(photo): try: image = Image.open(photo) original_size = (image.width, image.height) - + if (image.width and image.height) < 512: size1 = image.width size2 = image.height @@ -825,7 +782,7 @@ def resize_photo_sticker(photo): else: maxsize = (512, 512) image.thumbnail(maxsize) - + LOGS.info(f"Resized image from {original_size} to {image.size}") return image except Exception as e: @@ -839,7 +796,9 @@ async def ffmpeg_convert(input_, output, remove=False): input_, name=output[:-5], remove=remove ) if output.endswith(".gif"): - out, er = await bash(f"ffmpeg -i '{input_}' -an -sn -c:v copy '{output}.mp4' -y") + out, er = await bash( + f"ffmpeg -i '{input_}' -an -sn -c:v copy '{output}.mp4' -y" + ) LOGS.info(f"FFmpeg output: {out}, Error: {er}") else: out, er = await bash(f"ffmpeg -i '{input_}' '{output}' -y") @@ -856,7 +815,7 @@ async def create_webm(file, name="video", remove=False): _ = await metadata(file) name += ".webm" h, w = _["height"], _["width"] - + if h == w and h != 512: h, w = 512, 512 if h != 512 or w != 512: @@ -864,19 +823,19 @@ async def create_webm(file, name="video", remove=False): h, w = 512, -1 if w > h: h, w = -1, 512 - + await bash( f'ffmpeg -i "{file}" -preset fast -an -to 00:00:03 -crf 30 -bufsize 256k -b:v {_["bitrate"]} -vf "scale={w}:{h},fps=30" -c:v libvpx-vp9 "{name}" -y' ) - + if remove and os.path.exists(file): os.remove(file) LOGS.info(f"Removed original file: {file}") - + if os.path.exists(name): LOGS.info(f"Successfully created webm: {name}") return name - + LOGS.error(f"Webm creation failed - output file not created: {name}") return None except Exception as e: @@ -891,37 +850,39 @@ def to_image(input_, name, remove=False): if not input_: LOGS.error("Input file is None") return None - + if not os.path.exists(input_): LOGS.error(f"Input file does not exist: {input_}") return None - + try: import cv2 except ImportError: - raise DependencyMissingError("This function needs 'cv2' to be installed.") - + raise DependencyMissingError( + "This function needs 'cv2' to be installed." + ) + img = cv2.VideoCapture(input_) success, frame = img.read() - + if not success: LOGS.error(f"Failed to read frame from {input_}") return None - + cv2.imwrite(name, frame) img.release() - + if not os.path.exists(name): LOGS.error(f"Failed to save image: {name}") return None - + if remove and os.path.exists(input_): os.remove(input_) LOGS.info(f"Removed original file: {input_}") - + LOGS.info(f"Successfully converted to image: {name}") return name - + except Exception as e: LOGS.exception(f"Error in to_image conversion: {str(e)}") return None @@ -936,11 +897,11 @@ async def convert( ): """Convert between different file formats.""" LOGS.info(f"Converting {input_file} to {convert_to or allowed_formats}") - + if not input_file: LOGS.error("Input file is None") return None - + if not os.path.exists(input_file): LOGS.error(f"Input file does not exist: {input_file}") return None @@ -977,8 +938,10 @@ def recycle_type(exte): input_file, convert_to="gif", remove_old=remove_old ) if gif_file: - return await TgConverter.create_webm(gif_file, outname, remove=True) - + return await TgConverter.create_webm( + gif_file, outname, remove=True + ) + # Json -> Tgs elif ext == "json": if recycle_type("tgs"): @@ -986,7 +949,7 @@ def recycle_type(exte): return await TgConverter.animated_sticker( input_file, name, remove=remove_old ) - + # Video to Something elif ext in ["webm", "mp4", "gif"]: for exte in ["webm", "mp4", "gif"]: @@ -997,14 +960,16 @@ def recycle_type(exte): ) if result: return result - + for exte in ["png", "jpg", "jpeg", "webp"]: if recycle_type(exte): name = outname + "." + exte - result = TgConverter.to_image(input_file, name, remove=remove_old) + result = TgConverter.to_image( + input_file, name, remove=remove_old + ) if result: return result - + # Image to Something elif ext in ["jpg", "jpeg", "png", "webp"]: for extn in ["png", "webp", "ico"]: @@ -1020,7 +985,7 @@ def recycle_type(exte): except Exception as e: LOGS.error(f"Failed to convert image to {extn}: {str(e)}") continue - + for extn in ["webm", "gif", "mp4"]: if recycle_type(extn): name = outname + "." + extn @@ -1038,10 +1003,12 @@ def recycle_type(exte): return await TgConverter.ffmpeg_convert( input_file, name, remove=remove_old ) - - LOGS.error(f"No valid conversion found for {input_file} to {convert_to or allowed_formats}") + + LOGS.error( + f"No valid conversion found for {input_file} to {convert_to or allowed_formats}" + ) return None - + except Exception as e: LOGS.exception(f"Error in convert: {str(e)}") return None @@ -1081,13 +1048,16 @@ def safe_load(file, *args, **kwargs): def get_chat_and_msgid(link): - matches = re.findall("https:\\/\\/t\\.me\\/(c\\/|)(.*)\\/(.*)", link) - if not matches: - return None, None - _, chat, msg_id = matches[0] - if chat.isdigit(): - chat = int("-100" + chat) - return chat, int(msg_id) + m = re.findall(r"t\.me\/(c\/)?([^\/]+)\/(\d+)", link) + if m: + is_channel, chat, msg_id = m[0] + if is_channel: + chat = int("-100" + chat) + return chat, int(msg_id) + + m = re.findall(r"user_id=(\d+)&message_id=(\d+)", link) + if m: + return int(m[0][0]), int(m[0][1]) # --------- END --------- # diff --git a/pyUltroid/loader.py b/pyUltroid/loader.py index a5b4c52580..a7970b7e8d 100644 --- a/pyUltroid/loader.py +++ b/pyUltroid/loader.py @@ -75,3 +75,34 @@ def load( if func == import_module: plugin = plugin.split(".")[-1] after_load(self, modl, plugin_name=plugin) + + def load_single_plugin(self, plugin_path, func=import_module, after_load=None): + """Load a single plugin file""" + try: + if not os.path.exists(plugin_path): + self._logger.error(f"Plugin file not found: {plugin_path}") + return False + + plugin_name = os.path.basename(plugin_path).replace(".py", "") + + if func == import_module: + # Convert file path to module path + plugin_module = str(plugin_path).replace(".py", "").replace("/", ".").replace("\\", ".") + modl = func(plugin_module) + else: + modl = func(plugin_path) + + self._logger.info(f"Successfully loaded plugin: {plugin_name}") + + if callable(after_load): + after_load(self, modl, plugin_name=plugin_name) + + return True + + except ModuleNotFoundError as er: + self._logger.error(f"{plugin_path}: '{er.name}' not installed!") + return False + except Exception as exc: + self._logger.error(f"pyUltroid - {self.key} - ERROR - {plugin_path}") + self._logger.exception(exc) + return False diff --git a/pyUltroid/scripts/redis.py b/pyUltroid/scripts/redis.py new file mode 100644 index 0000000000..0dc5d3a1b5 --- /dev/null +++ b/pyUltroid/scripts/redis.py @@ -0,0 +1,31 @@ +import asyncio +from redis import Redis +from redis.exceptions import ConnectionError + + +def connect_localhost_redis(): + try: + redis = Redis(host="localhost", port=6379, db=0, decode_responses=True) + redis.ping() + return redis + except ConnectionError as er: + return False + + +async def is_redis_server_installed(): + proc = await asyncio.create_subprocess_exec("redis-server", "--version") + stdout, stderr = await proc.communicate() + return proc.returncode == 0 + + +async def start_redis_server(): + proc = await asyncio.create_subprocess_exec("redis-server", "--daemonize yes") + stdout, stderr = await proc.communicate() + await asyncio.sleep(2) # Wait for server to start + return proc.returncode == 0 + + +async def stop_redis_server(): + proc = await asyncio.create_subprocess_exec("redis-cli", "shutdown") + stdout, stderr = await proc.communicate() + return proc.returncode == 0 diff --git a/pyUltroid/scripts/webapp.py b/pyUltroid/scripts/webapp.py new file mode 100644 index 0000000000..448e85f979 --- /dev/null +++ b/pyUltroid/scripts/webapp.py @@ -0,0 +1,107 @@ +import aiohttp +import zipfile +import os +import logging +from pathlib import Path +from pyUltroid.state_config import temp_config_store +import shutil + + +async def fetch_recent_release(): + """ + Fetch the most recent release from GitHub, download the ultroid-dist.zip, + and extract it to the webapp directory. + Skip download if the same version is already installed. + """ + try: + # Configuration + repo = "TeamUltroid/webapp" + zip_filename = "ultroid-dist.zip" + webapp_path = Path("resources/webapp") # Adjust this path as needed + + # Create webapp directory if it doesn't exist + webapp_path.mkdir(parents=True, exist_ok=True) + + # Temporary file for the download + temp_zip = Path(f"resources/temp_{zip_filename}") + + # GitHub API endpoint to get the latest release + api_url = f"https://api.github.com/repos/{repo}/releases/latest" + + logging.info("Fetching latest webapp release info...") + + async with aiohttp.ClientSession() as session: + # Get latest release info + async with session.get(api_url) as response: + if response.status != 200: + logging.error( + f"Failed to fetch release info: HTTP {response.status}" + ) + return False + + data = await response.json() + + # Get the release version/tag + latest_version = data.get("tag_name") + + # Check if we already have this version + current_version = temp_config_store.get("webapp_version") + + if current_version == latest_version and webapp_path.exists(): + logging.info( + f"Webapp already at latest version {latest_version}. Skipping download." + ) + return True + + # Find the ultroid-dist.zip asset + asset_url = None + for asset in data.get("assets", []): + if asset["name"] == zip_filename: + asset_url = asset["browser_download_url"] + break + + if not asset_url: + logging.error( + f"Could not find {zip_filename} in the latest release" + ) + return False + + # Download the zip file + logging.info( + f"Downloading {zip_filename} (version {latest_version})..." + ) + async with session.get(asset_url) as zip_response: + if zip_response.status != 200: + logging.error( + f"Failed to download zip: HTTP {zip_response.status}" + ) + return False + + with open(temp_zip, "wb") as f: + f.write(await zip_response.read()) + + # Clear existing webapp files + logging.info("Clearing existing webapp files...") + for item in webapp_path.glob("*"): + if item.is_dir(): + shutil.rmtree(item) + else: + item.unlink() + + # Extract the zip file + logging.info(f"Extracting {zip_filename} to webapp directory...") + with zipfile.ZipFile(temp_zip, "r") as zip_ref: + zip_ref.extractall(webapp_path) + + # Clean up the temporary zip file + temp_zip.unlink() + + # Save the new version in config + temp_config_store.set("webapp_version", latest_version) + + logging.info(f"Webapp successfully updated to version {latest_version}!") + return True + + except Exception as e: + logging.error(f"Error updating webapp: {str(e)}") + return False diff --git a/pyUltroid/startup/BaseClient.py b/pyUltroid/startup/BaseClient.py index 603121b52e..d51cabecf7 100644 --- a/pyUltroid/startup/BaseClient.py +++ b/pyUltroid/startup/BaseClient.py @@ -113,8 +113,9 @@ async def fast_uploader(self, file, **kwargs): message = kwargs.get("message", f"Uploading {filename}...") by_bot = self._bot size = os.path.getsize(file) + # Don't show progress bar when file size is less than 5MB. - if size < 5 * 2 ** 20: + if size < 5 * 2**20: show_progress = False if use_cache and self._cache and self._cache.get("upload_cache"): for files in self._cache["upload_cache"]: @@ -140,12 +141,14 @@ async def fast_uploader(self, file, **kwargs): file=f, filename=filename, progress_callback=( - lambda completed, total: self.loop.create_task( - progress(completed, total, event, start_time, message) + ( + lambda completed, total: self.loop.create_task( + progress(completed, total, event, start_time, message) + ) ) - ) - if show_progress - else None, + if show_progress + else None + ), ) cache = { "by_bot": by_bot, @@ -171,7 +174,7 @@ async def fast_downloader(self, file, **kwargs): if show_progress: event = kwargs["event"] # Don't show progress bar when file size is less than 10MB. - if file.size < 10 * 2 ** 20: + if file.size < 10 * 2**20: show_progress = False import mimetypes @@ -204,12 +207,14 @@ async def fast_downloader(self, file, **kwargs): location=file, out=f, progress_callback=( - lambda completed, total: self.loop.create_task( - progress(completed, total, event, start_time, message) + ( + lambda completed, total: self.loop.create_task( + progress(completed, total, event, start_time, message) + ) ) - ) - if show_progress - else None, + if show_progress + else None + ), ) return raw_file, time.time() - start_time diff --git a/pyUltroid/startup/__init__.py b/pyUltroid/startup/__init__.py index 8d8063a535..a74c3955a6 100644 --- a/pyUltroid/startup/__init__.py +++ b/pyUltroid/startup/__init__.py @@ -10,13 +10,8 @@ import sys from logging import INFO, WARNING, FileHandler, StreamHandler, basicConfig, getLogger -from .. import run_as_module from ._extra import _ask_input - -if run_as_module: - from ..configs import Var -else: - Var = None +from ..configs import Var def where_hosted(): @@ -39,61 +34,54 @@ def where_hosted(): return "local" -if run_as_module: - from telethon import __version__ - from telethon.tl.alltlobjects import LAYER - - from ..version import __version__ as __pyUltroid__ - from ..version import ultroid_version - - file = f"ultroid{sys.argv[6]}.log" if len(sys.argv) > 6 else "ultroid.log" +from telethon import __version__ +from telethon.tl.alltlobjects import LAYER - if os.path.exists(file): - os.remove(file) +from ..version import __version__ as __pyUltroid__ +from ..version import ultroid_version - HOSTED_ON = where_hosted() - LOGS = getLogger("pyUltLogs") - TelethonLogger = getLogger("Telethon") - TelethonLogger.setLevel(INFO) +file = f"ultroid{sys.argv[6]}.log" if len(sys.argv) > 6 else "ultroid.log" - _, v, __ = platform.python_version_tuple() +if os.path.exists(file): + os.remove(file) - if int(v) < 10: - from ._extra import _fix_logging +HOSTED_ON = where_hosted() +LOGS = getLogger("pyUltLogs") +TelethonLogger = getLogger("Telethon") +TelethonLogger.setLevel(INFO) - _fix_logging(FileHandler) +_, v, __ = platform.python_version_tuple() - _ask_input() +_ask_input() - _LOG_FORMAT = "%(asctime)s | %(name)s [%(levelname)s] : %(message)s" - basicConfig( - format=_LOG_FORMAT, - level=INFO, - datefmt="%m/%d/%Y, %H:%M:%S", - handlers=[FileHandler(file), StreamHandler()], - ) - try: +_LOG_FORMAT = "%(asctime)s | %(name)s [%(levelname)s] : %(message)s" +basicConfig( + format=_LOG_FORMAT, + level=INFO, + datefmt="%m/%d/%Y, %H:%M:%S", + handlers=[FileHandler(file), StreamHandler()], +) +try: + import coloredlogs - import coloredlogs + coloredlogs.install(level=None, logger=LOGS, fmt=_LOG_FORMAT) +except ImportError: + pass - coloredlogs.install(level=None, logger=LOGS, fmt=_LOG_FORMAT) - except ImportError: - pass - - LOGS.info( - """ - ----------------------------------- - Starting Deployment - ----------------------------------- +LOGS.info( + """ + ----------------------------------- + Starting Deployment + ----------------------------------- """ - ) +) - LOGS.info(f"Python version - {platform.python_version()}") - LOGS.info(f"py-Ultroid Version - {__pyUltroid__}") - LOGS.info(f"Telethon Version - {__version__} [Layer: {LAYER}]") - LOGS.info(f"Ultroid Version - {ultroid_version} [{HOSTED_ON}]") +LOGS.info(f"Python version - {platform.python_version()}") +LOGS.info(f"py-Ultroid Version - {__pyUltroid__}") +LOGS.info(f"Telethon Version - {__version__} [Layer: {LAYER}]") +LOGS.info(f"Ultroid Version - {ultroid_version} [{HOSTED_ON}]") - try: - from safety.tools import * - except ImportError: - LOGS.error("'safety' package not found!") +try: + from safety.tools import * +except ImportError: + LOGS.error("'safety' package not found!") diff --git a/pyUltroid/startup/_database.py b/pyUltroid/startup/_database.py index f7bc274524..7a45c14a68 100644 --- a/pyUltroid/startup/_database.py +++ b/pyUltroid/startup/_database.py @@ -1,352 +1,86 @@ # Ultroid - UserBot # Copyright (C) 2021-2025 TeamUltroid # -# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# This file is a part of # PLease read the GNU Affero General Public License in # . -import ast -import os -import sys - -from .. import run_as_module +import asyncio from . import * - -if run_as_module: - from ..configs import Var - - -Redis = MongoClient = psycopg2 = Database = None -if Var.REDIS_URI or Var.REDISHOST: - try: - from redis import Redis - except ImportError: - LOGS.info("Installing 'redis' for database.") - os.system(f"{sys.executable} -m pip install -q redis hiredis") - from redis import Redis -elif Var.MONGO_URI: - try: - from pymongo import MongoClient - except ImportError: - LOGS.info("Installing 'pymongo' for database.") - os.system(f"{sys.executable} -m pip install -q pymongo[srv]") - from pymongo import MongoClient -elif Var.DATABASE_URL: - try: - import psycopg2 - except ImportError: - LOGS.info("Installing 'pyscopg2' for database.") - os.system(f"{sys.executable} -m pip install -q psycopg2-binary") - import psycopg2 -else: - try: - from localdb import Database - except ImportError: - LOGS.info("Using local file as database.") - os.system(f"{sys.executable} -m pip install -q localdb.json") - from localdb import Database - -# --------------------------------------------------------------------------------------------- # - - -class _BaseDatabase: - def __init__(self, *args, **kwargs): - self._cache = {} - - def get_key(self, key): - if key in self._cache: - return self._cache[key] - value = self._get_data(key) - self._cache.update({key: value}) - return value - - def re_cache(self): - self._cache.clear() - for key in self.keys(): - self._cache.update({key: self.get_key(key)}) - - def ping(self): - return 1 - - @property - def usage(self): - return 0 - - def keys(self): - return [] - - def del_key(self, key): - if key in self._cache: - del self._cache[key] - self.delete(key) - return True - - def _get_data(self, key=None, data=None): - if key: - data = self.get(str(key)) - if data and isinstance(data, str): - try: - data = ast.literal_eval(data) - except BaseException: - pass - return data - - def set_key(self, key, value, cache_only=False): - value = self._get_data(data=value) - self._cache[key] = value - if cache_only: - return - return self.set(str(key), str(value)) - - def rename(self, key1, key2): - _ = self.get_key(key1) - if _: - self.del_key(key1) - self.set_key(key2, _) - return 0 - return 1 +from ..configs import Var -class MongoDB(_BaseDatabase): - def __init__(self, key, dbname="UltroidDB"): - self.dB = MongoClient(key, serverSelectionTimeoutMS=5000) - self.db = self.dB[dbname] - super().__init__() - - def __repr__(self): - return f"" - - @property - def name(self): - return "Mongo" - - @property - def usage(self): - return self.db.command("dbstats")["dataSize"] - - def ping(self): - if self.dB.server_info(): - return True - - def keys(self): - return self.db.list_collection_names() - - def set(self, key, value): - if key in self.keys(): - self.db[key].replace_one({"_id": key}, {"value": str(value)}) - else: - self.db[key].insert_one({"_id": key, "value": str(value)}) - return True - - def delete(self, key): - self.db.drop_collection(key) - - def get(self, key): - if x := self.db[key].find_one({"_id": key}): - return x["value"] - - def flushall(self): - self.dB.drop_database("UltroidDB") - self._cache.clear() - return True - - -# --------------------------------------------------------------------------------------------- # - -# Thanks to "Akash Pattnaik" / @BLUE-DEVIL1134 -# for SQL Implementation in Ultroid. -# -# Please use https://elephantsql.com/ ! - +async def UltroidDB(): + from .. import HOSTED_ON -class SqlDB(_BaseDatabase): - def __init__(self, url): - self._url = url - self._connection = None - self._cursor = None + # Try Redis first if configured + if Var.REDIS_URI or Var.REDISHOST: try: - self._connection = psycopg2.connect(dsn=url) - self._connection.autocommit = True - self._cursor = self._connection.cursor() - self._cursor.execute( - "CREATE TABLE IF NOT EXISTS Ultroid (ultroidCli varchar(70))" - ) - except Exception as error: - LOGS.exception(error) - LOGS.info("Invaid SQL Database") - if self._connection: - self._connection.close() - sys.exit() - super().__init__() - - @property - def name(self): - return "SQL" + from ..database.redis import RedisDB - @property - def usage(self): - self._cursor.execute( - "SELECT pg_size_pretty(pg_relation_size('Ultroid')) AS size" - ) - data = self._cursor.fetchall() - return int(data[0][0].split()[0]) - - def keys(self): - self._cursor.execute( - "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'ultroid'" - ) # case sensitive - data = self._cursor.fetchall() - return [_[0] for _ in data] + return RedisDB( + host=Var.REDIS_URI or Var.REDISHOST, + password=Var.REDIS_PASSWORD or Var.REDISPASSWORD, + port=Var.REDISPORT, + platform=HOSTED_ON, + decode_responses=True, + socket_timeout=5, + retry_on_timeout=True, + ) + except Exception as e: + LOGS.warning(f"Redis connection failed: {e}") - def get(self, variable): + # Try MongoDB if configured + if Var.MONGO_URI: try: - self._cursor.execute(f"SELECT {variable} FROM Ultroid") - except psycopg2.errors.UndefinedColumn: - return None - data = self._cursor.fetchall() - if not data: - return None - if len(data) >= 1: - for i in data: - if i[0]: - return i[0] + from ..database.mongo import MongoDB - def set(self, key, value): - try: - self._cursor.execute(f"ALTER TABLE Ultroid DROP COLUMN IF EXISTS {key}") - except (psycopg2.errors.UndefinedColumn, psycopg2.errors.SyntaxError): - pass - except BaseException as er: - LOGS.exception(er) - self._cache.update({key: value}) - self._cursor.execute(f"ALTER TABLE Ultroid ADD {key} TEXT") - self._cursor.execute(f"INSERT INTO Ultroid ({key}) values (%s)", (str(value),)) - return True + return MongoDB(Var.MONGO_URI) + except Exception as e: + LOGS.warning(f"MongoDB connection failed: {e}") - def delete(self, key): - try: - self._cursor.execute(f"ALTER TABLE Ultroid DROP COLUMN {key}") - except psycopg2.errors.UndefinedColumn: - return False - return True + # Try local Redis server + try: + from ..database.redis import RedisDB - def flushall(self): - self._cache.clear() - self._cursor.execute("DROP TABLE Ultroid") - self._cursor.execute( - "CREATE TABLE IF NOT EXISTS Ultroid (ultroidCli varchar(70))" + from ..scripts.redis import ( + is_redis_server_installed, + connect_localhost_redis, + start_redis_server, ) - return True - - -# --------------------------------------------------------------------------------------------- # - -class RedisDB(_BaseDatabase): - def __init__( - self, - host, - port, - password, - platform="", - logger=LOGS, - *args, - **kwargs, - ): - if host and ":" in host: - spli_ = host.split(":") - host = spli_[0] - port = int(spli_[-1]) - if host.startswith("http"): - logger.error("Your REDIS_URI should not start with http !") - import sys + # Check for running Redis instance first + if redis := connect_localhost_redis(): + LOGS.info("Connected to running Redis server") + return RedisDB(client=redis) - sys.exit() - elif not host or not port: - logger.error("Port Number not found") - import sys + # Try to start Redis if not running + if not await is_redis_server_installed(): + raise ConnectionError("Redis server is not installed") - sys.exit() - kwargs["host"] = host - kwargs["password"] = password - kwargs["port"] = port + if await start_redis_server(): + LOGS.info("Started new Redis server") + if redis := connect_localhost_redis(): + return RedisDB(client=redis) + raise ConnectionError("Failed to connect to newly started Redis server") - if platform.lower() == "qovery" and not host: - var, hash_, host, password = "", "", "", "" - for vars_ in os.environ: - if vars_.startswith("QOVERY_REDIS_") and vars.endswith("_HOST"): - var = vars_ - if var: - hash_ = var.split("_", maxsplit=2)[1].split("_")[0] - if hash: - kwargs["host"] = os.environ.get(f"QOVERY_REDIS_{hash_}_HOST") - kwargs["port"] = os.environ.get(f"QOVERY_REDIS_{hash_}_PORT") - kwargs["password"] = os.environ.get(f"QOVERY_REDIS_{hash_}_PASSWORD") - self.db = Redis(**kwargs) - self.set = self.db.set - self.get = self.db.get - self.keys = self.db.keys - self.delete = self.db.delete - super().__init__() - - @property - def name(self): - return "Redis" - - @property - def usage(self): - return sum(self.db.memory_usage(x) for x in self.keys()) - - -# --------------------------------------------------------------------------------------------- # + raise ConnectionError("Failed to start Redis server") + except Exception as e: + LOGS.warning(f"Local Redis setup failed: {e}") -class LocalDB(_BaseDatabase): - def __init__(self): - self.db = Database("ultroid") - self.get = self.db.get - self.set = self.db.set - self.delete = self.db.delete - super().__init__() - - @property - def name(self): - return "LocalDB" - - def keys(self): - return self._cache.keys() - - def __repr__(self): - return f"" - - -def UltroidDB(): - _er = False - from .. import HOSTED_ON - + # Fallback to memory database try: - if Redis: - return RedisDB( - host=Var.REDIS_URI or Var.REDISHOST, - password=Var.REDIS_PASSWORD or Var.REDISPASSWORD, - port=Var.REDISPORT, - platform=HOSTED_ON, - decode_responses=True, - socket_timeout=5, - retry_on_timeout=True, - ) - elif MongoClient: - return MongoDB(Var.MONGO_URI) - elif psycopg2: - return SqlDB(Var.DATABASE_URL) - else: - LOGS.critical( - "No DB requirement fullfilled!\nPlease install redis, mongo or sql dependencies...\nTill then using local file as database." - ) - return LocalDB() - except BaseException as err: - LOGS.exception(err) - exit() + from ..database.base import BaseDatabase + + LOGS.critical( + "No database requirements fulfilled! Using memory database as fallback. " + "Please install Redis, MongoDB or SQL dependencies for persistent storage." + ) + return BaseDatabase() + except Exception as e: + LOGS.critical(f"Failed to initialize memory database: {e}") + exit(1) # --------------------------------------------------------------------------------------------- # diff --git a/pyUltroid/startup/_extra.py b/pyUltroid/startup/_extra.py index e71907f841..6b0baf8acd 100644 --- a/pyUltroid/startup/_extra.py +++ b/pyUltroid/startup/_extra.py @@ -5,20 +5,6 @@ # PLease read the GNU Affero General Public License in # . -# https://bugs.python.org/issue26789 -# 'open' not defined has been fixed in Python3.10 -# for other older versions, something need to be done. - - -def _fix_logging(handler): - handler._builtin_open = open - - def _new_open(self): - open_func = self._builtin_open - return open_func(self.baseFilename, self.mode) - - setattr(handler, "_open", _new_open) - def _ask_input(): # Ask for Input even on Vps and other platforms. diff --git a/pyUltroid/startup/connections.py b/pyUltroid/startup/connections.py index 005f199218..aac9f0d716 100644 --- a/pyUltroid/startup/connections.py +++ b/pyUltroid/startup/connections.py @@ -12,25 +12,19 @@ from telethon.errors.rpcerrorlist import AuthKeyDuplicatedError from telethon.sessions.string import _STRUCT_PREFORMAT, CURRENT_VERSION, StringSession +from logging import getLogger from ..configs import Var -from . import * from .BaseClient import UltroidClient _PYRO_FORM = {351: ">B?256sI?", 356: ">B?256sQ?", 362: ">BI?256sQ?"} -# https://github.com/pyrogram/pyrogram/blob/master/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst +logger = getLogger(__name__) -DC_IPV4 = { - 1: "149.154.175.53", - 2: "149.154.167.51", - 3: "149.154.175.100", - 4: "149.154.167.91", - 5: "91.108.56.130", -} +# https://github.com/pyrogram/pyrogram/blob/master/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst -def validate_session(session, logger=LOGS, _exit=True): +def validate_session(session, logger=logger, _exit=True): from strings import get_string if session: @@ -43,6 +37,14 @@ def validate_session(session, logger=LOGS, _exit=True): # Pyrogram Session elif len(session) in _PYRO_FORM.keys(): + DC_IPV4 = { + 1: "149.154.175.53", + 2: "149.154.167.51", + 3: "149.154.175.100", + 4: "149.154.167.91", + 5: "91.108.56.130", + } + data_ = struct.unpack( _PYRO_FORM[len(session)], base64.urlsafe_b64decode(session + "=" * (-len(session) % 4)), @@ -78,7 +80,7 @@ def vc_connection(udB, ultroid_bot): VC_SESSION = Var.VC_SESSION or udB.get_key("VC_SESSION") if VC_SESSION and VC_SESSION != Var.SESSION: - LOGS.info("Starting up VcClient.") + logger.info("Starting up VcClient.") try: return UltroidClient( validate_session(VC_SESSION, _exit=False), @@ -86,9 +88,9 @@ def vc_connection(udB, ultroid_bot): exit_on_error=False, ) except (AuthKeyDuplicatedError, EOFError): - LOGS.info(get_string("py_c3")) + logger.info(get_string("py_c3")) udB.del_key("VC_SESSION") except Exception as er: - LOGS.info("While creating Client for VC.") - LOGS.exception(er) + logger.info("While creating Client for VC.") + logger.exception(er) return ultroid_bot diff --git a/pyUltroid/startup/funcs.py b/pyUltroid/startup/funcs.py index f5338fa709..370bd8e5bb 100644 --- a/pyUltroid/startup/funcs.py +++ b/pyUltroid/startup/funcs.py @@ -11,6 +11,8 @@ import shutil import time from random import randint +import base64 +from urllib.parse import unquote from ..configs import Var @@ -43,6 +45,11 @@ ) from telethon.utils import get_peer_id from decouple import config, RepositoryEnv +from telethon import functions +from urllib.parse import urlparse, parse_qs +import json +from datetime import datetime +import requests from .. import LOGS, ULTConfig from ..fns.helper import download_file, inline_mention, updater @@ -90,6 +97,7 @@ async def autoupdate_local_database(): def update_envs(): """Update Var. attributes to udB""" from .. import udB + _envs = [*list(os.environ)] if ".env" in os.listdir("."): [_envs.append(_) for _ in list(RepositoryEnv(config._find_file(".")).data)] @@ -107,11 +115,37 @@ def update_envs(): async def startup_stuff(): from .. import udB + # Create essential directories first - fast operation x = ["resources/auth", "resources/downloads"] for x in x: if not os.path.isdir(x): os.mkdir(x) + # Handle critical configuration first + TZ = udB.get_key("TIMEZONE") + if TZ and timezone: + try: + timezone(TZ) + os.environ["TZ"] = TZ + time.tzset() + except AttributeError as er: + LOGS.debug(er) + except BaseException: + LOGS.critical( + "Incorrect Timezone ,\nCheck Available Timezone From Here https://graph.org/Ultroid-06-18-2\nSo Time is Default UTC" + ) + os.environ["TZ"] = "UTC" + time.tzset() + + # Start background task for less critical operations + asyncio.create_task(_delayed_startup_operations()) + + +async def _delayed_startup_operations(): + # These operations are moved to a separate function to run in background + from .. import udB, ULTConfig + from ..fns.helper import download_file + CT = udB.get_key("CUSTOM_THUMBNAIL") if CT: path = "resources/extras/thumbnail.jpg" @@ -122,10 +156,11 @@ async def startup_stuff(): LOGS.exception(er) elif CT is False: ULTConfig.thumb = None + GT = udB.get_key("GDRIVE_AUTH_TOKEN") if GT: with open("resources/auth/gdrive_creds.json", "w") as t_file: - t_file.write(GT) + t_file.write(json.dumps(GT)) if udB.get_key("AUTH_TOKEN"): udB.del_key("AUTH_TOKEN") @@ -136,21 +171,6 @@ async def startup_stuff(): with open(".megarc", "w") as mega: mega.write(f"[Login]\nUsername = {MM}\nPassword = {MP}") - TZ = udB.get_key("TIMEZONE") - if TZ and timezone: - try: - timezone(TZ) - os.environ["TZ"] = TZ - time.tzset() - except AttributeError as er: - LOGS.debug(er) - except BaseException: - LOGS.critical( - "Incorrect Timezone ,\nCheck Available Timezone From Here https://graph.org/Ultroid-06-18-2\nSo Time is Default UTC" - ) - os.environ["TZ"] = "UTC" - time.tzset() - async def autobot(): from .. import udB, ultroid_bot @@ -221,8 +241,10 @@ async def autobot(): async def autopilot(): + # Split into critical and non-critical operations from .. import asst, udB, ultroid_bot + # Critical: Ensure LOG_CHANNEL exists channel = udB.get_key("LOG_CHANNEL") new_channel = None if channel: @@ -232,8 +254,9 @@ async def autopilot(): LOGS.exception(err) udB.del_key("LOG_CHANNEL") channel = None - if not channel: + if not channel: + # Create log channel if needed - this is critical for operation async def _save(exc): udB._cache["LOG_CHANNEL"] = ultroid_bot.me.id await asst.send_message( @@ -244,6 +267,7 @@ async def _save(exc): msg_ = "'LOG_CHANNEL' not found! Add it in order to use 'BOTMODE'" LOGS.error(msg_) return await _save(msg_) + LOGS.info("Creating a Log Channel for You!") try: r = await ultroid_bot( @@ -263,12 +287,25 @@ async def _save(exc): LOGS.info( "Something Went Wrong , Create A Group and set its id on config var LOG_CHANNEL." ) - return await _save(str(er)) + new_channel = True chat = r.chats[0] channel = get_peer_id(chat) udB.set_key("LOG_CHANNEL", channel) + + # Start non-critical operations in background + asyncio.create_task(_delayed_autopilot(channel, new_channel)) + + return channel + + +async def _delayed_autopilot(channel, new_channel): + # Non-critical autopilot operations + from .. import asst, udB, ultroid_bot + from ..fns.helper import download_file + + # Handle assistant bot permissions in log channel assistant = True try: await ultroid_bot.get_permissions(int(channel), asst.me.username) @@ -282,6 +319,7 @@ async def _save(exc): except BaseException as er: assistant = False LOGS.exception(er) + if assistant and new_channel: try: achat = await asst.get_entity(int(channel)) @@ -289,6 +327,7 @@ async def _save(exc): achat = None LOGS.info("Error while getting Log channel from Assistant") LOGS.exception(er) + if achat and not achat.admin_rights: rights = ChatAdminRights( add_admins=True, @@ -313,18 +352,25 @@ async def _save(exc): except BaseException as er: LOGS.info("Error while promoting assistant in Log Channel..") LOGS.exception(er) - if isinstance(chat.photo, ChatPhotoEmpty): - photo, _ = await download_file( - "https://graph.org/file/27c6812becf6f376cbb10.jpg", "channelphoto.jpg" - ) - ll = await ultroid_bot.upload_file(photo) - try: - await ultroid_bot( - EditPhotoRequest(int(channel), InputChatUploadedPhoto(ll)) - ) - except BaseException as er: - LOGS.exception(er) - os.remove(photo) + + # Set channel photo - low priority + try: + chat = await ultroid_bot.get_entity(int(channel)) + if isinstance(chat.photo, ChatPhotoEmpty): + try: + photo, _ = await download_file( + "https://graph.org/file/27c6812becf6f376cbb10.jpg", + "channelphoto.jpg", + ) + ll = await ultroid_bot.upload_file(photo) + await ultroid_bot( + EditPhotoRequest(int(channel), InputChatUploadedPhoto(ll)) + ) + os.remove(photo) + except BaseException as er: + LOGS.exception(er) + except Exception as e: + LOGS.exception(e) # customize assistant @@ -432,7 +478,6 @@ async def plug(plugin_channels): LOGS.exception(er) - async def ready(): from .. import asst, udB, ultroid_bot @@ -527,3 +572,123 @@ async def enable_inline(ultroid_bot, username): await asyncio.sleep(1) await ultroid_bot.send_message(bf, "Search") await ultroid_bot.send_read_acknowledge(bf) + + +async def user_sync_workflow(): + from .. import udB, ultroid_bot + from ..configs import CENTRAL_REPO_URL, ADMIN_BOT_USERNAME + + from ..state_config import temp_config_store + + # Check if user data exists in temp config store + user_data = temp_config_store.get("X-TG-USER") + + if user_data and (user_data.get("id") != ultroid_bot.uid): + temp_config_store.remove("X-TG-USER") + temp_config_store.remove(f"X-TG-INIT-DATA-{user_data['id']}") + temp_config_store.remove(f"X-TG-HASH-{user_data['id']}") + + if user_data: + user_id = str(user_data["id"]) + # Get encoded data and decode it + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{user_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + # Decode the data + init_data = ( + base64.b64decode(encoded_init_data.encode()).decode() + if encoded_init_data + else None + ) + hash_value = ( + base64.b64decode(encoded_hash.encode()).decode() if encoded_hash else None + ) + + return await authenticate_user_request(user_data, init_data, hash_value) + + try: + + url = ( + await ultroid_bot( + functions.messages.RequestWebViewRequest( + ADMIN_BOT_USERNAME, + ADMIN_BOT_USERNAME, + platform="android", + from_bot_menu=False, + url=CENTRAL_REPO_URL, + ) + ) + ).url + + # Parse the URL fragment to get webAppData + fragment = urlparse(url).fragment + params = parse_qs(fragment) + tg_web_data_raw = params.get("tgWebAppData", [None])[0] + + # Parse the tgWebAppData parameters + tg_data_params = parse_qs(tg_web_data_raw) if tg_web_data_raw else {} + + # Extract and parse user data + user_str = tg_data_params.get("user", [None])[0] + if not user_str: + raise Exception("No user data found") + + user = json.loads(unquote(user_str)) + hash_value = tg_data_params.get("hash", [None])[0] + + await authenticate_user_request(user, tg_web_data_raw, hash_value) + except Exception as e: + LOGS.exception(e) + + +async def authenticate_user_request(user: dict, init_data: str, hash_value: str): + from .. import udB, ultroid_bot + from ..configs import CENTRAL_REPO_URL, ADMIN_BOT_USERNAME + + from ..state_config import temp_config_store + + try: + + # Prepare user data payload + user_data = { + "id": user["id"], + "first_name": user["first_name"], + "last_name": user.get("last_name", ""), + "username": user["username"], + "language_code": user["language_code"], + "photo_url": user["photo_url"], + "last_active": datetime.now().isoformat(), + "joined_at": datetime.now().isoformat(), + } + + # Encode init data and hash using base64 before storing + user_id = str(user["id"]) + encoded_init_data = ( + base64.b64encode(init_data.encode()).decode() if init_data else "" + ) + encoded_hash = ( + base64.b64encode(hash_value.encode()).decode() if hash_value else "" + ) + + # Store with user ID as part of the key + temp_config_store.set(f"X-TG-INIT-DATA-{user_id}", encoded_init_data) + temp_config_store.set(f"X-TG-HASH-{user_id}", encoded_hash) + temp_config_store.set("X-TG-USER", user_data) + + # Make PUT request + response = requests.put( + f"{CENTRAL_REPO_URL}/api/v1/users/{user['id']}", + headers={ + "Content-Type": "application/json", + "x-telegram-init-data": init_data, + "x-telegram-hash": hash_value or "", + }, + json=user_data, + ) + if response.status_code == 200: + LOGS.info(f"User {user['id']} authenticated successfully") + else: + LOGS.error(f"User {user['id']} authentication failed") + + except Exception as e: + LOGS.exception(e) diff --git a/pyUltroid/startup/loader.py b/pyUltroid/startup/loader.py index 5f49114a2e..7064568034 100644 --- a/pyUltroid/startup/loader.py +++ b/pyUltroid/startup/loader.py @@ -9,6 +9,10 @@ import subprocess import sys from shutil import rmtree +import json +from pathlib import Path +import aiohttp +import base64 from decouple import config from git import Repo @@ -18,13 +22,20 @@ from ..loader import Loader from . import * from .utils import load_addons - +from ..configs import CENTRAL_REPO_URL def _after_load(loader, module, plugin_name=""): if not module or plugin_name.startswith("_"): return from strings import get_help + # Normalize Addons plugin names by stripping last two hashes + normalized_name = plugin_name + if loader.key == "Addons" and plugin_name.count("_") >= 2: + normalized_name = "_".join(plugin_name.rsplit("_", 2)[:-2]) + else: + normalized_name = plugin_name + if doc_ := get_help(plugin_name) or module.__doc__: try: doc = doc_.format(i=HNDLR) @@ -35,17 +46,18 @@ def _after_load(loader, module, plugin_name=""): if loader.key in HELP.keys(): update_cmd = HELP[loader.key] try: - update_cmd.update({plugin_name: doc}) + update_cmd.update({normalized_name: doc}) except BaseException as er: loader._logger.exception(er) else: try: - HELP.update({loader.key: {plugin_name: doc}}) + HELP.update({loader.key: {normalized_name: doc}}) except BaseException as em: loader._logger.exception(em) def load_other_plugins(addons=None, pmbot=None, manager=None, vcbot=None): + import asyncio # for official _exclude = udB.get_key("EXCLUDE_OFFICIAL") or config("EXCLUDE_OFFICIAL", None) @@ -54,46 +66,293 @@ def load_other_plugins(addons=None, pmbot=None, manager=None, vcbot=None): # "INCLUDE_ONLY" was added to reduce Big List in "EXCLUDE_OFFICIAL" Plugin _in_only = udB.get_key("INCLUDE_ONLY") or config("INCLUDE_ONLY", None) _in_only = _in_only.split() if _in_only else [] + + # Load official plugins first - these are critical for core functionality Loader().load(include=_in_only, exclude=_exclude, after_load=_after_load) - # for assistant + # List to collect all background tasks + background_tasks = [] + + # for assistant - load in background if not USER_MODE and not udB.get_key("DISABLE_AST_PLUGINS"): _ast_exc = ["pmbot"] if _in_only and "games" not in _in_only: _ast_exc.append("games") - Loader(path="assistant").load( - log=False, exclude=_ast_exc, after_load=_after_load - ) - # for addons + # Create an async function that can be used with create_task + async def load_assistant(): + Loader(path="assistant").load( + log=False, exclude=_ast_exc, after_load=_after_load + ) + + # Add to background tasks + loop = asyncio.get_event_loop() + background_tasks.append(loop.create_task(load_assistant())) + + # for addons - prepare in background but don't block startup if addons: - if url := udB.get_key("ADDONS_URL"): - subprocess.run(f"git clone -q {url} addons", shell=True) - if os.path.exists("addons") and not os.path.exists("addons/.git"): - rmtree("addons") + loop = asyncio.get_event_loop() + background_tasks.append(loop.create_task(setup_addons())) + + if not USER_MODE: + # Load these in background as they're not critical + async def load_extra_modules(): + # group manager + if manager: + Loader(path="assistant/manager", key="Group Manager").load() + + # chat via assistant + if pmbot: + Loader(path="assistant/pmbot.py").load(log=False) + + # Add to background tasks + loop = asyncio.get_event_loop() + background_tasks.append(loop.create_task(load_extra_modules())) + + # vc bot - load in background + if vcbot and (vcClient and not vcClient.me.bot): + + async def setup_vcbot(): + try: + import pytgcalls # ignore: pylint + + if os.path.exists("vcbot"): + if os.path.exists("vcbot/.git"): + subprocess.run("cd vcbot && git pull", shell=True) + else: + rmtree("vcbot") + if not os.path.exists("vcbot"): + subprocess.run( + "git clone https://github.com/TeamUltroid/VcBot vcbot", + shell=True, + ) + try: + if not os.path.exists("vcbot/downloads"): + os.mkdir("vcbot/downloads") + Loader(path="vcbot", key="VCBot").load(after_load=_after_load) + except FileNotFoundError as e: + LOGS.error(f"{e} Skipping VCBot Installation.") + except ModuleNotFoundError: + LOGS.error("'pytgcalls' not installed!\nSkipping loading of VCBOT.") + + # Add to background tasks + background_tasks.append(asyncio.create_task(setup_vcbot())) + + # Return the list of background tasks in case the caller wants to await them + return background_tasks + +async def check_for_updates(): + """Check for updates to official plugins using compute_diff endpoint.""" + try: + from ..state_config import temp_config_store + stored_states = json.loads(temp_config_store.get("OFFICIAL_PLUGINS_STATE") or "{}") + + if not stored_states: + LOGS.info("No stored plugin states found. Skipping update check.") + return [] + + # Get authentication data + user_data = temp_config_store.get("X-TG-USER") + if not user_data: + LOGS.error("No authentication data found. Please authenticate first.") + return [] + + user_id = str(user_data["id"]) + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{user_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + if not encoded_init_data or not encoded_hash: + LOGS.error("Missing authentication tokens. Please authenticate first.") + return [] + + # Decode authentication data + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + async with aiohttp.ClientSession() as session: + api_url = f"{CENTRAL_REPO_URL}/api/v1/plugins/compute_diff" + + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + async with session.post( + api_url, + json=stored_states, + headers=headers + ) as response: + if response.status == 200: + data = await response.json() + updates = data.get("updates_available", []) + + if updates: + LOGS.info(f"Found {len(updates)} plugin updates available") + return updates + else: + LOGS.info("All plugins are up to date") + return [] + else: + LOGS.error(f"Failed to check for updates. Status: {response.status}") + return [] + except Exception as e: + LOGS.error(f"Error checking for plugin updates: {str(e)}") + return [] + +async def setup_addons(): + """Setup and load addons/plugins.""" + if url := udB.get_key("ADDONS_URL"): + subprocess.run(f"git clone -q {url} addons", shell=True) + + if not os.path.exists("addons/__init__.py"): + with open("addons/__init__.py", "w") as f: + f.write("from plugins import *") + + if udB.get_key("INCLUDE_ALL"): + # Query official plugins and sync them + try: + # Get authentication data + from ..state_config import temp_config_store + user_data = temp_config_store.get("X-TG-USER") + if not user_data: + LOGS.error("No authentication data found. Please authenticate first.") + return + + user_id = str(user_data["id"]) + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{user_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + if not encoded_init_data or not encoded_hash: + LOGS.error("Missing authentication tokens. Please authenticate first.") + return + + # Decode authentication data + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + async with aiohttp.ClientSession() as session: + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + # Get all official plugins with offset-based pagination + api_url = f"{CENTRAL_REPO_URL}/api/v1/plugins/official" + limit = 100 + offset = 0 + all_plugins = [] + while True: + params = { + "limit": limit, + "offset": offset, + "sort_by": "updated_at", + "sort_order": "desc" + } + async with session.get(api_url, params=params, headers=headers) as response: + if response.status == 200: + data = await response.json() + plugins = data.get("plugins", []) + all_plugins.extend(plugins) + pagination = data.get("pagination", {}) + if not pagination.get("has_next_page"): + break + offset += limit + else: + error_text = await response.text() + LOGS.error(f"Failed to fetch official plugins. Status: {response.status}, Error: {error_text}") + break + + if not all_plugins: + LOGS.warning("No official plugins found") + return + + # Create addons directory if it doesn't exist + addons_dir = Path(__file__).parent.parent.parent / "addons" + addons_dir.mkdir(exist_ok=True) + + # Track plugin states for compute_diff + plugin_states = {} + + # Async function to download a single plugin + async def download_plugin(plugin): + try: + plugin_id = str(plugin["id"]) + plugin_title = plugin["title"] + download_url = plugin["download_link"] + updated_at = plugin["updated_at"] + file_path = plugin.get("file_path") + + if not file_path: + LOGS.warning(f"Missing file_path for plugin {plugin_title}, skipping") + return None + + # Generate safe filename from file_path + safe_filename = os.path.basename(file_path) + if not safe_filename.endswith('.py'): + safe_filename += '.py' + + target_path = addons_dir / safe_filename + + # Download the plugin + async with session.get(download_url) as plugin_response: + if plugin_response.status == 200: + plugin_content = await plugin_response.text() + # Write file with explicit UTF-8 encoding + target_path.write_text(plugin_content, encoding='utf-8') + # No per-plugin success log + return (plugin_id, updated_at) + else: + LOGS.error(f"Failed to download plugin {plugin_title}. Status: {plugin_response.status}") + return None + except Exception as e: + LOGS.error(f"Error processing plugin {plugin.get('title', 'unknown')}: {str(e)}") + return None + + # Download plugins in batches using asyncio.gather + batch_size = 10 + plugin_results = [] + for i in range(0, len(all_plugins), batch_size): + batch = all_plugins[i:i+batch_size] + results = await asyncio.gather(*(download_plugin(plugin) for plugin in batch)) + plugin_results.extend(results) + + # Store plugin states in temp config + for result in plugin_results: + if result: + plugin_id, updated_at = result + plugin_states[plugin_id] = updated_at + + if plugin_states: + LOGS.info(f"Successfully downloaded {len(plugin_states)} official plugins") + temp_config_store.set("OFFICIAL_PLUGINS_STATE", json.dumps(plugin_states)) + LOGS.info(f"Successfully synced {len(plugin_states)} official plugins") + else: + LOGS.warning("No plugins were successfully processed") + except Exception as e: + LOGS.error(f"Error syncing official plugins: {str(e)}") + + # Fallback to UltroidAddons if official plugins sync fails if not os.path.exists("addons"): - subprocess.run( - f"git clone -q -b {Repo().active_branch} https://github.com/TeamUltroid/UltroidAddons.git addons", - shell=True, - ) + try: + repo = Repo() + branch = repo.active_branch.name + subprocess.run( + f"git clone -q -b {branch} https://github.com/TeamUltroid/UltroidAddons.git addons", + shell=True, + check=True + ) + except Exception as e: + LOGS.error(f"Failed to clone UltroidAddons: {str(e)}") + # Try master branch as fallback + subprocess.run( + "git clone -q https://github.com/TeamUltroid/UltroidAddons.git addons", + shell=True + ) else: subprocess.run("cd addons && git pull -q && cd ..", shell=True) - if not os.path.exists("addons"): - subprocess.run( - "git clone -q https://github.com/TeamUltroid/UltroidAddons.git addons", - shell=True, - ) - if os.path.exists("addons/addons.txt"): - # generally addons req already there so it won't take much time - # subprocess.run( - # "rm -rf /usr/local/lib/python3.*/site-packages/pip/_vendor/.wh*" - # ) - subprocess.run( - f"{sys.executable} -m pip install --no-cache-dir -q -r ./addons/addons.txt", - shell=True, - ) - _exclude = udB.get_key("EXCLUDE_ADDONS") _exclude = _exclude.split() if _exclude else [] _in_only = udB.get_key("INCLUDE_ADDONS") @@ -106,35 +365,3 @@ def load_other_plugins(addons=None, pmbot=None, manager=None, vcbot=None): after_load=_after_load, load_all=True, ) - - if not USER_MODE: - # group manager - if manager: - Loader(path="assistant/manager", key="Group Manager").load() - - # chat via assistant - if pmbot: - Loader(path="assistant/pmbot.py").load(log=False) - - # vc bot - if vcbot and (vcClient and not vcClient.me.bot): - try: - import pytgcalls # ignore: pylint - - if os.path.exists("vcbot"): - if os.path.exists("vcbot/.git"): - subprocess.run("cd vcbot && git pull", shell=True) - else: - rmtree("vcbot") - if not os.path.exists("vcbot"): - subprocess.run( - "git clone https://github.com/TeamUltroid/VcBot vcbot", shell=True - ) - try: - if not os.path.exists("vcbot/downloads"): - os.mkdir("vcbot/downloads") - Loader(path="vcbot", key="VCBot").load(after_load=_after_load) - except FileNotFoundError as e: - LOGS.error(f"{e} Skipping VCBot Installation.") - except ModuleNotFoundError: - LOGS.error("'pytgcalls' not installed!\nSkipping loading of VCBOT.") diff --git a/pyUltroid/startup/utils.py b/pyUltroid/startup/utils.py index 5445dbf534..f3efda40ee 100644 --- a/pyUltroid/startup/utils.py +++ b/pyUltroid/startup/utils.py @@ -29,6 +29,7 @@ def load_addons(plugin_name): + plugin_name = str(plugin_name) base_name = plugin_name.split("/")[-1].split("\\")[-1].replace(".py", "") if base_name.startswith("__"): return diff --git a/pyUltroid/state_config.py b/pyUltroid/state_config.py new file mode 100644 index 0000000000..d281724807 --- /dev/null +++ b/pyUltroid/state_config.py @@ -0,0 +1,38 @@ +import json, os +from typing import Optional + + +class TempConfigHandler: + path = ".config/ultroid.json" + + def set(self, key: str, value: str): + os.makedirs(os.path.dirname(self.path), exist_ok=True) + try: + with open(self.path, "r+") as f: + try: + data = json.load(f) + except json.JSONDecodeError: + data = {} + except FileNotFoundError: + data = {} + + with open(self.path, "w") as f: + data[key] = value + json.dump(data, f, indent=4) + + def get(self, key: str) -> Optional[str]: + try: + with open(self.path, "r") as f: + data = json.load(f) + return data.get(key) + except (FileNotFoundError, json.JSONDecodeError): + return None + + def remove(self): + try: + os.remove(self.path) + except FileNotFoundError: + pass + + +temp_config_store = TempConfigHandler() diff --git a/pyUltroid/web/cache.py b/pyUltroid/web/cache.py new file mode 100644 index 0000000000..f065efec7b --- /dev/null +++ b/pyUltroid/web/cache.py @@ -0,0 +1,47 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import time +from typing import Dict, Any, Optional +import logging +from threading import Lock + +logger = logging.getLogger(__name__) + + +class TTLCache: + def __init__(self, ttl_seconds: int = 300): + self._cache: Dict[str, Dict[str, Any]] = {} + self._lock = Lock() + self.ttl = ttl_seconds + + def get(self, key: str) -> Optional[Dict[str, Any]]: + with self._lock: + if key not in self._cache: + return None + entry = self._cache[key] + if time.time() > entry["expires_at"]: + del self._cache[key] + return None + + return entry["data"] + + def set(self, key: str, value: Dict[str, Any]) -> None: + with self._lock: + self._cache[key] = {"data": value, "expires_at": time.time() + self.ttl} + + def invalidate(self, key: str) -> None: + with self._lock: + self._cache.pop(key, None) + + def clear(self) -> None: + with self._lock: + self._cache.clear() + + +# Global cache instance with 5 minute TTL +owner_cache = TTLCache(ttl_seconds=300) diff --git a/pyUltroid/web/decorators.py b/pyUltroid/web/decorators.py new file mode 100644 index 0000000000..1a45fc9a7c --- /dev/null +++ b/pyUltroid/web/decorators.py @@ -0,0 +1,117 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from functools import wraps +from typing import Callable, Optional, Any +import json +import os +from aiohttp import web +import logging +from .. import ultroid_bot + +logger = logging.getLogger(__name__) + + +def is_owner(user_id: Optional[int]) -> bool: + """Check if the user is the bot owner by comparing with ultroid_bot.me.id.""" + try: + return user_id == ultroid_bot.me.id + except Exception as e: + logger.error(f"Failed to check owner status: {e}") + return False + + +def route( + path: str, + method: str = "GET", + *, + authenticated: bool = True, + owner_only: bool = False, + description: Optional[str] = None, +) -> Callable: + """ + Route decorator with authentication and owner-only options. + + Args: + path: URL path for the route + method: HTTP method (GET, POST, etc.) + authenticated: Whether route requires TMA authentication + owner_only: Whether route is restricted to bot owner (checks against ultroid_bot.me.id) + description: Route description for documentation + + Example: + @route("/api/test", method="POST", authenticated=True, owner_only=True) + async def handler(request): + return web.json_response({"status": "ok"}) + """ + + def decorator(handler: Callable) -> Callable: + @wraps(handler) + async def wrapped(request: web.Request) -> web.Response: + try: + # Skip auth checks for unauthenticated routes + if not authenticated: + return await handler(request) + + # Get user from request (set by telegram_auth_middleware) + user = request.get("user", {}) + + # Check owner access if required + if owner_only: + user_id = user.get("id") + if not user_id or not is_owner(int(user_id)): + raise web.HTTPForbidden( + text=json.dumps( + {"error": "This endpoint is restricted to bot owner"} + ), + content_type="application/json", + ) + + return await handler(request) + + except web.HTTPException: + raise + except Exception as e: + logger.error(f"Error in route handler: {str(e)}", exc_info=True) + return web.json_response({"error": "Internal server error"}, status=500) + + # Store route metadata + wrapped._route = { + "path": path, + "method": method.upper(), + "authenticated": authenticated, + "owner_only": owner_only, + "description": description, + } + + return wrapped + + return decorator + + +def setup_routes(app: web.Application, handlers: list[Callable]) -> None: + """ + Setup routes from a list of handler functions decorated with @route. + + Args: + app: aiohttp Application instance + handlers: List of handler functions with route decorators + """ + for handler in handlers: + if hasattr(handler, "_route"): + route_info = handler._route + method = route_info["method"] + path = route_info["path"] + + # Add route to app + app.router.add_route(method, path, handler) + + logger.debug( + f"Added route: {method} {path} " + f"(auth: {route_info['authenticated']}, " + f"owner: {route_info['owner_only']})" + ) diff --git a/pyUltroid/web/middleware.py b/pyUltroid/web/middleware.py new file mode 100644 index 0000000000..b38f8acbd3 --- /dev/null +++ b/pyUltroid/web/middleware.py @@ -0,0 +1,160 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import logging +from aiohttp import web +from typing import Callable, Awaitable +import os, hmac, json, time, hashlib +from urllib.parse import parse_qs, unquote +from .. import udB + +logger = logging.getLogger(__name__) + +# API paths that don't require authentication +PUBLIC_PATHS = [ + "/health", + "/metrics", + "/api/user", + "/api/v1/plugins", # GET plugins list + "/api/v1/plugins/compute_diff", # POST compute updates + "/api/plugins/installed", # GET installed plugins list +] + +# Paths that only allow GET without authentication +GET_ONLY_PUBLIC_PATHS = [ + "/api/settings/miniapp", # Only GET is public, POST requires auth +] + +# Paths that start with these prefixes don't require auth +PUBLIC_PATH_PREFIXES = [ + "/api/v1/plugins/uploader/", # GET plugins by uploader +] + + +def parse_init_data(init_data_raw: str) -> dict: + try: + parsed = parse_qs(init_data_raw) + result = {} + for key, value in parsed.items(): + if key == "user" and value: + result[key] = json.loads(unquote(value[0])) + elif value: + result[key] = value[0] + return result + except Exception as e: + logger.error(f"Failed to parse init data: {e}", exc_info=True) + return {} + + +def checkValidateInitData( + hash_str: str, init_data_raw: str, token: str, c_str: str = "WebAppData" +) -> bool: + if not all([hash_str, init_data_raw, token]): + logger.error("Missing required validation arguments") + return False + try: + data_pairs = [] + for chunk in unquote(init_data_raw).split("&"): + if not chunk.startswith("hash="): + kv = chunk.split("=", 1) + if len(kv) == 2: + data_pairs.append(kv) + data_pairs.sort(key=lambda x: x[0]) + data_check_string = "\n".join([f"{rec[0]}={rec[1]}" for rec in data_pairs]) + logger.debug(f"Data check string for HMAC:\n{data_check_string}") + + secret_key = hmac.new(c_str.encode(), token.encode(), hashlib.sha256).digest() + calculated_hash = hmac.new( + secret_key, data_check_string.encode(), hashlib.sha256 + ).hexdigest() + + logger.debug(f"Expected hash: {hash_str}") + logger.debug(f"Calculated hash: {calculated_hash}") + return calculated_hash == hash_str + except Exception as e: + logger.error(f"Error in validation: {e}", exc_info=True) + return False + + +@web.middleware +async def telegram_auth_middleware( + request: web.Request, handler: Callable[[web.Request], Awaitable[web.Response]] +) -> web.Response: + # Always allow OPTIONS requests for CORS + # Allow public paths without authentication + # Allow GET requests for GET_ONLY_PUBLIC_PATHS + # Allow non-API paths without authentication + if ( + request.method == "OPTIONS" + or request.path in PUBLIC_PATHS + or (request.path in GET_ONLY_PUBLIC_PATHS and request.method == "GET") + or any(request.path.startswith(prefix) for prefix in PUBLIC_PATH_PREFIXES) + or request.path.startswith("/api/v1/plugins/") + and request.method == "GET" # Allow GET for individual plugins + or (not request.path.startswith("/api/")) + ): + return await handler(request) + + try: + bot_token = udB.get_key("BOT_TOKEN") + if not bot_token: + logger.error("BOT_TOKEN not set for: %s", request.path) + raise web.HTTPInternalServerError( + text=json.dumps({"error": "Server configuration error"}) + ) + + auth_header = request.headers.get("Authorization", "") + if not auth_header.startswith("tma "): + logger.warning("Invalid auth header for: %s", request.path) + raise web.HTTPUnauthorized( + text=json.dumps({"error": "Authorization required"}) + ) + + init_data_raw = auth_header[4:] + parsed_data = parse_init_data(init_data_raw) + + if not parsed_data or not (hash_to_verify := parsed_data.get("hash")): + logger.error("Invalid/missing init data for: %s", request.path) + raise web.HTTPBadRequest(text=json.dumps({"error": "Invalid init data"})) + + if not checkValidateInitData( + hash_str=hash_to_verify, init_data_raw=init_data_raw, token=bot_token + ): + logger.warning("Signature validation failed: %s", request.path) + raise web.HTTPUnauthorized(text=json.dumps({"error": "Invalid signature"})) + + if auth_date_str := parsed_data.get("auth_date"): + try: + auth_date = int(auth_date_str) + if time.time() - auth_date > 3600: + raise web.HTTPUnauthorized( + text=json.dumps({"error": "Authentication expired"}) + ) + except ValueError: + raise web.HTTPBadRequest( + text=json.dumps({"error": "Invalid auth_date"}) + ) + + for key in ["user", "start_param", "auth_date", "chat_type", "chat_instance"]: + request[key] = parsed_data.get(key, {} if key == "user" else None) + + logger.info( + "Auth success: %s, user: %s", + request.path, + parsed_data.get("user", {}).get("id", "unknown"), + ) + return await handler(request) + + except web.HTTPException as e: + if e.status_code not in [400, 401, 403]: + logger.error("HTTP error in auth: %s", e, exc_info=True) + raise + except Exception as e: + logger.error("Unexpected auth error: %s", e, exc_info=True) + raise web.HTTPInternalServerError( + text=json.dumps({"error": "Internal server error"}) + ) diff --git a/pyUltroid/web/routers/admin.py b/pyUltroid/web/routers/admin.py new file mode 100644 index 0000000000..2bfcd2249a --- /dev/null +++ b/pyUltroid/web/routers/admin.py @@ -0,0 +1,175 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from aiohttp import web +import os +import sys +import time +import logging +import asyncio +from typing import Optional +from pathlib import Path +from git import Repo +import json + +from ..decorators import route, setup_routes +from ..middleware import telegram_auth_middleware +from ... import ultroid_bot + +logger = logging.getLogger(__name__) + +try: + from git import Repo +except ImportError: + logger.error("admin: 'gitpython' module not found!") + Repo = None + + +def is_owner(user_id: Optional[int]) -> bool: + """Check if the user is the bot owner.""" + try: + return user_id == ultroid_bot.me.id + except Exception as e: + logger.error(f"Failed to check owner status: {e}") + return False + + +async def check_owner(request: web.Request) -> bool: + """Middleware to check if the user is the bot owner.""" + user = request.get("user", {}) + user_id = user.get("id") + if not user_id or not is_owner(int(user_id)): + raise web.HTTPForbidden( + text=json.dumps({"error": "Only bot owner can access this endpoint"}), + content_type="application/json", + ) + return True + + +async def restart_bot() -> None: + """Restart the bot process.""" + if os.getenv("DYNO"): # Heroku + os.system("kill 1") + else: + if len(sys.argv) > 1: + os.execl(sys.executable, sys.executable, "main.py") + else: + os.execl(sys.executable, sys.executable, "-m", "pyUltroid") + + +async def update_bot(fast: bool = False) -> dict: + """Update the bot from the repository.""" + try: + repo = Repo() + branch = repo.active_branch + + if fast: + stdout, stderr, code = await bash( + "git pull -f && pip3 install -r requirements.txt" + ) + if code != 0: + raise Exception(f"Fast update failed: {stderr}") + return { + "status": "success", + "message": "Fast update completed", + "restart_required": True, + } + + # Check for updates + origin = repo.remotes.origin + origin.fetch() + if not repo.is_dirty(): + commits_behind = sum( + 1 for _ in repo.iter_commits(f"{branch.name}..origin/{branch.name}") + ) + if commits_behind == 0: + return { + "status": "success", + "message": "Already up to date!", + "update_available": False, + } + + # Pull changes + stdout, stderr, code = await bash( + "git pull && pip3 install -r requirements.txt" + ) + if code != 0: + raise Exception(f"Update failed: {stderr}") + + return { + "status": "success", + "message": "Update successful", + "branch": branch.name, + "update_available": True, + "restart_required": True, + } + except Exception as e: + logger.error(f"Update failed: {str(e)}", exc_info=True) + return {"status": "error", "message": f"Update failed: {str(e)}"} + + +async def bash(cmd: str) -> tuple[str, str, int]: + """Execute a bash command and return stdout, stderr, and return code.""" + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await process.communicate() + return stdout.decode(), stderr.decode(), process.returncode + + +# Route handlers +@route( + "/api/admin/update", + method="POST", + owner_only=True, + description="Update the bot from repository", +) +async def handle_update(request: web.Request) -> web.Response: + """Handle bot update request. + Query params: + fast: bool - Whether to perform a fast update (force pull) + """ + await check_owner(request) + + try: + data = await request.json() if request.can_read_body else {} + fast = data.get("fast", False) + result = await update_bot(fast) + return web.json_response(result) + except json.JSONDecodeError: + # If no body provided, assume default options + result = await update_bot(False) + return web.json_response(result) + + +@route( + "/api/admin/restart", method="POST", owner_only=True, description="Restart the bot" +) +async def handle_restart(request: web.Request) -> web.Response: + """Handle bot restart request.""" + await check_owner(request) + + try: + # Schedule the restart + asyncio.create_task(restart_bot()) + return web.json_response({"status": "success", "message": "Restart initiated"}) + except Exception as e: + logger.error(f"Restart failed: {str(e)}", exc_info=True) + return web.json_response( + {"status": "error", "message": f"Restart failed: {str(e)}"}, status=500 + ) + + +# List of all handlers +handlers = [handle_update, handle_restart] + + +def setup_admin_routes(app: web.Application) -> None: + """Setup admin routes with authentication middleware.""" + setup_routes(app, handlers) diff --git a/pyUltroid/web/routers/miniapp.py b/pyUltroid/web/routers/miniapp.py new file mode 100644 index 0000000000..a07a7837b8 --- /dev/null +++ b/pyUltroid/web/routers/miniapp.py @@ -0,0 +1,111 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import logging +from aiohttp import web +from telethon import events +from telethon.tl.functions.messages import SetBotPrecheckoutResultsRequest +from telethon.tl.functions.payments import ExportInvoiceRequest +from telethon.tl.types import ( + DataJSON, + InputInvoiceMessage, + InputMediaInvoice, + LabeledPrice, + Invoice, + UpdateBotPrecheckoutQuery, +) + +from ... import asst, udB +from ..decorators import route, setup_routes +from .admin import check_owner, is_owner + +logger = logging.getLogger(__name__) + + +@asst.on(events.Raw(types=UpdateBotPrecheckoutQuery)) +async def handle_precheckout_query(event: UpdateBotPrecheckoutQuery): + """Handle pre-checkout queries to confirm transactions.""" + logger.info(f"Received pre-checkout query: {event}") + try: + await asst( + SetBotPrecheckoutResultsRequest( + query_id=event.query_id, + success=True, + ) + ) + logger.info(f"Successfully answered pre-checkout query: {event.query_id}") + except Exception as e: + logger.error( + f"Failed to answer pre-checkout query {event.query_id}: {e}", exc_info=True + ) + # Deny the transaction on failure + await asst( + SetBotPrecheckoutResultsRequest( + query_id=event.query_id, + success=False, + error="An internal error occurred. Please try again later.", + ) + ) + + +@route( + "/api/miniapp/create_invoice", + method="POST", + description="Create a donation invoice", +) +async def handle_create_invoice(request: web.Request) -> web.Response: + + try: + data = await request.json() + amount = int(data.get("amount")) + + if not amount: + return web.json_response({"error": "amount is required"}, status=400) + + # For XTR (Stars), no provider token is needed. + # The provider is Telegram. + title = f"Donate {amount} Stars" + description = f"Support Ultroid by donating {amount} Telegram Stars. ✨" + payload = f"ultroid_stars_{amount}".encode() + + # Create the invoice media + invoice_media = InputMediaInvoice( + title=title, + description=description, + invoice=Invoice( + currency="XTR", + prices=[LabeledPrice(label=f"{amount} Star(s)", amount=amount)], + test=False, # Set to False for real transactions + phone_requested=False, + email_requested=False, + shipping_address_requested=False, + flexible=False, + ), + payload=payload, + provider="telegram", + provider_data=DataJSON(data="{}"), + start_param="ultroid-donation", + ) + + # Export the invoice link + exported_invoice = await asst(ExportInvoiceRequest(invoice_media=invoice_media)) + + return web.json_response({"url": exported_invoice.url}) + + except Exception as e: + logger.error(f"Failed to create invoice: {e}", exc_info=True) + return web.json_response( + {"error": f"Failed to create invoice: {str(e)}"}, status=500 + ) + + +handlers = [handle_create_invoice] + + +def setup_miniapp_routes(app: web.Application) -> None: + """Setup miniapp routes.""" + setup_routes(app, handlers) diff --git a/pyUltroid/web/routers/plugins.py b/pyUltroid/web/routers/plugins.py new file mode 100644 index 0000000000..e21295fe2d --- /dev/null +++ b/pyUltroid/web/routers/plugins.py @@ -0,0 +1,268 @@ +import logging +import aiohttp +import base64 +from aiohttp import web +from ...state_config import temp_config_store +from ...configs import CENTRAL_REPO_URL + +logger = logging.getLogger(__name__) + + +async def get_auth_headers(request: web.Request): + """Get authentication headers for central API requests""" + from ... import ultroid_bot + + # Use bot ID for auth + user_id = str(ultroid_bot.me.id) + + # Get stored central API auth data from temp config + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{user_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + if not encoded_init_data or not encoded_hash: + raise web.HTTPUnauthorized(text="Central API authentication data not found.") + + # Decode the stored data for central API + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + # Return headers + return { + "Content-Type": "application/json", + "x-telegram-init-data": init_data, + "x-telegram-hash": hash_value, + } + + +async def proxy_request( + request: web.Request, path: str, method: str = "GET", require_auth: bool = False +): + """Generic proxy function to forward requests to central API""" + try: + # Get authentication headers if required + if require_auth: + headers = await get_auth_headers(request) + else: + headers = {"Content-Type": "application/json"} + + # Prepare the target URL + target_url = f"{CENTRAL_REPO_URL}{path}" + + async with aiohttp.ClientSession() as session: + # Forward the request with appropriate method + if method == "GET": + async with session.get(target_url, headers=headers) as response: + resp_json = await response.json() + return web.json_response(resp_json, status=response.status) + + elif method == "POST": + # Handle multipart form data for file uploads + if request.content_type.startswith("multipart/"): + data = await request.post() + form_data = aiohttp.FormData() + + # Add auth data to form fields if this is a protected route + if require_auth: + try: + from ... import ultroid_bot + + user_id = str(ultroid_bot.me.id) + encoded_init_data = temp_config_store.get( + f"X-TG-INIT-DATA-{user_id}" + ) + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + if encoded_init_data and encoded_hash: + init_data = base64.b64decode( + encoded_init_data.encode() + ).decode() + hash_value = base64.b64decode( + encoded_hash.encode() + ).decode() + + # Add auth data as form fields + form_data.add_field("x_telegram_init_data", init_data) + form_data.add_field("x_telegram_hash", hash_value) + except Exception as e: + logger.error(f"Error adding auth to form: {str(e)}") + + # Add the regular form data fields + for key, value in data.items(): + if hasattr(value, "file"): + form_data.add_field( + key, + value.file, + filename=value.filename, + content_type=value.content_type, + ) + else: + form_data.add_field(key, value) + + # For multipart, we need to remove content-type from headers + headers_without_content_type = headers.copy() + if "Content-Type" in headers_without_content_type: + headers_without_content_type.pop("Content-Type") + + async with session.post( + target_url, headers=headers_without_content_type, data=form_data + ) as response: + try: + resp_json = await response.json() + return web.json_response(resp_json, status=response.status) + except: + # Return raw text if not JSON + resp_text = await response.text() + return web.Response(text=resp_text, status=response.status) + else: + body = await request.json() + async with session.post( + target_url, headers=headers, json=body + ) as response: + resp_json = await response.json() + return web.json_response(resp_json, status=response.status) + + elif method == "PUT": + if request.content_type.startswith("multipart/"): + data = await request.post() + form_data = aiohttp.FormData() + + # Add auth data to form fields if this is a protected route + if require_auth: + try: + from ... import ultroid_bot + + user_id = str(ultroid_bot.me.id) + encoded_init_data = temp_config_store.get( + f"X-TG-INIT-DATA-{user_id}" + ) + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + if encoded_init_data and encoded_hash: + init_data = base64.b64decode( + encoded_init_data.encode() + ).decode() + hash_value = base64.b64decode( + encoded_hash.encode() + ).decode() + + # Add auth data as form fields + form_data.add_field("x_telegram_init_data", init_data) + form_data.add_field("x_telegram_hash", hash_value) + except Exception as e: + logger.error(f"Error adding auth to form for PUT: {str(e)}") + + # Add the regular form data fields + for key, value in data.items(): + if hasattr(value, "file"): + form_data.add_field( + key, + value.file, + filename=value.filename, + content_type=value.content_type, + ) + else: + form_data.add_field(key, value) + + # For multipart, remove Content-Type from headers + headers_without_content_type = headers.copy() + if "Content-Type" in headers_without_content_type: + headers_without_content_type.pop("Content-Type") + + async with session.put( + target_url, headers=headers_without_content_type, data=form_data + ) as response: + try: + resp_json = await response.json() + return web.json_response(resp_json, status=response.status) + except: + # Return raw text if not JSON + resp_text = await response.text() + return web.Response(text=resp_text, status=response.status) + else: + body = await request.json() + async with session.put( + target_url, headers=headers, json=body + ) as response: + resp_json = await response.json() + return web.json_response(resp_json, status=response.status) + + elif method == "DELETE": + async with session.delete(target_url, headers=headers) as response: + resp_json = await response.json() + return web.json_response(resp_json, status=response.status) + + except web.HTTPUnauthorized as e: + return web.json_response({"error": str(e)}, status=401) + except Exception as e: + logger.error(f"Error in proxy request: {e}", exc_info=True) + return web.json_response( + {"error": f"Internal server error: {str(e)}"}, status=500 + ) + + +# Plugin handlers +async def proxy_plugins_list(request: web.Request): + """Proxy GET /api/v1/plugins""" + return await proxy_request(request, "/api/v1/plugins", require_auth=True) + + +async def proxy_plugin_get(request: web.Request): + """Proxy GET /api/v1/plugins/{plugin_id}""" + plugin_id = request.match_info["plugin_id"] + return await proxy_request( + request, f"/api/v1/plugins/{plugin_id}", require_auth=True + ) + + +async def proxy_plugin_upload(request: web.Request): + """Proxy POST /api/v1/plugins""" + return await proxy_request(request, "/api/v1/plugins", "POST", require_auth=True) + + +async def proxy_plugin_update(request: web.Request): + """Proxy PUT /api/v1/plugins/{plugin_id}""" + plugin_id = request.match_info["plugin_id"] + return await proxy_request( + request, f"/api/v1/plugins/{plugin_id}", "PUT", require_auth=True + ) + + +async def proxy_plugin_delete(request: web.Request): + """Proxy DELETE /api/v1/plugins/{plugin_id}""" + plugin_id = request.match_info["plugin_id"] + return await proxy_request( + request, f"/api/v1/plugins/{plugin_id}", "DELETE", require_auth=True + ) + + +async def proxy_plugins_by_uploader(request: web.Request): + """Proxy GET /api/v1/plugins/uploader/{uploader_id}""" + uploader_id = request.match_info["uploader_id"] + return await proxy_request( + request, f"/api/v1/plugins/uploader/{uploader_id}", require_auth=True + ) + + +async def proxy_plugins_compute_diff(request: web.Request): + """Proxy POST /api/v1/plugins/compute_diff""" + return await proxy_request( + request, "/api/v1/plugins/compute_diff", "POST", require_auth=True + ) + + +def setup_plugin_routes(app): + """Setup routes for plugins API""" + # Public routes - no auth required + app.router.add_get("/api/v1/plugins", proxy_plugins_list) + app.router.add_get( + "/api/v1/plugins/uploader/{uploader_id}", proxy_plugins_by_uploader + ) + app.router.add_get("/api/v1/plugins/{plugin_id}", proxy_plugin_get) + + # Protected routes - auth required + app.router.add_post("/api/v1/plugins", proxy_plugin_upload) + app.router.add_post("/api/v1/plugins/compute_diff", proxy_plugins_compute_diff) + app.router.add_put("/api/v1/plugins/{plugin_id}", proxy_plugin_update) + app.router.add_delete("/api/v1/plugins/{plugin_id}", proxy_plugin_delete) + + logger.info("Plugin proxy routes configured at /api/v1/plugins") diff --git a/pyUltroid/web/server.py b/pyUltroid/web/server.py new file mode 100644 index 0000000000..3c0373efba --- /dev/null +++ b/pyUltroid/web/server.py @@ -0,0 +1,515 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from aiohttp import web +import json +from typing import Dict, Optional +import logging +import os +from .. import ultroid_bot, udB +from pyUltroid.fns.helper import time_formatter +from telethon.utils import get_display_name +import time +import ssl +from pathlib import Path +from .tg_scraper import scraper +from .middleware import telegram_auth_middleware +import aiohttp_cors +from .routers.admin import setup_admin_routes +from .routers.plugins import setup_plugin_routes +from .routers.miniapp import setup_miniapp_routes +from .cache import owner_cache +from ..configs import Var + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Track server start time +start_time = time.time() + + +class UltroidWebServer: + def __init__(self): + # Check for BOT_TOKEN + bot_token = os.getenv("BOT_TOKEN") + if not bot_token: + logger.error( + "BOT_TOKEN environment variable is not set! Authentication will fail." + ) + else: + logger.info("BOT_TOKEN is properly configured.") + + # Important: telegram_auth_middleware must come before no_cors_middleware + self.app = web.Application(middlewares=[telegram_auth_middleware]) + self.setup_routes() + self.port = Var.PORT + self.bot = ultroid_bot + self.ssl_context = None + + def setup_routes(self): + """Setup basic API routes""" + # Add routes + self.app.router.add_get("/api/user", self.get_ultroid_owner_info) + self.app.router.add_get("/health", self.health_check) + self.app.router.add_post("/api/settings/miniapp", self.save_miniapp_settings) + self.app.router.add_get("/api/settings/miniapp", self.get_miniapp_settings) + + # Setup admin, plugin, and miniapp routes + setup_admin_routes(self.app) + logger.info("Admin routes configured at /api/admin/") + + setup_plugin_routes(self.app) + logger.info("Plugin routes configured at /api/v1/plugins/") + + setup_miniapp_routes(self.app) + logger.info("MiniApp routes configured at /api/miniapp/") + + # Add plugin installation routes + self.app.router.add_post("/api/plugins/install", self.install_plugin) + self.app.router.add_get("/api/plugins/installed", self.get_installed_plugins) + logger.info("Plugin installation routes configured at /api/plugins/") + + cors = aiohttp_cors.setup( + self.app, + defaults={ + "*": aiohttp_cors.ResourceOptions( + allow_credentials=True, + expose_headers="*", + allow_headers="*", + allow_methods="*", + ) }, + ) # Add CORS to all registered routes + for route in list(self.app.router.routes()): + cors.add(route) + + async def health_check(self, request: web.Request) -> web.Response: + """Health check endpoint that doesn't require auth""" + return web.json_response({"status": "ok"}) + + async def save_miniapp_settings(self, request: web.Request) -> web.Response: + """Save mini app and bot configuration settings to udB""" + try: + data = await request.json() + + # Handle both single key-value and multiple settings + settings_to_save = [] + + if "key" in data and "value" in data: + # Single key-value pair + settings_to_save.append({"key": data["key"], "value": data["value"]}) + elif "settings" in data: + # Multiple settings + settings_to_save = data["settings"] + else: + return web.json_response({"error": "Missing key/value or settings parameter"}, status=400) + + if not settings_to_save: + return web.json_response({"error": "No settings provided"}, status=400) + + # Bot configuration settings that should be stored directly in udB + bot_config_keys = [ + "DUAL_MODE", "BOT_MODE", "HNDLR", "DUAL_HNDLR", + "SUDO", "SUDO_HNDLR", "ADDONS", "PLUGIN_CHANNEL", "EMOJI_IN_HELP", + "PMSETTING", "INLINE_PM", "PM_TEXT", "PMPIC", "PMWARNS", "AUTOAPPROVE", + "PMLOG", "PMLOGGROUP", "PMBOT", "STARTMSG", "STARTMEDIA", "BOT_INFO_START", + "ALIVE_TEXT", "ALIVE_PIC", "INLINE_PIC", "TAG_LOG", "FBAN_GROUP_ID", + "EXCLUDE_FED", "RMBG_API", "DEEP_AI", "OCR_API", "GDRIVE_FOLDER_ID", + "VC_SESSION", "PMBOT_FSUB" + ] + + # Get current miniapp settings once + miniapp_settings = udB.get_key("MINIAPP_SETTINGS") or {} + miniapp_settings_updated = False + + # Process all settings + for setting in settings_to_save: + key = setting.get("key") + value = setting.get("value") + + if not key: + continue + + if key in bot_config_keys: + # Handle special data type conversions based on callbackstuffs.py + if key == "PMWARNS": + # PMWARNS should be stored as integer + try: + value = int(value) + except (ValueError, TypeError): + return web.json_response( + {"error": f"PMWARNS must be a valid integer, got: {value}"}, status=400 + ) + elif key in ["DUAL_MODE", "BOT_MODE", "SUDO", "ADDONS", "PMSETTING", + "INLINE_PM", "AUTOAPPROVE", "PMLOG", "PMBOT"]: + # Boolean settings that should be stored as string "True"/"False" + if isinstance(value, bool): + value = "True" if value else "False" + elif value not in ["True", "False", True, False]: + # Convert other truthy/falsy values + value = "True" if value else "False" + elif key == "PMBOT_FSUB" and isinstance(value, (list, tuple)): + # PMBOT_FSUB should be stored as string representation of list + value = str(value) + elif key == "EXCLUDE_FED" and isinstance(value, str): + # EXCLUDE_FED can be space-separated IDs, keep as string + pass + + # Store bot configuration directly in udB + udB.set_key(key, value) + logger.info(f"Saved Bot config setting: {key}={value}") + else: + # Store mini app settings in MINIAPP_SETTINGS + miniapp_settings[key] = value + miniapp_settings_updated = True + logger.info(f"Saved Mini App setting: {key}={value}") + + # Save miniapp settings once if any were updated + if miniapp_settings_updated: + udB.set_key("MINIAPP_SETTINGS", miniapp_settings) + + saved_count = len(settings_to_save) + return web.json_response( + {"success": True, "message": f"{saved_count} setting(s) saved successfully"} + ) + except Exception as e: + logger.error(f"Error saving settings: {str(e)}", exc_info=True) + return web.json_response( + {"error": f"Failed to save settings: {str(e)}"}, status=500 + ) + + async def get_miniapp_settings(self, request: web.Request) -> web.Response: + """Get mini app and bot configuration settings from udB""" + try: + # Get mini app settings + miniapp_settings = udB.get_key("MINIAPP_SETTINGS") or {} + + # Get bot configuration settings + bot_config_keys = [ + "DUAL_MODE", "BOT_MODE", "HNDLR", "DUAL_HNDLR", + "SUDO", "SUDO_HNDLR", "ADDONS", "PLUGIN_CHANNEL", "EMOJI_IN_HELP", + "PMSETTING", "INLINE_PM", "PM_TEXT", "PMPIC", "PMWARNS", "AUTOAPPROVE", + "PMLOG", "PMLOGGROUP", "PMBOT", "STARTMSG", "STARTMEDIA", "BOT_INFO_START", + "ALIVE_TEXT", "ALIVE_PIC", "INLINE_PIC", "TAG_LOG", "FBAN_GROUP_ID", + "EXCLUDE_FED", "RMBG_API", "DEEP_AI", "OCR_API", "GDRIVE_FOLDER_ID", + "VC_SESSION", "PMBOT_FSUB" + ] + bot_settings = {} + + for key in bot_config_keys: + value = udB.get_key(key) + if value is not None: + bot_settings[key] = value + + # Merge both settings (only include keys that have values) + all_settings = {**miniapp_settings, **bot_settings} + + return web.json_response(all_settings) + except Exception as e: + logger.error(f"Error getting settings: {str(e)}", exc_info=True) + return web.json_response( + {"error": f"Failed to get settings: {str(e)}"}, status=500 + ) + + async def install_plugin(self, request: web.Request) -> web.Response: + """Install a plugin by ID and store it in udB INSTALLED_PLUGINS""" + try: + # Check if user is authenticated + if not request.get('user'): + return web.json_response( + {"error": "Authentication required"}, status=401 + ) + + data = await request.json() + plugin_id = data.get("plugin_id") + + if not plugin_id: + return web.json_response( + {"error": "Missing plugin_id parameter"}, status=400 + ) + + # Import necessary modules for plugin installation + import aiohttp + import base64 + from pathlib import Path + from ..configs import CENTRAL_REPO_URL + from ..state_config import temp_config_store + + # Get authentication data + user_data = temp_config_store.get("X-TG-USER") + if not user_data: + return web.json_response( + {"error": "No authentication data found"}, status=401 + ) + + user_id = str(user_data["id"]) + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{user_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{user_id}") + + if not encoded_init_data or not encoded_hash: + return web.json_response( + {"error": "Missing authentication tokens"}, status=401 + ) + + # Decode authentication data + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + async with aiohttp.ClientSession() as session: + # First, get plugin details + api_url = f"{CENTRAL_REPO_URL}/api/v1/plugins/{plugin_id}" + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + async with session.get(api_url, headers=headers) as response: + if response.status != 200: + error_text = await response.text() + return web.json_response( + {"error": f"Failed to fetch plugin details: {error_text}"}, + status=response.status + ) + + plugin_data = await response.json() + + # Download the plugin file + download_url = plugin_data.get("download_link") + if not download_url: + return web.json_response( + {"error": "Plugin download link not available"}, status=400 + ) + + async with session.get(download_url) as download_response: + if download_response.status != 200: + return web.json_response( + {"error": "Failed to download plugin file"}, + status=download_response.status + ) + + plugin_content = await download_response.text() + + # Create addons directory if it doesn't exist + addons_dir = Path("addons") + addons_dir.mkdir(exist_ok=True) + + # Generate safe filename + plugin_title = plugin_data.get("title", "plugin") + file_path = plugin_data.get("file_path", "") + if file_path: + safe_filename = Path(file_path).name + else: + safe_filename = f"{plugin_title.lower().replace(' ', '_')}_{plugin_id}.py" + + if not safe_filename.endswith('.py'): + safe_filename += '.py' + + target_path = addons_dir / safe_filename + + # Write the plugin file + target_path.write_text(plugin_content, encoding='utf-8') + + # Update installed plugins list in udB + installed_plugins = udB.get_key("INSTALLED_PLUGINS") or [] + if str(plugin_id) not in installed_plugins: + installed_plugins.append(str(plugin_id)) + udB.set_key("INSTALLED_PLUGINS", installed_plugins) + + # Load the plugin dynamically + try: + from ..loader import Loader + from ..startup.utils import load_addons + from ..startup.loader import _after_load + + # Load the specific plugin + loader = Loader(path="addons", key="Addons") + # Load only the newly installed plugin + loader.load_single_plugin( + target_path, + func=load_addons, + after_load=_after_load + ) + + logger.info(f"Successfully loaded plugin: {plugin_title}") + except Exception as load_error: + logger.error(f"Error loading plugin {plugin_title}: {str(load_error)}") + + return web.json_response({ + "success": True, + "message": f"Plugin '{plugin_title}' installed successfully", + "plugin_id": plugin_id, + "filename": safe_filename + }) + + except Exception as e: + logger.error(f"Error installing plugin: {str(e)}", exc_info=True) + return web.json_response( + {"error": f"Failed to install plugin: {str(e)}"}, status=500 + ) + + async def get_installed_plugins(self, request: web.Request) -> web.Response: + """Get list of installed plugin IDs including official plugins from INCLUDE_ALL""" + try: + # Get manually installed plugins + installed_plugins = udB.get_key("INSTALLED_PLUGINS") or [] + + # Get official plugins installed via INCLUDE_ALL + from ..state_config import temp_config_store + official_plugins_state = temp_config_store.get("OFFICIAL_PLUGINS_STATE") + + if official_plugins_state: + try: + import json + official_plugins = json.loads(official_plugins_state) + # Add official plugin IDs to installed list + for plugin_id in official_plugins.keys(): + if plugin_id not in installed_plugins: + installed_plugins.append(plugin_id) + except Exception as e: + logger.error(f"Error parsing official plugins state: {str(e)}") + + return web.json_response({ + "installed_plugins": installed_plugins + }) + except Exception as e: + logger.error(f"Error getting installed plugins: {str(e)}", exc_info=True) + return web.json_response( + {"error": f"Failed to get installed plugins: {str(e)}"}, status=500 + ) + + async def get_ultroid_owner_info(self, request: web.Request) -> web.Response: + cache_key = f"owner_info_{self.bot.me.id}" + cached_data = owner_cache.get(cache_key) + + if cached_data: + logger.debug("Returning cached owner info") + return web.json_response(cached_data) + + try: + stats = { + "uptime": time_formatter(time.time() - start_time), + } + + public_data = { + "name": get_display_name(self.bot.me), + "bio": "", + "avatar": "", + "username": self.bot.me.username, + "telegram_url": ( + f"https://t.me/{self.bot.me.username}" + if self.bot.me.username + else None + ), + "stats": stats, + "skills": ["Telegram Bot Management", "Automation", "Python"], + "user_id": self.bot.me.id, + "authenticated_user": request.get("user", {}), + "start_param": request.get("start_param"), + "auth_date": request.get("auth_date"), + } + + if self.bot.me.username: + try: + profile_info = await scraper.get_profile_info(self.bot.me.username) + if profile_info: + if "bio" in profile_info and profile_info["bio"]: + public_data["bio"] = profile_info["bio"] + if "avatar" in profile_info and profile_info["avatar"]: + public_data["avatar"] = profile_info["avatar"] + except Exception as e: + logger.error(f"Error fetching profile info: {e}") + + owner_cache.set(cache_key, public_data) + return web.json_response(public_data) + + except Exception as e: + logger.error(f"Error in get_ultroid_owner_info: {e}", exc_info=True) + return web.json_response( + {"error": "Failed to fetch owner info"}, status=500 + ) + + async def _setup_web_app_build(self): + """Setup web app build directory if it exists""" + from pyUltroid.scripts.webapp import fetch_recent_release + + # Download and extract the latest webapp release + success = await fetch_recent_release() + + # First try using the downloaded webapp from resources + webapp_path = Path("resources/webapp") + if success and webapp_path.exists(): + logger.info(f"Setting up webapp at {webapp_path.absolute()}") + if Var.MINIAPP_URL: + with open(webapp_path / "config.json", "w") as f: + config_data = { + "apiUrl": Var.MINIAPP_URL, + } + json.dump(config_data, f, indent=4) + + # Add specific handler for root path to serve index.html + index_file = webapp_path / "index.html" + if index_file.exists(): + + async def root_handler(request): + logger.debug("Serving index.html for root path /") + return web.FileResponse(index_file) + + # Add root handler first to ensure it has priority + self.app.router.add_get("/", root_handler) + logger.info(f"Added specific route for / to serve {index_file}") + + try: + self.app.router.add_static("/", path=webapp_path) + logger.info(f"Serving static files from {webapp_path}") + except Exception as e: + logger.error(f"Failed to add static route: {e}", exc_info=True) + return + + async def handle_index(request): + index_file = webapp_path / "index.html" + if index_file.exists(): + logger.debug(f"Serving index.html for route: {request.path}") + return web.FileResponse(index_file) + else: + logger.warning(f"index.html not found at {index_file}") + return web.Response( + text="Web application is being setup...", + content_type="text/html", + ) + + # Add fallback route for SPA navigation + self.app.router.add_get("/{tail:.*}", handle_index) + logger.info("Added SPA fallback handler") + else: + logger.info("Web app build is disabled!") + + async def cleanup(self): + """Clean up resources when server shuts down""" + await scraper.close() + owner_cache.clear() + + async def start(self, host: str = "0.0.0.0", port: Optional[int] = None): + """Asynchronously starts the web server.""" + if Var.RENDER_WEB: + logger.info("Setting up web app build...") + await self._setup_web_app_build() + + self.app.on_shutdown.append(self.cleanup) + + runner = web.AppRunner(self.app) + await runner.setup() + + _port = port or self.port + site = web.TCPSite(runner, host, _port, ssl_context=self.ssl_context) + await site.start() + + logger.info( + f"Starting {'HTTPS' if self.ssl_context else 'HTTP'} server on {host}:{_port}" + ) + + +ultroid_server = UltroidWebServer() diff --git a/pyUltroid/web/tg_scraper.py b/pyUltroid/web/tg_scraper.py new file mode 100644 index 0000000000..fea234e828 --- /dev/null +++ b/pyUltroid/web/tg_scraper.py @@ -0,0 +1,115 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2025 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import aiohttp +import logging +import re +from bs4 import BeautifulSoup +from typing import Dict, Optional, Tuple + +logger = logging.getLogger(__name__) + + +class TelegramProfileScraper: + """Utility to scrape public information from Telegram web profiles""" + + def __init__(self): + self.session = None + self.cache = {} # Simple cache to avoid repeated requests + + async def _get_session(self): + if self.session is None or self.session.closed: + self.session = aiohttp.ClientSession( + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + } + ) + return self.session + + async def close(self): + if self.session and not self.session.closed: + await self.session.close() + + async def get_profile_info(self, username: str) -> Optional[Dict]: + """ + Scrape profile information from a public Telegram profile + + Args: + username: Telegram username without the @ symbol + + Returns: + Dictionary with profile information or None if not found + """ + # Check cache first + if username in self.cache: + return self.cache[username] + + try: + # Clean the username + username = username.strip().lower() + if username.startswith("@"): + username = username[1:] + + url = f"https://t.me/{username}" + session = await self._get_session() + + async with session.get(url) as response: + if response.status != 200: + logger.warning( + f"Failed to fetch profile for {username}: HTTP {response.status}" + ) + return None + + html = await response.text() + + # Parse the HTML + soup = BeautifulSoup(html, "html.parser") + + # Extract profile information + result = {} + + # Get profile image + img_tag = soup.select_one("img.tgme_page_photo_image") + if img_tag and "src" in img_tag.attrs: + result["avatar"] = img_tag["src"] + + # Get bio + bio_div = soup.select_one("div.tgme_page_description") + if bio_div: + result["bio"] = bio_div.get_text(strip=True) + + # Get name + title_tag = soup.select_one("div.tgme_page_title") + if title_tag: + result["name"] = title_tag.get_text(strip=True) + + # Cache the result + self.cache[username] = result + return result + + except Exception as e: + logger.error(f"Error scraping profile for {username}: {e}") + return None + + async def get_profile_image(self, username: str) -> Optional[str]: + """Get just the profile image URL""" + profile = await self.get_profile_info(username) + return profile.get("avatar") if profile else None + + async def get_profile_bio(self, username: str) -> Optional[str]: + """Get just the profile bio""" + profile = await self.get_profile_info(username) + return profile.get("bio") if profile else None + + +# Singleton instance +scraper = TelegramProfileScraper() + +if __name__ == "__main__": + import asyncio + + asyncio.run(scraper.get_profile_info("karboncopy")) diff --git a/requirements.txt b/requirements.txt index acbc790d4b..46a0a262ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,21 @@ # Important Requirements here. telethon -gitpython https://github.com/New-dev0/Telethon-Patch/archive/main.zip python-decouple python-dotenv telegraph enhancer requests +gitpython +aiohttp +aiohttp-cors +catbox-uploader +cloudscraper +pynacl +telegraph +enhancer +requests aiohttp catbox-uploader -cloudscraper \ No newline at end of file +cloudscraper +redis diff --git a/strings/strings/en.yml b/strings/strings/en.yml index 85dd02758f..ce8c6b0a7e 100644 --- a/strings/strings/en.yml +++ b/strings/strings/en.yml @@ -601,7 +601,7 @@ help_chats: " -\n\n• `{i}delchat `\n Delete the grou help_cleanaction: " -\n\n•`{i}addclean`\n Clean all Upcoming action msg in added chat like someone joined/left/pin etc.\n\n•`{i}remclean`\n Remove chat from database.\n\n•`{i}listclean`\n To get list of all chats where its activated.\n\n" help_converter: " -\n\n• `{i}convert `\n Reply to media to convert it into gif / image / webm / normal sticker.\n\n• `{i}doc `\n Reply to a text msg to save it in a file.\n\n• `{i}open`\n Reply to a file to reveal it's text.\n\n• `{i}rename `\n Rename the file\n\n• `{i}thumbnail `\n Upload Your file with your custom thumbnail.\n" help_core: " -\n\n• `{i}install `\n To install the plugin,\n `{i}install f`\n To force Install.\n\n• `{i}uninstall `\n To unload and remove the plugin.\n\n• `{i}load `\n To load unloaded unofficial plugin.\n\n• `{i}unload `\n To unload unofficial plugin.\n\n• `{i}help `\n Shows you a help menu (like this) for every plugin.\n\n• `{i}getaddons `\n Load Plugins from the given raw link.\n" -help_database: " -\n\n• **DataBase Commands, do not use if you don't know what it is.**\n\n• `{i}setdb key | value`\n Set Value in Database.\n e.g :\n `{i}setdb hi there`\n `{i}setdb hi there | ultroid here`\n `{i}setdb --extend variable value` or `{i}setdb -e variable value` to add the value to the exiting values in db.\n\n• `{i}deldb key`\n Delete Key from DB.\n\n• `{i}rendb old keyname | new keyname`\n Update Key Name\n" +help_database: " -\n\n• **DataBase Commands, do not use if you don't know what it is.**\n\n• `{i}setdb key | value`\n Set Value in Database.\n e.g :\n `{i}setdb hi there`\n `{i}setdb hi there | ultroid here`\n `{i}setdb --extend variable value` or `{i}setdb -e variable value` to add the value to the exiting values in db.\n\n• `{i}deldb key`\n Delete Key from DB.\n\n• `{i}rendb old keyname | new keyname`\n Update Key Name\n\n• `{i}get var `\n Get value of the given variable name.\n\n• `{i}get type `\n Get variable type.\n\n• `{i}get db `\n Get db value of the given key.\n\n• `{i}get keys`\n Get all redis keys.\n" help_devtools: " -\n\n• `{i}bash `\n• `{i}bash -c ` Carbon image as command output.\n Run linux commands on telegram.\n\n• `{i}eval `\n Evaluate python commands on telegram.\n Shortcuts:\n client = bot = event.client\n e = event\n p = print\n reply = await event.get_reply_message()\n chat = event.chat_id\n\n• `{i}cpp `\n Run c++ code from Telegram.\n\n• `{i}sysinfo`\n Shows System Info.\n" help_downloadupload: " -\n\n• `{i}ul `\n Upload files on telegram.\n Use following arguments before or after filename as per requirement:\n `--stream` to upload as stream.\n `--delete` to delete file after uploading.\n `--no-thumb` to upload without thumbnail.\n\n• `{i}dl `\n Reply to file to download.\n\n• `{i}download (| filename)`\n Download using DDL. Will autogenerate filename if not given.\n" help_echo: "\n\n•`{i}addecho `\n Start Auto Echo message of Replied user.\n\n•`{i}remecho `\n Turn It off\n\n•`{i}listecho `\n To Get list.\n" @@ -640,7 +640,6 @@ help_tools: " -\n\n• `{i}circle`\n Reply to a audio song or gif to get vide help_unsplash: " -\n\n• {i}unsplash ; \n Unsplash Image Search.\n" help_usage: "\n\n• `{i}usage`\n Get overall usage.\n\n• `{i}usage heroku`\n Get heroku stats.\n\n• `{i}usage db`\n Get database storage usage.\n" help_utilities: " -\n\n• `{i}kickme` : Leaves the group.\n\n• `{i}date` : Show Calender.\n\n• `{i}listreserved`\n List all usernames (channels/groups) you own.\n\n• `{i}stats` : See your profile stats.\n\n• `{i}paste` - `Include long text / Reply to text file.`\n\n• `{i}info `\n Reply to someone's msg.\n\n• `{i}invite `\n Add user to the chat.\n\n• `{i}rmbg `\n Remove background from that picture.\n\n• `{i}telegraph `\n Upload media/text to telegraph.\n\n• `{i}json `\n Get the json encoding of the message.\n\n• `{i}suggest or `\n Create a Yes/No poll for the replied suggestion.\n\n• `{i}ipinfo ` : Get info about that IP address.\n\n• `{i}cpy `\n Copy the replied message, with formatting. Expires in 24hrs.\n• `{i}pst`\n Paste the copied message, with formatting.\n\n• `{i}thumb ` : Download the thumbnail of the replied file.\n\n• `{i}getmsg `\n Get messages from chats with forward/copy restrictions.\n" -help_variables: " -\n\n• `{i}get var `\n Get value of the given variable name.\n\n• `{i}get type `\n Get variable type.\n\n• `{i}get db `\n Get db value of the given key.\n\n• `{i}get keys`\n Get all redis keys.\n" help_vctools: " -\n\n• `{i}startvc`\n Start Group Call in a group.\n\n• `{i}stopvc`\n Stop Group Call in a group.\n\n• `{i}vctitle `\n Change the title Group call.\n\n• `{i}vcinvite`\n Invite all members of group in Group Call.\n (You must be joined)\n" help_videotools: " -\n\n•`{i}sample <duration in seconds>`\n Creates Short sample of video..\n\n• `{i}vshots <number of shots>`\n Creates screenshot of video..\n\n• `{i}vtrim <start time> - <end time> in seconds`\n Crop a Lengthy video..\n" help_warn: "\n\n•`{i}warn <reply to user> <reason>`\n Gives Warn.\n\n•`{i}resetwarn <reply to user>`\n To reset All Warns.\n\n•`{i}warns <reply to user>`\n To Get List of Warnings of a user.\n\n•`{i}setwarn <warn count> | <ban/mute/kick>`\n Set Number in warn count for warnings\n After putting ' | ' mark put action like ban/mute/kick\n Its Default 3 kick\n Example : `setwarn 5 | mute`\n\n" diff --git a/upload.py b/upload.py new file mode 100644 index 0000000000..ea9a48b491 --- /dev/null +++ b/upload.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +# Script to upload all plugins from a folder to the Ultroid Plugin Store. + +import asyncio +import aiohttp +import json +import base64 +import os + +from dotenv import load_dotenv +load_dotenv() + +import logging +from pathlib import Path +from typing import Optional + +# AI-related imports +try: + from openai import OpenAI +except ImportError: + print("Error: 'openai' is not installed. Please install it with 'pip install openai'") + exit(1) + + +# Import from Ultroid modules +from pyUltroid.state_config import temp_config_store + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def get_plugin_metadata_with_ai(file_content: str, model_client: OpenAI) -> Optional[dict]: + """ + Analyzes plugin code using an AI model to extract metadata. + + Args: + file_content: The source code of the plugin. + model_client: The AI client to use for analysis. + + Returns: + A dictionary with extracted metadata or None on failure. + """ + logger.info("Analyzing plugin with AI to extract metadata...") + system_prompt = """ +You are an expert Python developer specializing in Ultroid plugins. +Your task is to analyze a given plugin file and extract specific metadata. +Respond with ONLY a valid JSON object containing the following keys: +- "description": A short, clear description of what the plugin does, derived from its docstring or overall purpose. +- "commands": A list of strings, where each string is a command pattern (e.g., "ping", "start"). Find these in `@ultroid_cmd(pattern="...")` decorators. +- "packages": A list of strings, where each string is an external pip package required by the plugin. Analyze the import statements to determine these. Common built-in modules should be ignored. +""" + + user_prompt = f""" +Analyze this Ultroid plugin code and return the metadata as a JSON object. + +```python +{file_content} +``` +""" + + try: + response = model_client.chat.completions.create( + messages=[ + { + "role": "system", + "content": system_prompt + }, + { + "role": "user", + "content": user_prompt + } + ], + model="meta-llama/llama-4-scout-17b-16e-instruct", + response_format={"type": "json_object"}, + ) + + content = response.choices[0].message.content + if content.startswith("```json"): + content = content[7:] + if content.endswith("```"): + content = content[:-3] + print(content) + metadata = json.loads(content) + logger.info("Successfully extracted metadata from AI.") + return metadata + + except (json.JSONDecodeError, IndexError) as e: + logger.error(f"Failed to get or parse AI response: {e}") + return None + except Exception as e: + logger.error(f"An unexpected error occurred during AI analysis: {e}") + return None + +async def upload_addon_plugins(): + """ + Scans the UltroidAddons folder, extracts metadata for each plugin, + and uploads them to the plugin store. + """ + api_url = "http://localhost:8055" + bot_id = 1444249738 # Placeholder, adjust if needed + + # Get stored authentication data + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{bot_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{bot_id}") + + if not encoded_init_data or not encoded_hash: + logger.error("Authentication data not found. Please authenticate with Ultroid Central first.") + return + + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + # Initialize AI Client + token = os.environ.get("GROQ_API_KEY") + if not token: + logger.error("GROQ_API_KEY not found in environment variables.") + return + + client = OpenAI( + base_url="https://api.groq.com/openai/v1", + api_key=token, + ) + + # Path to addons, assuming it's a sibling to the 'Ultroid' directory + addon_path = Path("addons_copy") + if not addon_path.exists() or not addon_path.is_dir(): + logger.error(f"Could not find the UltroidAddons directory at: {addon_path}") + return + + logger.info(f"Scanning for plugins in: {addon_path}") + + async with aiohttp.ClientSession() as session: + for plugin_path in sorted(addon_path.glob("*.py")): + if plugin_path.name == "__init__.py": + continue + + logger.info(f"--- Processing plugin: {plugin_path.name} ---") + + try: + content = plugin_path.read_text(encoding='utf-8') + metadata = get_plugin_metadata_with_ai(content, client) + + if not metadata: + logger.warning(f"Could not get metadata for {plugin_path.name}. Skipping.") + continue + + # Prepare JSON data + json_data = { + "title": plugin_path.stem.replace('_', ' ').title(), + "description": metadata.get("description", "N/A"), + "tags": ["community", plugin_path.stem], + "packages": metadata.get("packages", []), + "commands": metadata.get("commands", []), + "is_trusted": True, + "is_official": True, + "plugin_filename": plugin_path.name, + "plugin_content": base64.b64encode(content.encode('utf-8')).decode('utf-8') + } + + # Prepare headers with authentication + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + # Upload the plugin + logger.info(f"Uploading '{json_data['title']}'...") + plugin_url = f"{api_url}/api/v1/plugins" + + async with session.post( + plugin_url, + json=json_data, + headers=headers + ) as response: + status = response.status + resp_text = await response.text() + + if status in (200, 201): + logger.info(f"Successfully uploaded '{json_data['title']}'. Status: {status}") + else: + logger.error(f"Failed to upload '{json_data['title']}'. Status: {status}, Response: {resp_text[:200]}") + + except Exception as e: + logger.error(f"An unexpected error occurred while processing {plugin_path.name}: {e}") + + # Optional: Add a small delay to avoid overwhelming the server or AI service + await asyncio.sleep(1) + +async def delete_all_plugins(): + """Delete all plugins uploaded by the current user.""" + api_url = "http://localhost:8055" + bot_id = 1444249738 # Placeholder, adjust if needed + + # Get stored authentication data + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{bot_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{bot_id}") + + if not encoded_init_data or not encoded_hash: + logger.error("Authentication data not found. Please authenticate with Ultroid Central first.") + return + + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + # Prepare headers with authentication + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + async with aiohttp.ClientSession() as session: + plugin_url = f"{api_url}/api/v1/plugins/user/plugins" + + async with session.delete( + plugin_url, + headers=headers + ) as response: + status = response.status + resp_text = await response.text() + + if status in (200, 201): + data = json.loads(resp_text) + logger.info(f"Successfully deleted {data.get('deleted_count', 0)} plugins") + else: + logger.error(f"Failed to delete plugins. Status: {status}, Response: {resp_text[:200]}") + +async def upload_single_plugin(plugin_path_str): + """ + Upload a single plugin file to the plugin store. + """ + api_url = "http://localhost:8055" + bot_id = 1444249738 # Placeholder, adjust if needed + + # Get stored authentication data + encoded_init_data = temp_config_store.get(f"X-TG-INIT-DATA-{bot_id}") + encoded_hash = temp_config_store.get(f"X-TG-HASH-{bot_id}") + + if not encoded_init_data or not encoded_hash: + logger.error("Authentication data not found. Please authenticate with Ultroid Central first.") + return + + init_data = base64.b64decode(encoded_init_data.encode()).decode() + hash_value = base64.b64decode(encoded_hash.encode()).decode() + + # Initialize AI Client + token = os.environ.get("GROQ_API_KEY") + if not token: + logger.error("GROQ_API_KEY not found in environment variables.") + return + + client = OpenAI( + base_url="https://api.groq.com/openai/v1", + api_key=token, + ) + + plugin_path = Path(plugin_path_str) + if not plugin_path.exists() or not plugin_path.is_file(): + logger.error(f"Plugin file not found: {plugin_path}") + return + + logger.info(f"Processing single plugin: {plugin_path.name}") + + async with aiohttp.ClientSession() as session: + try: + content = plugin_path.read_text(encoding='utf-8') + metadata = get_plugin_metadata_with_ai(content, client) + + if not metadata: + logger.warning(f"Could not get metadata for {plugin_path.name}.") + return + + # Prepare JSON data + json_data = { + "title": plugin_path.stem.replace('_', ' ').title(), + "description": metadata.get("description", "N/A"), + "tags": ["community", plugin_path.stem], + "packages": metadata.get("packages", []), + "commands": metadata.get("commands", []), + "is_trusted": True, + "is_official": True, + "plugin_filename": plugin_path.name, + "plugin_content": base64.b64encode(content.encode('utf-8')).decode('utf-8') + } + + # Prepare headers with authentication + headers = { + "Content-Type": "application/json", + "X-Telegram-Init-Data": init_data, + "X-Telegram-Hash": hash_value + } + + # Upload the plugin + logger.info(f"Uploading '{json_data['title']}'...") + plugin_url = f"{api_url}/api/v1/plugins" + + async with session.post( + plugin_url, + json=json_data, + headers=headers + ) as response: + status = response.status + resp_text = await response.text() + + if status in (200, 201): + logger.info(f"Successfully uploaded '{json_data['title']}'. Status: {status}") + else: + logger.error(f"Failed to upload '{json_data['title']}'. Status: {status}, Response: {resp_text[:200]}") + + except Exception as e: + logger.error(f"An unexpected error occurred while processing {plugin_path.name}: {e}") + +if __name__ == "__main__": + import sys + import argparse + + if "GROQ_API_KEY" not in os.environ: + print("Error: GROQ_API_KEY environment variable is not set.") + print("Please set it to a valid Groq API key.") + sys.exit(1) + + parser = argparse.ArgumentParser(description='Ultroid Plugin Store Management') + parser.add_argument('action', choices=['upload', 'delete', 'upload_one'], + help='Action to perform: upload all, upload one, or delete all plugins') + parser.add_argument('--path', type=str, help='Path to the plugin file (for upload_one)') + + args = parser.parse_args() + + if args.action == 'upload': + asyncio.run(upload_addon_plugins()) + elif args.action == 'delete': + asyncio.run(delete_all_plugins()) + elif args.action == 'upload_one': + if not args.path: + print("Please provide --path to the plugin file for upload_one.") + sys.exit(1) + asyncio.run(upload_single_plugin(args.path)) \ No newline at end of file