diff --git a/assistant/start.py b/assistant/start.py index 4024b488f3..4464bdcb24 100644 --- a/assistant/start.py +++ b/assistant/start.py @@ -7,7 +7,10 @@ from datetime import datetime -from pytz import timezone as tz +try: + from pytz import timezone as tz +except ImportError: + tz = None from telethon import Button, events from telethon.errors.rpcerrorlist import MessageDeleteForbiddenError from telethon.utils import get_display_name @@ -211,6 +214,11 @@ async def setting(event): @callback("tz", owner=True) async def timezone_(event): await event.delete() + if tz is None: + return await event.client.send_message( + event.sender_id, + f"`pytz` is not installed, TimeZone feature is unavailable.\n\nInstall it via `{HNDLR}update` or `{HNDLR}bash pip install pytz`.", + ) pru = event.sender_id var = "TIMEZONE" name = "Timezone" diff --git a/plugins/admintools.py b/plugins/admintools.py index 2e8736bb05..56821d1e51 100644 --- a/plugins/admintools.py +++ b/plugins/admintools.py @@ -469,3 +469,37 @@ async def autodelte(ult): f"Auto Delete Setting is Already same to `{match}`", time=5 ) await ult.eor(f"Auto Delete Status Changed to `{match}` !") + + +@ultroid_cmd( + pattern=r"kickall( (.*)|$)", + admins_only=True, + fullsudo=True, + require="ban_users", +) +async def kickall_cmd(ult): + """Kick all non-admin members from the group.""" + if ult.is_private: + return await ult.eor("`Use this in a Group.`", time=5) + match = ult.pattern_match.group(1).strip() + confirm = match.lower() == "confirm" + if not confirm: + return await ult.eor( + f"`Are you sure? This will kick ALL non-admin members!\nUse` `{HNDLR}kickall confirm` `to proceed.`", + time=10, + ) + xx = await ult.eor("`Starting KickAll...`") + kicked = 0 + failed = 0 + async for member in ult.client.iter_participants(ult.chat_id): + if member.bot or getattr(member.participant, "admin_rights", None) or getattr(member.participant, "creator", False): + continue + try: + await ult.client.kick_participant(ult.chat_id, member.id) + kicked += 1 + await asyncio.sleep(0.5) + except Exception: + failed += 1 + await xx.edit( + f"**KickAll Done!**\n**Kicked:** `{kicked}`\n**Failed:** `{failed}`" + ) diff --git a/plugins/broadcast.py b/plugins/broadcast.py index 5350b2aa26..9734c8ef63 100644 --- a/plugins/broadcast.py +++ b/plugins/broadcast.py @@ -20,6 +20,45 @@ from . import HNDLR, LOGS, eor, get_string, udB, ultroid_bot, ultroid_cmd KeyM = KeyManager("BROADCAST", cast=list) +BlackM = KeyManager("BROADCAST_BLACKLIST", cast=list) + + +@ultroid_cmd( + pattern=r"addblacklist( (.*)|$)", + allow_sudo=False, +) +async def broadcast_blacklist_add(event): + msgg = event.pattern_match.group(1).strip() + chat_id = int(msgg) if msgg and msgg.lstrip("-").isdigit() else event.chat_id + if BlackM.contains(chat_id): + return await event.eor("`Already in broadcast blacklist.`", time=5) + BlackM.add(chat_id) + await event.eor(f"`Added {chat_id} to broadcast blacklist.`", time=5) + + +@ultroid_cmd( + pattern=r"remblacklist( (.*)|$)", + allow_sudo=False, +) +async def broadcast_blacklist_rem(event): + msgg = event.pattern_match.group(1).strip() + chat_id = int(msgg) if msgg and msgg.lstrip("-").isdigit() else event.chat_id + if not BlackM.contains(chat_id): + return await event.eor("`Not in broadcast blacklist.`", time=5) + BlackM.remove(chat_id) + await event.eor(f"`Removed {chat_id} from broadcast blacklist.`", time=5) + + +@ultroid_cmd( + pattern="listblacklist$", + allow_sudo=False, +) +async def broadcast_blacklist_list(event): + bl = BlackM.get() + if not bl: + return await event.eor("`Broadcast blacklist is empty.`", time=5) + msg = "**Broadcast Blacklist:**\n" + "\n".join(f"• `{c}`" for c in bl) + await event.eor(msg) @ultroid_cmd( @@ -147,7 +186,10 @@ async def forw(event): sent_count = 0 previous_message = await event.get_reply_message() error_count = 0 + blacklist = BlackM.get() or [] for channel in channels: + if channel in blacklist: + continue try: await ultroid_bot.forward_messages(channel, previous_message) sent_count += 1 @@ -192,7 +234,10 @@ async def sending(event): if previous_message: error_count = 0 sent_count = 0 + blacklist = BlackM.get() or [] for channel in channels: + if channel in blacklist: + continue try: await ultroid_bot.send_message(channel, previous_message) sent_count += 1 diff --git a/plugins/delayspam.py b/plugins/delayspam.py new file mode 100644 index 0000000000..937a916696 --- /dev/null +++ b/plugins/delayspam.py @@ -0,0 +1,95 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}delayspam ` + Spam a message `count` times with `delay` seconds between each. + Reply to a message to spam that message instead. + +• `{i}stopspam` + Stop an ongoing delayspam. + +Examples: + `{i}delayspam 5 2 Hello!` — sends "Hello!" 5 times with 2s delay + `{i}delayspam 10 1` (reply to a message) — forwards replied msg 10 times with 1s delay +""" + +import asyncio + +from . import HNDLR, eod, get_string, udB, ultroid_bot, ultroid_cmd + +_spam_tasks = {} + + +@ultroid_cmd(pattern=r"delayspam( (.*)|$)") +async def delayspam_cmd(ult): + args = ult.pattern_match.group(1).strip().split(None, 2) + reply = await ult.get_reply_message() + + if len(args) < 2: + return await ult.eor( + f"`Usage: {HNDLR}delayspam [message]\n" + f"Or reply to a message: {HNDLR}delayspam `", + time=10, + ) + + try: + count = int(args[0]) + delay = float(args[1]) + except ValueError: + return await ult.eor("`count and delay must be numbers.`", time=5) + + if count > 200: + return await ult.eor("`Max 200 repetitions allowed.`", time=5) + if delay < 0.5: + return await ult.eor("`Minimum delay is 0.5 seconds.`", time=5) + + text = args[2] if len(args) > 2 else None + chat_id = ult.chat_id + + if not text and not reply: + return await ult.eor( + f"`Provide a message or reply to one.\nUsage: {HNDLR}delayspam `", + time=8, + ) + + await ult.delete() + + task_key = f"{ult.sender_id}_{chat_id}" + if task_key in _spam_tasks: + _spam_tasks[task_key].cancel() + + async def _do_spam(): + for i in range(count): + if task_key not in _spam_tasks: + break + try: + if reply: + await ultroid_bot.send_message( + chat_id, reply.text or "", file=reply.media + ) + else: + await ultroid_bot.send_message(chat_id, text) + except Exception: + break + await asyncio.sleep(delay) + _spam_tasks.pop(task_key, None) + + task = asyncio.get_event_loop().create_task(_do_spam()) + _spam_tasks[task_key] = task + + +@ultroid_cmd(pattern="stopspam$") +async def stopspam_cmd(ult): + task_key = f"{ult.sender_id}_{ult.chat_id}" + if task_key in _spam_tasks: + _spam_tasks.pop(task_key).cancel() + await ult.eor("`Spam stopped.`", time=3) + else: + await ult.eor("`No active spam in this chat.`", time=3) diff --git a/plugins/pdftools.py b/plugins/pdftools.py index 6131cc03ec..518b4742c5 100644 --- a/plugins/pdftools.py +++ b/plugins/pdftools.py @@ -33,14 +33,6 @@ import cv2 import numpy as np -try: - from PIL import Image -except ImportError: - Image = None - LOGS.info(f"{__file__}: PIL not Installed.") -from PyPDF2 import PdfFileMerger, PdfFileReader, PdfFileWriter -from telethon.errors.rpcerrorlist import PhotoSaveFileInvalidError - from pyUltroid.fns.tools import four_point_transform from . import ( @@ -54,6 +46,20 @@ ultroid_cmd, ) +try: + from PIL import Image +except ImportError: + Image = None + LOGS.info(f"{__file__}: PIL not Installed.") +try: + from PyPDF2 import PdfReader, PdfWriter, PdfMerger + PdfFileReader = PdfReader + PdfFileWriter = PdfWriter + PdfFileMerger = PdfMerger +except ImportError: + from PyPDF2 import PdfFileMerger, PdfFileReader, PdfFileWriter +from telethon.errors.rpcerrorlist import PhotoSaveFileInvalidError + if not os.path.isdir("pdf"): os.mkdir("pdf") diff --git a/plugins/pmpermit.py b/plugins/pmpermit.py index 31e3af197a..72f3d837fb 100644 --- a/plugins/pmpermit.py +++ b/plugins/pmpermit.py @@ -36,6 +36,9 @@ • `{i}listapproved` List all approved PMs. + +• `{i}pmpermit on/off` + Enable or disable PMPermit. Check current status with no argument. """ import asyncio @@ -100,7 +103,7 @@ _not_approved = {} _to_delete = {} -my_bot = asst.me.username +my_bot = asst.me.username if asst.me else None def update_pm(userid, message, warns_given): @@ -163,74 +166,102 @@ async def permitpm(event): await event.forward_to(udB.get_key("PMLOGGROUP") or LOG_CHANNEL) -if udB.get_key("PMSETTING"): - if udB.get_key("AUTOAPPROVE"): - - @ultroid_bot.on( - events.NewMessage( - outgoing=True, - func=lambda e: e.is_private and e.out and not e.text.startswith(HNDLR), - ), +@ultroid_cmd(pattern="pmpermit( (.*)|$)", fullsudo=True) +async def toggle_pmpermit(e): + arg = e.pattern_match.group(1).strip().lower() + if arg in ("on", "enable", "1"): + udB.set_key("PMSETTING", True) + await e.eor("`PMPermit enabled.`", time=5) + elif arg in ("off", "disable", "0"): + udB.del_key("PMSETTING") + await e.eor("`PMPermit disabled.`", time=5) + else: + status = "enabled" if udB.get_key("PMSETTING") else "disabled" + await e.eor( + f"`PMPermit is currently {status}.\nUse {HNDLR}pmpermit on/off to toggle.`", + time=8, ) - async def autoappr(e): - miss = await e.get_chat() - if miss.bot or miss.is_self or miss.verified or miss.id in DEVLIST: - return - if keym.contains(miss.id): - return - keym.add(miss.id) - await delete_pm_warn_msgs(miss.id) - try: - await ultroid_bot.edit_folder(miss.id, folder=0) - except BaseException: - pass - try: - await asst.edit_message( - LOG_CHANNEL, - _not_approved[miss.id], - f"#AutoApproved : OutGoing Message.\nUser : {inline_mention(miss, html=True)} [{miss.id}]", - parse_mode="html", - ) - except KeyError: - await asst.send_message( - LOG_CHANNEL, - f"#AutoApproved : OutGoing Message.\nUser : {inline_mention(miss, html=True)} [{miss.id}]", - parse_mode="html", - ) - except MessageNotModifiedError: - pass - @ultroid_bot.on( - events.NewMessage( - incoming=True, - func=lambda e: e.is_private - and e.sender_id not in DEVLIST - and not e.out - and not e.sender.bot - and not e.sender.is_self - and not e.sender.verified, + +@ultroid_bot.on( + events.NewMessage( + outgoing=True, + func=lambda e: e.is_private and e.out and bool(e.text) and not e.text.startswith(HNDLR), + ), +) +async def autoappr(e): + if not udB.get_key("PMSETTING") or not udB.get_key("AUTOAPPROVE"): + return + miss = await e.get_chat() + if miss.bot or miss.is_self or miss.verified or miss.id in DEVLIST: + return + if keym.contains(miss.id): + return + keym.add(miss.id) + await delete_pm_warn_msgs(miss.id) + try: + await ultroid_bot.edit_folder(miss.id, folder=0) + except BaseException: + pass + _log_ch = udB.get_key("LOG_CHANNEL") + if not _log_ch: + return + try: + await asst.edit_message( + _log_ch, + _not_approved[miss.id], + f"#AutoApproved : OutGoing Message.\nUser : {inline_mention(miss, html=True)} [{miss.id}]", + parse_mode="html", ) + except KeyError: + await asst.send_message( + _log_ch, + f"#AutoApproved : OutGoing Message.\nUser : {inline_mention(miss, html=True)} [{miss.id}]", + parse_mode="html", + ) + except MessageNotModifiedError: + pass + + +@ultroid_bot.on( + events.NewMessage( + incoming=True, + func=lambda e: e.is_private + and e.sender_id not in DEVLIST + and not e.out + and e.sender is not None + and not e.sender.bot + and not e.sender.is_self + and not e.sender.verified, ) - async def permitpm(event): - inline_pm = Redis("INLINE_PM") or False - user = event.sender - if not keym.contains(user.id) and event.text != UND: - if Redis("MOVE_ARCHIVE"): - try: - await ultroid_bot.edit_folder(user.id, folder=1) - except BaseException as er: - LOGS.info(er) - if event.media and not udB.get_key("DISABLE_PMDEL"): - await event.delete() - name = user.first_name - fullname = get_display_name(user) - username = f"@{user.username}" - mention = inline_mention(user) - count = keym.count() +) +async def permitpm(event): + if not udB.get_key("PMSETTING"): + return + inline_pm = (Redis("INLINE_PM") and my_bot) or False + user = event.sender + if user is None: + return + cur_text = event.text or "" + if not keym.contains(user.id) and cur_text != UND: + if Redis("MOVE_ARCHIVE"): try: - wrn = COUNT_PM[user.id] + 1 + await ultroid_bot.edit_folder(user.id, folder=1) + except BaseException as er: + LOGS.info(er) + if event.media and not udB.get_key("DISABLE_PMDEL"): + await event.delete() + name = user.first_name + fullname = get_display_name(user) + username = f"@{user.username}" if user.username else str(user.id) + mention = inline_mention(user) + count = keym.count() + _log_ch = udB.get_key("LOG_CHANNEL") + try: + wrn = COUNT_PM[user.id] + 1 + if _log_ch: await asst.edit_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, _not_approved[user.id], f"Incoming PM from **{mention}** [`{user.id}`] with **{wrn}/{WARNS}** warning!", buttons=[ @@ -238,195 +269,126 @@ async def permitpm(event): Button.inline("Block PM", data=f"block_{user.id}"), ], ) - except KeyError: + except KeyError: + if _log_ch: _not_approved[user.id] = await asst.send_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, f"Incoming PM from **{mention}** [`{user.id}`] with **1/{WARNS}** warning!", buttons=[ Button.inline("Approve PM", data=f"approve_{user.id}"), Button.inline("Block PM", data=f"block_{user.id}"), ], ) - wrn = 1 - except MessageNotModifiedError: - wrn = 1 - if user.id in LASTMSG: - prevmsg = LASTMSG[user.id] - if event.text != prevmsg: - if "PMSecurity" in event.text or "**PMSecurity" in event.text: - return - await delete_pm_warn_msgs(user.id) - message_ = UNAPPROVED_MSG.format( - ON=OWNER_NAME, - warn=wrn, - twarn=WARNS, - UND=UND, - name=name, - fullname=fullname, - username=username, - count=count, - mention=mention, - ) - update_pm(user.id, message_, wrn) - if inline_pm: - results = await ultroid_bot.inline_query( - my_bot, f"ip_{user.id}" - ) - try: - _to_delete[user.id] = await results[0].click( - user.id, reply_to=event.id, hide_via=True - ) - except Exception as e: - LOGS.info(str(e)) - elif PMPIC: - _to_delete[user.id] = await ultroid_bot.send_file( - user.id, - PMPIC, - caption=message_, - ) - else: - _to_delete[user.id] = await ultroid_bot.send_message( - user.id, message_ - ) - - else: - await delete_pm_warn_msgs(user.id) - message_ = UNAPPROVED_MSG.format( - ON=OWNER_NAME, - warn=wrn, - twarn=WARNS, - UND=UND, - name=name, - fullname=fullname, - username=username, - count=count, - mention=mention, + wrn = 1 + except MessageNotModifiedError: + wrn = 1 + + async def _send_warn_msg(): + message_ = UNAPPROVED_MSG.format( + ON=OWNER_NAME, + warn=wrn, + twarn=WARNS, + UND=UND, + name=name, + fullname=fullname, + username=username, + count=count, + mention=mention, + ) + update_pm(user.id, message_, wrn) + if inline_pm: + try: + results = await ultroid_bot.inline_query(my_bot, f"ip_{user.id}") + _to_delete[user.id] = await results[0].click( + user.id, reply_to=event.id, hide_via=True ) - update_pm(user.id, message_, wrn) - if inline_pm: - try: - results = await ultroid_bot.inline_query( - my_bot, f"ip_{user.id}" - ) - _to_delete[user.id] = await results[0].click( - user.id, reply_to=event.id, hide_via=True - ) - except Exception as e: - LOGS.info(str(e)) - elif PMPIC: - _to_delete[user.id] = await ultroid_bot.send_file( - user.id, - PMPIC, - caption=message_, - ) - else: - _to_delete[user.id] = await ultroid_bot.send_message( - user.id, message_ - ) - LASTMSG.update({user.id: event.text}) + except Exception as ex: + LOGS.info(str(ex)) + elif PMPIC: + _to_delete[user.id] = await ultroid_bot.send_file( + user.id, PMPIC, caption=message_ + ) else: + _to_delete[user.id] = await ultroid_bot.send_message(user.id, message_) + + prevmsg = LASTMSG.get(user.id) + if prevmsg is not None: + if cur_text != prevmsg: + if "PMSecurity" in cur_text: + return await delete_pm_warn_msgs(user.id) - message_ = UNAPPROVED_MSG.format( - ON=OWNER_NAME, - warn=wrn, - twarn=WARNS, - UND=UND, - name=name, - fullname=fullname, - username=username, - count=count, - mention=mention, - ) - update_pm(user.id, message_, wrn) - if inline_pm: - try: - results = await ultroid_bot.inline_query( - my_bot, f"ip_{user.id}" - ) - _to_delete[user.id] = await results[0].click( - user.id, reply_to=event.id, hide_via=True - ) - except Exception as e: - LOGS.info(str(e)) - elif PMPIC: - _to_delete[user.id] = await ultroid_bot.send_file( - user.id, - PMPIC, - caption=message_, - ) - else: - _to_delete[user.id] = await ultroid_bot.send_message( - user.id, message_ - ) - LASTMSG.update({user.id: event.text}) - if user.id not in COUNT_PM: - COUNT_PM.update({user.id: 1}) + await _send_warn_msg() else: - COUNT_PM[user.id] = COUNT_PM[user.id] + 1 - if COUNT_PM[user.id] >= WARNS: await delete_pm_warn_msgs(user.id) - _to_delete[user.id] = await event.respond(UNS) - try: - del COUNT_PM[user.id] - del LASTMSG[user.id] - except KeyError: - await asst.send_message( - udB.get_key("LOG_CHANNEL"), - "PMPermit is messed! Pls restart the bot!!", - ) - return LOGS.info("COUNT_PM is messed.") - await ultroid_bot(BlockRequest(user.id)) - await ultroid_bot(ReportSpamRequest(peer=user.id)) + await _send_warn_msg() + else: + await delete_pm_warn_msgs(user.id) + await _send_warn_msg() + + LASTMSG[user.id] = cur_text + COUNT_PM[user.id] = COUNT_PM.get(user.id, 0) + 1 + if COUNT_PM[user.id] >= WARNS: + await delete_pm_warn_msgs(user.id) + _to_delete[user.id] = await event.respond(UNS) + COUNT_PM.pop(user.id, None) + LASTMSG.pop(user.id, None) + await ultroid_bot(BlockRequest(user.id)) + await ultroid_bot(ReportSpamRequest(peer=user.id)) + if _log_ch: await asst.edit_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, _not_approved[user.id], f"**{mention}** [`{user.id}`] was Blocked for spamming.", ) - @ultroid_cmd(pattern="(start|stop|clear)archive$", fullsudo=True) - async def _(e): - x = e.pattern_match.group(1).strip() - if x == "start": - udB.set_key("MOVE_ARCHIVE", "True") - await e.eor("Now I will move new Unapproved DM's to archive", time=5) - elif x == "stop": - udB.set_key("MOVE_ARCHIVE", "False") - await e.eor("Now I won't move new Unapproved DM's to archive", time=5) - elif x == "clear": - try: - await e.client.edit_folder(unpack=1) - await e.eor("Unarchived all chats", time=5) - except Exception as mm: - await e.eor(str(mm), time=5) - - @ultroid_cmd(pattern="(a|approve)(?: |$)", fullsudo=True) - async def approvepm(apprvpm): - if apprvpm.reply_to_msg_id: - user = (await apprvpm.get_reply_message()).sender - elif apprvpm.is_private: - user = await apprvpm.get_chat() - else: - return await apprvpm.edit(NO_REPLY) - if user.id in DEVLIST: - return await eor( - apprvpm, - "This is a developer account.\nAutomatically approved.", - ) - if not keym.contains(user.id): - keym.add(user.id) - try: - await delete_pm_warn_msgs(user.id) - await apprvpm.client.edit_folder(user.id, folder=0) - except BaseException: - pass - await eod( - apprvpm, - f"{inline_mention(user, html=True)} approved to PM!", - parse_mode="html", - ) + +@ultroid_cmd(pattern="(start|stop|clear)archive$", fullsudo=True) +async def _(e): + x = e.pattern_match.group(1).strip() + if x == "start": + udB.set_key("MOVE_ARCHIVE", "True") + await e.eor("Now I will move new Unapproved DM's to archive", time=5) + elif x == "stop": + udB.set_key("MOVE_ARCHIVE", "False") + await e.eor("Now I won't move new Unapproved DM's to archive", time=5) + elif x == "clear": + try: + await e.client.edit_folder(unpack=1) + await e.eor("Unarchived all chats", time=5) + except Exception as mm: + await e.eor(str(mm), time=5) + + +@ultroid_cmd(pattern="(a|approve)(?: |$)", fullsudo=True) +async def approvepm(apprvpm): + if apprvpm.reply_to_msg_id: + user = (await apprvpm.get_reply_message()).sender + elif apprvpm.is_private: + user = await apprvpm.get_chat() + else: + return await apprvpm.edit(NO_REPLY) + if user.id in DEVLIST: + return await eor( + apprvpm, + "This is a developer account.\nAutomatically approved.", + ) + if not keym.contains(user.id): + keym.add(user.id) + try: + await delete_pm_warn_msgs(user.id) + await apprvpm.client.edit_folder(user.id, folder=0) + except BaseException: + pass + await eod( + apprvpm, + f"{inline_mention(user, html=True)} approved to PM!", + parse_mode="html", + ) + _log_ch = udB.get_key("LOG_CHANNEL") + if _log_ch: try: await asst.edit_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, _not_approved[user.id], f"#APPROVED\n\n{inline_mention(user, html=True)} [{user.id}] was approved to PM you!", buttons=[ @@ -437,7 +399,7 @@ async def approvepm(apprvpm): ) except KeyError: _not_approved[user.id] = await asst.send_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, f"#APPROVED\n\n{inline_mention(user, html=True)} [{user.id}] was approved to PM you!", buttons=[ Button.inline("Disapprove PM", data=f"disapprove_{user.id}"), @@ -447,32 +409,35 @@ async def approvepm(apprvpm): ) except MessageNotModifiedError: pass - else: - await apprvpm.eor("`User may already be approved.`", time=5) - - @ultroid_cmd(pattern="(da|disapprove)(?: |$)", fullsudo=True) - async def disapprovepm(e): - if e.reply_to_msg_id: - user = (await e.get_reply_message()).sender - elif e.is_private: - user = await e.get_chat() - else: - return await e.edit(NO_REPLY) - if user.id in DEVLIST: - return await eor( - e, - "`This is a developer account.\nCannot be disapproved.`", - ) - if keym.contains(user.id): - keym.remove(user.id) - await eod( - e, - f"{inline_mention(user, html=True)} Disapproved to PM!", - parse_mode="html", - ) + else: + await apprvpm.eor("`User may already be approved.`", time=5) + + +@ultroid_cmd(pattern="(da|disapprove)(?: |$)", fullsudo=True) +async def disapprovepm(e): + if e.reply_to_msg_id: + user = (await e.get_reply_message()).sender + elif e.is_private: + user = await e.get_chat() + else: + return await e.edit(NO_REPLY) + if user.id in DEVLIST: + return await eor( + e, + "`This is a developer account.\nCannot be disapproved.`", + ) + if keym.contains(user.id): + keym.remove(user.id) + await eod( + e, + f"{inline_mention(user, html=True)} Disapproved to PM!", + parse_mode="html", + ) + _log_ch = udB.get_key("LOG_CHANNEL") + if _log_ch: try: await asst.edit_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, _not_approved[user.id], f"#DISAPPROVED\n\n{inline_mention(user, html=True)} [{user.id}] was disapproved to PM you.", buttons=[ @@ -483,7 +448,7 @@ async def disapprovepm(e): ) except KeyError: _not_approved[user.id] = await asst.send_message( - udB.get_key("LOG_CHANNEL"), + _log_ch, f"#DISAPPROVED\n\n{inline_mention(user, html=True)} [{user.id}] was disapproved to PM you.", buttons=[ Button.inline("Approve PM", data=f"approve_{user.id}"), @@ -493,12 +458,12 @@ async def disapprovepm(e): ) except MessageNotModifiedError: pass - else: - await eod( - e, - f"{inline_mention(user, html=True)} was never approved!", - parse_mode="html", - ) + else: + await eod( + e, + f"{inline_mention(user, html=True)} was never approved!", + parse_mode="html", + ) @ultroid_cmd(pattern="block( (.*)|$)", fullsudo=True) diff --git a/plugins/snips.py b/plugins/snips.py index b77a4d1319..e27f92b77c 100644 --- a/plugins/snips.py +++ b/plugins/snips.py @@ -68,7 +68,9 @@ async def an(e): txt, btn = get_msg_button(wt.text) add_snip(wrd, txt, None, btn) await e.eor(f"Done : snip `${wrd}` Saved.") - ultroid_bot.add_handler(add_snips, events.NewMessage()) + if not udB.get_key("SNIP"): + udB.set_key("SNIP", True) + ultroid_bot.add_handler(add_snips, events.NewMessage()) @ultroid_cmd(pattern="remsnip( (.*)|$)") diff --git a/plugins/twitter.py b/plugins/twitter.py index e97d17f8a2..c105c923e9 100644 --- a/plugins/twitter.py +++ b/plugins/twitter.py @@ -22,8 +22,23 @@ """ +import asyncio import os + +try: + _current_loop = asyncio.get_event_loop() +except RuntimeError: + _current_loop = None + from twikit import Client + +if _current_loop is not None: + # On Windows, importing twikit replaces the global asyncio event loop + # policy (see twikit/__init__.py), which orphans the loop Ultroid's + # clients are already connected to. Re-register it so existing + # Telethon connections keep working. + asyncio.set_event_loop(_current_loop) + from . import LOGS, eor, get_string, udB, ultroid_cmd # Store client globally diff --git a/plugins/utilities.py b/plugins/utilities.py index b76653620e..42414aea9f 100644 --- a/plugins/utilities.py +++ b/plugins/utilities.py @@ -214,8 +214,14 @@ async def stats( sp_count = len(sp.sets) except BaseException: sp_count = 0 - full_name = inline_mention(event.client.me) - response = f"🔸 **Stats for {full_name}** \n\n" + me = event.client.me + full_name = inline_mention(me) + username_line = ( + f" (`@{me.username}` | [Profile](https://t.me/{me.username}))" + if me.username + else f" (`ID: {me.id}`)" + ) + response = f"🔸 **Stats for {full_name}**{username_line}\n\n" response += f"**Private Chats:** {private_chats} \n" response += f"** •• **`Users: {private_chats - bots}` \n" response += f"** •• **`Bots: {bots}` \n" diff --git a/pyUltroid/__main__.py b/pyUltroid/__main__.py index 061b79ea7e..82e67c8562 100644 --- a/pyUltroid/__main__.py +++ b/pyUltroid/__main__.py @@ -91,7 +91,7 @@ def main(): ultroid_bot.run_in_loop(WasItRestart(udB)) try: - cleanup_cache() + udB.re_cache() except BaseException: pass diff --git a/pyUltroid/_misc/_assistant.py b/pyUltroid/_misc/_assistant.py index af3c2f8821..b5f8032ef0 100644 --- a/pyUltroid/_misc/_assistant.py +++ b/pyUltroid/_misc/_assistant.py @@ -66,8 +66,10 @@ async def handler(event): return ult -def callback(data=None, from_users=[], admins=False, owner=False, **kwargs): +def callback(data=None, from_users=None, admins=False, owner=False, **kwargs): """Assistant's callback decorator""" + if from_users is None: + from_users = [] if "me" in from_users: from_users.remove("me") from_users.append(ultroid_bot.uid) diff --git a/pyUltroid/_misc/_decorators.py b/pyUltroid/_misc/_decorators.py index 5594f93cdf..2a92aa6dce 100644 --- a/pyUltroid/_misc/_decorators.py +++ b/pyUltroid/_misc/_decorators.py @@ -126,17 +126,20 @@ async def wrapp(ult): try: await dec(ult) except FloodWaitError as fwerr: - await asst.send_message( - udB.get_key("LOG_CHANNEL"), - f"`FloodWaitError:\n{str(fwerr)}\n\nSleeping for {tf((fwerr.seconds + 10)*1000)}`", - ) + _log_ch = udB.get_key("LOG_CHANNEL") + if _log_ch: + await asst.send_message( + _log_ch, + f"`FloodWaitError:\n{str(fwerr)}\n\nSleeping for {tf((fwerr.seconds + 10)*1000)}`", + ) await ultroid_bot.disconnect() await asyncio.sleep(fwerr.seconds + 10) await ultroid_bot.connect() - await asst.send_message( - udB.get_key("LOG_CHANNEL"), - "`Bot is working again`", - ) + if _log_ch: + await asst.send_message( + _log_ch, + "`Bot is working again`", + ) return except ChatSendInlineForbiddenError: return await eod(ult, "`Inline Locked In This Chat.`") @@ -203,22 +206,25 @@ async def wrapp(ult): ftext += f"{result}`" - if len(ftext) > 4096: - with BytesIO(ftext.encode()) as file: - file.name = "logs.txt" - error_log = await asst.send_file( - udB.get_key("LOG_CHANNEL"), - file, - caption="**Ultroid Client Error:** `Forward this to` @UltroidSupportChat\n\n", + _log_ch = udB.get_key("LOG_CHANNEL") + error_log = None + if _log_ch: + if len(ftext) > 4096: + with BytesIO(ftext.encode()) as file: + file.name = "logs.txt" + error_log = await asst.send_file( + _log_ch, + file, + caption="**Ultroid Client Error:** `Forward this to` @UltroidSupportChat\n\n", + ) + else: + error_log = await asst.send_message( + _log_ch, + ftext, ) - else: - error_log = await asst.send_message( - udB.get_key("LOG_CHANNEL"), - ftext, - ) - if ult.out: + if ult.out and error_log: await ult.edit( - f"[An error occurred]", + f'[An error occurred]', link_preview=False, parse_mode="html", ) diff --git a/pyUltroid/configs.py b/pyUltroid/configs.py index 5677e1685b..94d5718475 100644 --- a/pyUltroid/configs.py +++ b/pyUltroid/configs.py @@ -53,3 +53,5 @@ class Var: DATABASE_URL = config("DATABASE_URL", default=None) # for MONGODB users MONGO_URI = config("MONGO_URI", default=None) + # for local Telegram DB backup + TGDB_URL = config("TGDB_URL", default=None) diff --git a/pyUltroid/fns/helper.py b/pyUltroid/fns/helper.py index c51ab80e22..8d1149ba1e 100644 --- a/pyUltroid/fns/helper.py +++ b/pyUltroid/fns/helper.py @@ -22,9 +22,10 @@ try: - from aiohttp import ClientSession as aiohttp_client + from aiohttp import ClientSession as aiohttp_client, ClientTimeout as aiohttp_timeout except ImportError: aiohttp_client = None + aiohttp_timeout = None try: import requests except ImportError: @@ -78,6 +79,24 @@ async def wrapper(*args, **kwargs): # ~~~~~~~~~~~~~~~~~~~~ small funcs ~~~~~~~~~~~~~~~~~~~~ # +class KEEP_SAFE: + """Patterns to scan for in plugins before allowing installation.""" + + All = [ + r"os\.system", + r"subprocess\.(run|call|Popen|check_output)", + r"exec\s*\(", + r"eval\s*\(", + r"__import__\s*\(", + r"open\s*\(.*['\"]w['\"]", + r"shutil\.(rmtree|move|copy)", + r"session\.string", + r"get_me\(\)", + r"api_id", + r"api_hash", + ] + + def make_mention(user, custom=None): if user.username: return f"@{user.username}" @@ -88,11 +107,11 @@ def inline_mention(user, custom=None, html=False): mention_text = get_display_name(user) or "Deleted Account" if not custom else custom if isinstance(user, types.User): if html: - return f"{mention_text}" + return f'{mention_text}' return f"[{mention_text}](tg://user?id={user.id})" if isinstance(user, types.Channel) and user.username: if html: - return f"{mention_text}" + return f'{mention_text}' return f"[{mention_text}](https://t.me/{user.username})" return mention_text @@ -370,7 +389,7 @@ async def async_searcher( if evaluate: return await evaluate(data) if re_json: - return await data.json() + return await data.json(content_type=None) if re_content: return await data.read() if head or object: @@ -410,8 +429,9 @@ async def _download(content): async def fast_download(download_url, filename=None, progress_callback=None): if not aiohttp_client: return await download_file(download_url, filename)[0], None - async with aiohttp_client() as session: - async with session.get(download_url, timeout=None) as response: + _timeout = aiohttp_timeout(total=None) if aiohttp_timeout else None + async with aiohttp_client(timeout=_timeout) as session: + async with session.get(download_url) as response: if not filename: filename = unquote(download_url.rpartition("/")[-1]) total_size = int(response.headers.get("content-length", 0)) or None diff --git a/pyUltroid/fns/tools.py b/pyUltroid/fns/tools.py index b1f75831bb..4308973921 100644 --- a/pyUltroid/fns/tools.py +++ b/pyUltroid/fns/tools.py @@ -696,15 +696,15 @@ async def get_stored_file(event, hash): if not msg_id: return try: - msg = await asst.get_messages(udB.get_key("LOG_CHANNEL"), ids=msg_id) + msg = await asst.get_messages(udB.get_key("LOG_CHANNEL"), ids=int(msg_id)) except Exception as er: LOGS.warning(f"FileStore, Error: {er}") return - if not msg_id: + if not msg or 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) + await asst.send_message(event.chat_id, msg.text or "", file=msg.media, reply_to=event.id) def translate(text, lang_tgt="en", lang_src="auto", timeout=60, detect=False): diff --git a/pyUltroid/startup/_database.py b/pyUltroid/startup/_database.py index d6845c9199..79100fb392 100644 --- a/pyUltroid/startup/_database.py +++ b/pyUltroid/startup/_database.py @@ -314,7 +314,13 @@ def name(self): return "LocalDB" def keys(self): - return self._cache.keys() + try: + raw = self.db._raw_data() + if isinstance(raw, dict): + return list(raw.keys()) + except Exception: + pass + return list(self._cache.keys()) def __repr__(self): return f"" diff --git a/pyUltroid/startup/funcs.py b/pyUltroid/startup/funcs.py index eda2fa39c2..b893aebd25 100644 --- a/pyUltroid/startup/funcs.py +++ b/pyUltroid/startup/funcs.py @@ -94,8 +94,12 @@ 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)] + env_file = config._find_file(".") + if env_file: + try: + [_envs.append(_) for _ in list(RepositoryEnv(env_file).data)] + except Exception: + pass for envs in _envs: if ( envs in ["LOG_CHANNEL", "BOT_TOKEN", "BOTMODE", "DUAL_MODE", "language"] @@ -104,7 +108,7 @@ def update_envs(): if _value := os.environ.get(envs): udB.set_key(envs, _value) else: - udB.set_key(envs, config.config.get(envs)) + udB.set_key(envs, config(envs, default=None)) async def startup_stuff(): @@ -540,11 +544,11 @@ def _version_changes(udb): "BROADCAST", ]: key = udb.get_key(_) - if key and str(key)[0] != "[": - key = udb.get(_) + if key and not isinstance(key, list): + key_str = str(key) new_ = [ int(z) if z.isdigit() or (z.startswith("-") and z[1:].isdigit()) else z - for z in key.split() + for z in key_str.split() ] udb.set_key(_, new_) diff --git a/pyUltroid/startup/loader.py b/pyUltroid/startup/loader.py index 6294f94ec8..61be2f7fc8 100644 --- a/pyUltroid/startup/loader.py +++ b/pyUltroid/startup/loader.py @@ -72,8 +72,12 @@ def load_other_plugins(addons=None, pmbot=None, manager=None, vcbot=None): if os.path.exists("addons") and not os.path.exists("addons/.git"): rmtree("addons") if not os.path.exists("addons"): + try: + branch = str(Repo().active_branch) + except Exception: + branch = "main" subprocess.run( - f"git clone -q -b {Repo().active_branch} https://github.com/TeamUltroid/UltroidAddons.git addons", + f"git clone -q -b {branch} https://github.com/TeamUltroid/UltroidAddons.git addons", shell=True, ) else: diff --git a/requirements.txt b/requirements.txt index acbc790d4b..7a06ed940f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ # Important Requirements here. -telethon -gitpython +telethon>=1.44.0 +gitpython>=3.1.52 https://github.com/New-dev0/Telethon-Patch/archive/main.zip -python-decouple -python-dotenv -telegraph -enhancer -requests -aiohttp -catbox-uploader -cloudscraper \ No newline at end of file +python-decouple>=3.8 +python-dotenv>=1.2.2 +telegraph>=2.2.0 +enhancer>=0.3.4 +requests>=2.34.2 +aiohttp>=3.14.1 +catbox-uploader>=2.9 +cloudscraper>=1.2.71