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