Files
Ultroid-fork/pyUltroid/startup/funcs.py
google-labs-jules[bot] 522cf932ec fix: Address Pylint issues and bugs in pyUltroid/startup/
- Corrected undefined variable 'x' in funcs.py.
- Refactored configuration loading in funcs.py for clarity and to resolve Pylint errors.
- Changed logging f-strings to %-style formatting across multiple files.
- Added encoding='utf-8' to open() calls.
- Replaced list comprehensions used for side-effects with for-loops.
- Made some broad exception catches more specific.
- Added check=True to subprocess.run() calls in loader.py.
- Corrected function signature parameter orders (e.g., *args placement).
- Removed some unused variables and imports.
- Added Pylint disable comments for known false positives (e.g., no-member for psycopg2.errors and base class methods).
- Improved error handling and logging in funcs.py for startup sequences.
- Addressed dependency installation issues by commenting out 'pokedex' (unavailable on PyPI) and correcting case for 'SpeechRecognition' in requirements.txt.
2025-06-19 20:58:47 +00:00

782 lines
35 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
# <https://github.com/TeamUltroid/pyUltroid/blob/main/LICENSE>.
import asyncio
import os
import random
import shutil
import time
from random import randint
from ..configs import Var as UltroidConfigVars # Renamed for clarity
try:
from pytz import timezone, exceptions as pytz_exceptions
except ImportError:
timezone = None
pytz_exceptions = None # Define if pytz not available
from telethon.errors import (
ChannelsTooMuchError,
ChatAdminRequiredError,
MessageIdInvalidError,
MessageNotModifiedError,
UserNotParticipantError,
# Specific RPC errors if known for some operations
# e.g. rpcerrorlist.PhoneNumberInvalidError
)
from telethon.tl.custom import Button
from telethon.tl.functions.channels import (
CreateChannelRequest,
EditAdminRequest,
EditPhotoRequest,
InviteToChannelRequest,
)
from telethon.tl.functions.channels import JoinChannelRequest
from telethon.tl.functions.contacts import UnblockRequest
from telethon.tl.types import (
ChatAdminRights,
ChatPhotoEmpty,
InputChatUploadedPhoto,
InputMessagesFilterDocument,
)
from telethon.utils import get_peer_id
from decouple import config as decouple_config, RepositoryEnv # Aliased decouple.config
from .. import LOGS, ULTConfig
from ..fns.helper import download_file, inline_mention, updater
db_url = 0 # Module level global
async def autoupdate_local_database():
# Var here refers to the UltroidConfigVars instance if it's made global in __main__ or similar
# Or it should be passed or accessed consistently.
# Assuming Var is an instance of UltroidConfigVars accessible here.
# For now, let's assume it's correctly accessed from `from .. import Var` if Var is an instance.
# If `from .. import Var` actually imports the class, then this needs `ultroid_vars = UltroidConfigVars()`
# For this fix, I'll assume `Var` is the instance for now, as per original structure.
# If `Var` is the class, then `Var.TGDB_URL` would be an issue.
# The `configs.py` defines Var as a class. So `Var.TGDB_URL` is indeed problematic.
# This function is complex and relies on how Var, asst, udB, ultroid_bot are initialized and made available.
# Let's use `decouple_config` for TGDB_URL for safety, assuming it's an env var.
from .. import asst, udB, ultroid_bot, Var as GlobalUltroidConfig # Assuming this Var is the instance
global db_url
# Prioritize udB, then direct decouple_config, then cache
# GlobalUltroidConfig.TGDB_URL isn't defined in configs.py.
# This line was `udB.get_key("TGDB_URL") or Var.TGDB_URL or ultroid_bot._cache.get("TGDB_URL")`
# Var.TGDB_URL is problematic.
current_tgdb_url_env = decouple_config("TGDB_URL", default=None)
db_url = (
udB.get_key("TGDB_URL") or current_tgdb_url_env or ultroid_bot._cache.get("TGDB_URL")
)
if db_url:
_split = db_url.split("/")
if len(_split) > 2: # Basic check
_channel = _split[-2]
_id = _split[-1]
try:
await asst.edit_message(
int(_channel) if _channel.isdigit() else _channel,
message=int(_id), # message id should be int
file="database.json",
text="**Do not delete this file.**",
)
except MessageNotModifiedError:
return
except (MessageIdInvalidError, ValueError): # ValueError if _id is not int
LOGS.warning("Invalid message ID or channel for TGDB_URL update: %s", db_url)
except Exception as e_inner: # More specific Telethon errors if possible
LOGS.error("Error editing TGDB message: %s", e_inner)
else:
LOGS.warning("Malformed TGDB_URL: %s", db_url)
try:
# Similar issue with Var.LOG_CHANNEL
current_log_channel_env = decouple_config("LOG_CHANNEL", default=0, cast=int)
log_channel_val = (
udB.get_key("LOG_CHANNEL")
or current_log_channel_env
or asst._cache.get("LOG_CHANNEL")
or "me"
)
# Ensure log_channel_val is int if it's a number string for user_id/chat_id
if isinstance(log_channel_val, str) and log_channel_val.isdigit():
log_channel_val = int(log_channel_val)
elif isinstance(log_channel_val, str) and log_channel_val.startswith("-") and log_channel_val[1:].isdigit():
log_channel_val = int(log_channel_val)
msg = await asst.send_message(
log_channel_val, "**Do not delete this file.**", file="database.json"
)
asst._cache["TGDB_URL"] = msg.message_link
udB.set_key("TGDB_URL", msg.message_link)
except Exception as ex: # Catch more specific errors if known (e.g., telethon RPC errors)
LOGS.error("Error on autoupdate_local_database (sending new DB backup): %s", ex)
def update_envs():
"""Update Var attributes to udB"""
from .. import udB
_envs = [*list(os.environ)]
if ".env" in os.listdir("."):
# Original: RepositoryEnv(config._find_file('.')).data
# decouple.config is now decouple_config. We need the original config object for _find_file
# This is tricky because decouple.config is a function.
# RepositoryEnv itself finds the .env file.
try:
env_data = RepositoryEnv(".env").data # Attempt to directly use RepositoryEnv
for key_val_tuple in env_data: # RepositoryEnv.data is a list of tuples
_envs.append(key_val_tuple[0]) # Add only keys to _envs list for consistency
except Exception as e:
LOGS.warning("Could not read .env file for update_envs: %s", e)
for env_name in _envs:
# Check if this is one of the envs we care about syncing to udB
if (
env_name in ["LOG_CHANNEL", "BOT_TOKEN", "BOTMODE", "DUAL_MODE", "language"]
or env_name in udB.keys() # Sync if it's already a key in udB
):
# Prioritize os.environ, then decouple_config (which reads .env)
_value = os.environ.get(env_name)
if _value is None:
_value = decouple_config(env_name, default=None) # Use decouple_config here
if _value is not None: # Only set if we found a value
udB.set_key(env_name, _value)
# No else needed: if not in os.environ and not in .env (decouple_config returns None), don't change udB
async def startup_stuff():
from .. import udB
# Original: x = ["resources/auth", "resources/downloads"]
# Original: for x in x: if not os.path.isdir(x): os.mkdir(x)
# This was the E0602: Undefined variable 'x' because 'x' (the list) was overwritten by 'x' (the item)
folder_paths = ["resources/auth", "resources/downloads"]
for folder_path in folder_paths:
if not os.path.isdir(folder_path):
try:
os.makedirs(folder_path, exist_ok=True) # Use makedirs for safety
except OSError as e:
LOGS.error("Failed to create directory %s: %s", folder_path, e)
CT = udB.get_key("CUSTOM_THUMBNAIL")
if CT:
path = "resources/extras/thumbnail.jpg"
ULTConfig.thumb = path
try:
await download_file(CT, path)
except IOError as e: # More specific for file download issues
LOGS.error("Failed to download custom thumbnail: %s", e)
except Exception as er: # Catch other potential errors from download_file
LOGS.error("Generic error downloading custom thumbnail: %s", er)
elif CT is False:
ULTConfig.thumb = None
GT = udB.get_key("GDRIVE_AUTH_TOKEN")
if GT:
try:
with open("resources/auth/gdrive_creds.json", "w", encoding="utf-8") as t_file:
t_file.write(GT)
except IOError as e:
LOGS.error("Failed to write gdrive_creds.json: %s", e)
if udB.get_key("AUTH_TOKEN"):
udB.del_key("AUTH_TOKEN")
MM = udB.get_key("MEGA_MAIL")
MP = udB.get_key("MEGA_PASS")
if MM and MP:
try:
with open(".megarc", "w", encoding="utf-8") as mega:
mega.write(f"[Login]\nUsername = {MM}\nPassword = {MP}")
except IOError as e:
LOGS.error("Failed to write .megarc file: %s", e)
TZ = udB.get_key("TIMEZONE")
if TZ and timezone and pytz_exceptions: # Ensure pytz was imported
try:
timezone(TZ) # Validate timezone
os.environ["TZ"] = TZ
time.tzset()
except pytz_exceptions.UnknownTimeZoneError: # Specific exception for timezone
LOGS.error(
"Incorrect Timezone '%s'. Check available timezones. Defaulting to UTC.", TZ
)
os.environ["TZ"] = "UTC"
time.tzset()
except AttributeError as er: # If timezone() call failed for other reasons
LOGS.debug("AttributeError setting timezone: %s", er)
# Removed broad BaseException catch here, be more specific if other errors occur
elif not timezone and TZ :
LOGS.warning("Timezone package (pytz) not available, cannot set timezone %s", TZ)
async def autobot():
from .. import udB, ultroid_bot
if udB.get_key("BOT_TOKEN"):
return
await ultroid_bot.start() # Assuming ultroid_bot is already connected if this is called
LOGS.info("MAKING A TELEGRAM BOT FOR YOU AT @BotFather, Kindly Wait")
who = ultroid_bot.me
name = who.first_name + "'s Bot"
if who.username:
username = who.username + "_bot"
else:
username = "ultroid_" + (str(who.id))[5:] + "_bot"
bf = "@BotFather"
try:
await ultroid_bot(UnblockRequest(bf))
await ultroid_bot.send_message(bf, "/cancel")
await asyncio.sleep(1) # Consider making delays configurable or shorter if possible
await ultroid_bot.send_message(bf, "/newbot")
await asyncio.sleep(1)
isdone_msg = await ultroid_bot.get_messages(bf, limit=1)
if not isdone_msg:
LOGS.error("Failed to get response from BotFather after /newbot.")
return # Or sys.exit as originally
isdone = isdone_msg[0].text
if isdone.startswith("That I cannot do.") or "20 bots" in isdone:
LOGS.critical(
"Please make a Bot from @BotFather and add its token in BOT_TOKEN, as an env var and restart me."
)
sys.exit(1) # Keep sys.exit for critical setup failures
await ultroid_bot.send_message(bf, name)
await asyncio.sleep(1)
isdone_msg = await ultroid_bot.get_messages(bf, limit=1)
if not isdone_msg:
LOGS.error("Failed to get response from BotFather after sending bot name.")
return
isdone = isdone_msg[0].text
if not isdone.startswith("Good."):
await ultroid_bot.send_message(bf, "My Assistant Bot") # Fallback name
await asyncio.sleep(1)
isdone_msg = await ultroid_bot.get_messages(bf, limit=1)
if not isdone_msg:
LOGS.error("Failed to get response from BotFather after sending fallback name.")
return
isdone = isdone_msg[0].text
if not isdone.startswith("Good."):
LOGS.critical(
"Failed to set bot name. Please make a Bot from @BotFather and add its token in BOT_TOKEN."
)
sys.exit(1)
await ultroid_bot.send_message(bf, username)
await asyncio.sleep(1)
isdone_msg = await ultroid_bot.get_messages(bf, limit=1)
if not isdone_msg:
LOGS.error("Failed to get response from BotFather after sending username.")
return
isdone = isdone_msg[0].text
await ultroid_bot.send_read_acknowledge(bf) # Acknowledge BotFather's messages
if isdone.startswith("Sorry,"): # Username taken
ran = randint(1, 100)
username = "ultroid_" + (str(who.id))[6:] + str(ran) + "_bot"
await ultroid_bot.send_message(bf, username)
await asyncio.sleep(1)
isdone_msg = await ultroid_bot.get_messages(bf, limit=1)
if not isdone_msg:
LOGS.error("Failed to get response from BotFather after sending new username.")
return
isdone = isdone_msg[0].text
if isdone.startswith("Done!"):
try:
token = isdone.split("`")[1]
udB.set_key("BOT_TOKEN", token)
await enable_inline(ultroid_bot, username)
LOGS.info(
"Done. Successfully created @%s to be used as your assistant bot!", username
)
except IndexError:
LOGS.error("Failed to parse token from BotFather's response: %s", isdone)
sys.exit(1)
else:
LOGS.error(
"Bot creation failed at BotFather with response: %s. "
"Please delete some of your Telegram bots or set BOT_TOKEN manually.", isdone
)
sys.exit(1)
except UserNotParticipantError: # Or other specific Telethon errors
LOGS.error("BotFather interaction failed: UserNotParticipantError or similar. Is @BotFather blocked?")
except Exception as e: # Catch-all for other unexpected errors during bot creation
LOGS.error("An unexpected error occurred during autobot setup: %s", e, exc_info=True)
# Consider if sys.exit is needed here or if the bot can continue without assistant.
async def autopilot():
from .. import asst, udB, ultroid_bot
channel_id_str = udB.get_key("LOG_CHANNEL")
new_channel_created = False # Flag to track if we created the channel in this run
chat = None
if channel_id_str:
try:
# Ensure channel_id_str is correctly formatted for get_entity (int or specific string)
if isinstance(channel_id_str, str) and (channel_id_str.isdigit() or (channel_id_str.startswith("-") and channel_id_str[1:].isdigit())):
channel_entity_id = int(channel_id_str)
else: # Assume it's a username or invite link if not purely numeric string
channel_entity_id = channel_id_str
chat = await ultroid_bot.get_entity(channel_entity_id)
channel_id_str = get_peer_id(chat) # Normalize to peer ID
udB.set_key("LOG_CHANNEL", str(channel_id_str)) # Save normalized ID
except ValueError: # If channel_id_str is not a valid entity identifier
LOGS.error("LOG_CHANNEL value '%s' is not a valid channel/chat ID or username.", channel_id_str)
udB.del_key("LOG_CHANNEL")
channel_id_str = None
except Exception as err: # Catch other specific Telethon errors if possible
LOGS.error("Error getting entity for LOG_CHANNEL %s: %s", channel_id_str, err, exc_info=False)
udB.del_key("LOG_CHANNEL") # Invalidate problematic channel ID
channel_id_str = None
if not channel_id_str:
async def _save_pm_fallback(exc_msg):
# Fallback to PM if channel creation fails
udB.set_key("LOG_CHANNEL", str(ultroid_bot.me.id)) # Use string for consistency
await asst.send_message(
ultroid_bot.me.id, f"Failed to Create/Verify Log Channel due to: {exc_msg}. Logging to PM."
)
if ultroid_bot._bot: # Assuming _bot means it's running in BOT_MODE
msg_ = "'LOG_CHANNEL' not found! Add it in order to use 'BOTMODE'."
LOGS.error(msg_)
# In BOT_MODE, perhaps we shouldn't fallback to PM, or make it configurable.
# For now, let's assume it should not create a channel in BOT_MODE if not set.
return
LOGS.info("Creating a Log Channel for You!")
try:
r = await ultroid_bot(
CreateChannelRequest(
title="My Ultroid Logs",
about="My Ultroid Log Group\n\nJoin @TeamUltroid",
megagroup=True, # Creating a supergroup
),
)
chat = r.chats[0]
channel_id_str = str(get_peer_id(chat)) # Store as string
udB.set_key("LOG_CHANNEL", channel_id_str)
new_channel_created = True
LOGS.info("Successfully created new log channel: %s", channel_id_str)
except ChannelsTooMuchError as er:
LOGS.critical(
"You are in too many channels & groups. Please leave some and restart the bot."
)
await _save_pm_fallback(str(er))
return
except Exception as er: # Catch other specific Telethon errors
LOGS.error("Failed to create log channel: %s", er, exc_info=True)
await _save_pm_fallback(str(er))
return
if not chat and channel_id_str : # If channel_id_str was from DB but chat object wasn't fetched
try:
chat = await ultroid_bot.get_entity(int(channel_id_str)) # Assuming it's an int ID by now
except Exception as e:
LOGS.error("Failed to get chat entity for existing LOG_CHANNEL %s: %s", channel_id_str, e)
return # Cannot proceed without chat object
if not chat:
LOGS.error("Log channel setup failed, chat object is None.")
return
# Assistant invitation and promotion logic
assistant_can_operate = True
try:
# get_permissions requires int channel ID
await ultroid_bot.get_permissions(int(channel_id_str), asst.me.username)
except UserNotParticipantError:
LOGS.info("Assistant not in log channel. Inviting...")
try:
await ultroid_bot(InviteToChannelRequest(int(channel_id_str), [asst.me.username]))
except Exception as er: # Catch specific errors like ChatAdminRequired, UserBlocked, etc.
LOGS.error("Error inviting assistant to log channel: %s", er)
assistant_can_operate = False
except Exception as er:
LOGS.error("Error checking assistant permissions in log channel: %s", er)
assistant_can_operate = False
if assistant_can_operate and new_channel_created: # Only try to promote if we just created it and assistant is in
LOGS.info("Promoting assistant in newly created log channel...")
try:
# Ensure asst client has fetched the channel entity too
await asst.get_entity(int(channel_id_str))
rights = ChatAdminRights(
add_admins=True, invite_users=True, change_info=True,
ban_users=True, delete_messages=True, pin_messages=True,
anonymous=False, manage_call=True,
)
await ultroid_bot(
EditAdminRequest(int(channel_id_str), asst.me.username, rights, "Assistant")
)
LOGS.info("Successfully promoted assistant in log channel.")
except ChatAdminRequiredError:
LOGS.warning(
"Failed to promote 'Assistant Bot' in 'Log Channel': Bot needs admin rights in the channel."
)
except Exception as er:
LOGS.error("Error promoting assistant in log channel: %s", er, exc_info=True)
# Set channel photo if it's new or has no photo
if chat and isinstance(chat.photo, ChatPhotoEmpty):
photo_path = None
try:
photo_url = "https://graph.org/file/27c6812becf6f376cbb10.jpg"
photo_path, _ = await download_file(photo_url, "channelphoto.jpg")
uploaded_photo_file = await ultroid_bot.upload_file(photo_path)
await ultroid_bot(
EditPhotoRequest(int(channel_id_str), InputChatUploadedPhoto(uploaded_photo_file))
)
LOGS.info("Successfully set photo for log channel.")
except Exception as er: # Catch specific errors for download/upload/editphoto
LOGS.error("Failed to set photo for log channel: %s", er)
finally:
if photo_path and os.path.exists(photo_path):
os.remove(photo_path)
async def customize():
from .. import asst, udB, ultroid_bot
downloaded_profile_pic_path = None
try:
log_channel_id_str = udB.get_key("LOG_CHANNEL")
if not log_channel_id_str:
LOGS.warning("LOG_CHANNEL not found, cannot send customization status message.")
# Decide if customization should proceed without a log channel or return.
# For now, let's allow it to proceed but it won't be able to send status updates.
if asst.me.photo: # Check if assistant already has a profile photo
LOGS.info("Assistant bot already has a profile picture. Skipping customization of picture.")
# Optionally, one could still update description/about text. For now, full skip.
return
LOGS.info("Customising Assistant Bot via @BotFather...")
assistant_username_mention = f"@{asst.me.username}"
master_mention = ultroid_bot.me.first_name
if ultroid_bot.me.username:
master_mention = f"@{ultroid_bot.me.username}"
# Profile picture selection
profile_pic_options = [
"https://graph.org/file/92cd6dbd34b0d1d73a0da.jpg",
"https://graph.org/file/a97973ee0425b523cdc28.jpg",
"resources/extras/ultroid_assistant.jpg", # Local fallback
]
chosen_pic_source = random.choice(profile_pic_options)
if chosen_pic_source.startswith("http"):
downloaded_profile_pic_path, _ = await download_file(chosen_pic_source, "profile_temp.jpg")
elif os.path.exists(chosen_pic_source):
downloaded_profile_pic_path = chosen_pic_source
else:
LOGS.warning("Selected profile picture source %s not found or inaccessible.", chosen_pic_source)
downloaded_profile_pic_path = None # No picture will be set
status_msg = None
if log_channel_id_str:
try:
status_msg = await asst.send_message(
int(log_channel_id_str), "**Auto Customisation** initiated with @BotFather..."
)
except Exception as e_log:
LOGS.warning("Failed to send initial customization status to log channel: %s", e_log)
bot_father_handle = "BotFather" # Less prone to typos
# Interaction with BotFather
await ultroid_bot.send_message(bot_father_handle, "/cancel") # Start clean
await asyncio.sleep(1)
if downloaded_profile_pic_path:
await ultroid_bot.send_message(bot_father_handle, "/setuserpic")
await asyncio.sleep(1)
# Add check for BotFather's response here if needed
await ultroid_bot.send_message(bot_father_handle, assistant_username_mention)
await asyncio.sleep(1)
await ultroid_bot.send_file(bot_father_handle, downloaded_profile_pic_path)
await asyncio.sleep(2) # Allow time for processing
LOGS.info("Assistant profile picture updated.")
# Set About Text
await ultroid_bot.send_message(bot_father_handle, "/setabouttext")
await asyncio.sleep(1)
await ultroid_bot.send_message(bot_father_handle, assistant_username_mention)
await asyncio.sleep(1)
await ultroid_bot.send_message(
bot_father_handle, f"✨ Hello ✨!! I'm Assistant Bot of {master_mention}"
)
await asyncio.sleep(2)
LOGS.info("Assistant about text updated.")
# Set Description
await ultroid_bot.send_message(bot_father_handle, "/setdescription")
await asyncio.sleep(1)
await ultroid_bot.send_message(bot_father_handle, assistant_username_mention)
await asyncio.sleep(1)
await ultroid_bot.send_message(
bot_father_handle,
f"✨ Powerful Ultroid Assistant Bot ✨\n✨ Master ~ {master_mention}\n\n✨ Powered By ~ @TeamUltroid ✨"
)
await asyncio.sleep(2)
LOGS.info("Assistant description updated.")
await ultroid_bot.send_read_acknowledge(bot_father_handle)
if status_msg:
await status_msg.edit("Completed **Auto Customisation** at @BotFather.")
LOGS.info("Assistant bot customization complete.")
except Exception as e: # Catch more specific Telethon errors if they occur often
LOGS.error("Error during assistant customization: %s", e, exc_info=True)
if status_msg:
try:
await status_msg.edit(f"Customization failed: {e}")
except: pass # Ignore error editing status message
finally:
if downloaded_profile_pic_path and downloaded_profile_pic_path == "profile_temp.jpg" and os.path.exists(downloaded_profile_pic_path):
os.remove(downloaded_profile_pic_path)
async def plug(plugin_channels):
from .. import ultroid_bot
from .utils import load_addons # Assuming this is correctly placed
if ultroid_bot._bot: # Check if running in BOT_MODE
LOGS.info("Plugin Channels can't be used in 'BOTMODE'. Skipping plugin loading from channels.")
return
# Ensure addons directory exists
addons_dir = "addons"
if os.path.exists(addons_dir) and not os.path.isdir(addons_dir):
LOGS.error("'%s' exists but is not a directory. Cannot load addons.", addons_dir)
return # Or attempt to remove/rename file and create dir
if not os.path.exists(addons_dir):
try:
os.makedirs(addons_dir)
except OSError as e:
LOGS.error("Failed to create addons directory '%s': %s", addons_dir, e)
return
# Ensure addons/__init__.py exists
addons_init_py = os.path.join(addons_dir, "__init__.py")
if not os.path.exists(addons_init_py):
try:
with open(addons_init_py, "w", encoding="utf-8") as f:
f.write("# This file makes Python treat the 'addons' directory as a package.\n")
# The original content was "from plugins import *\n\nbot = ultroid_bot"
# This wildcard import is generally discouraged.
# If addons need access to `bot` or `plugins`, they should import them directly or be passed context.
# For now, keeping it minimal. If addons rely on this, it's a deeper refactor.
f.write("from .. import ultroid_bot as bot\n") # Make bot instance available if needed
f.write("from ..plugins import *\n") # If addons truly need this, but it's a lot.
except IOError as e:
LOGS.error("Failed to create %s: %s", addons_init_py, e)
# Decide if to proceed without it. Some loading mechanisms might fail.
LOGS.info("• Loading Plugins from Plugin Channel(s) •")
for channel_identifier in plugin_channels: # Renamed 'chat' to 'channel_identifier' for clarity
LOGS.info("• • • • Processing channel: %s", channel_identifier)
try:
# Ensure channel_identifier is valid for get_entity
if isinstance(channel_identifier, str) and (channel_identifier.isdigit() or \
(channel_identifier.startswith("-") and channel_identifier[1:].isdigit())):
entity = int(channel_identifier)
else:
entity = channel_identifier
async for message_item in ultroid_bot.iter_messages(
entity, search=".py", filter=InputMessagesFilterDocument, wait_time=10
):
if not hasattr(message_item, 'file') or not message_item.file or not hasattr(message_item.file, 'name'):
LOGS.warning("Skipping message without valid file attribute or name in channel %s", channel_identifier)
continue
# Sanitize plugin filename
safe_filename = message_item.file.name.replace("_", "-").replace("|", "-").replace("/", "-").replace("\\", "-")
plugin_path = os.path.join(addons_dir, safe_filename)
if os.path.exists(plugin_path):
LOGS.info("Plugin %s already exists. Skipping download.", plugin_path)
continue # Or implement an update mechanism
await asyncio.sleep(0.6) # Rate limiting?
if message_item.text and message_item.text.strip() == "#IGNORE":
LOGS.info("Ignoring plugin %s as per #IGNORE tag.", safe_filename)
continue
downloaded_path = None
try:
downloaded_path = await message_item.download_media(file=plugin_path)
if downloaded_path:
LOGS.info("Downloaded plugin: %s", downloaded_path)
load_addons(downloaded_path) # Assuming load_addons handles its own exceptions for loading
else:
LOGS.error("Failed to download plugin %s from %s", safe_filename, channel_identifier)
except Exception as load_err: # Catch errors from download or load_addons
LOGS.error("Error processing plugin %s from %s: %s", safe_filename, channel_identifier, load_err, exc_info=False)
if downloaded_path and os.path.exists(downloaded_path):
try:
os.remove(downloaded_path) # Clean up failed download
except OSError as e_rm:
LOGS.error("Failed to remove corrupted plugin %s: %s", downloaded_path, e_rm)
except ValueError as ve:
LOGS.error("Invalid channel identifier '%s' for plugin loading: %s", channel_identifier, ve)
except Exception as er: # Catch errors like channel not found, permissions etc.
LOGS.error("Error iterating messages in plugin channel %s: %s", channel_identifier, er, exc_info=True)
async def ready():
from .. import asst, udB, ultroid_bot
log_channel_id_str = udB.get_key("LOG_CHANNEL")
log_channel_id = None
if log_channel_id_str:
try:
log_channel_id = int(log_channel_id_str)
except ValueError:
LOGS.warning("LOG_CHANNEL ID '%s' is not an integer. Cannot send ready message.", log_channel_id_str)
# Fallback to PM or skip? For now, skip if not valid int.
log_channel_id = ultroid_bot.me.id # Fallback to self/PM
spam_sent_msg = None
photo_to_send = None
buttons_to_send = None
if not udB.get_key("INIT_DEPLOY"): # Detailed Message at Initial Deploy
msg_text = """🎇 **Thanks for Deploying Ultroid Userbot!**
• Here, are the Some Basic stuff from, where you can Know, about its Usage."""
photo_to_send = "https://graph.org/file/54a917cc9dbb94733ea5f.jpg"
buttons_to_send = Button.inline("• Click to Start •", "initft_2")
udB.set_key("INIT_DEPLOY", "Done")
else:
msg_text = (
f"**Ultroid has been deployed!**\n"
f"\n"
f"**UserMode**: {inline_mention(ultroid_bot.me)}\n"
f"**Assistant**: @{asst.me.username}\n"
f"\n"
f"**Support**: @TeamUltroid\n"
f""
)
prev_spam_id = udB.get_key("LAST_UPDATE_LOG_SPAM")
if prev_spam_id and log_channel_id:
try:
await ultroid_bot.delete_messages(log_channel_id, int(prev_spam_id))
except Exception as e_del: # Catch specific errors like MessageDeleteForbiddenError
LOGS.warning("Error deleting previous update message %s: %s", prev_spam_id, e_del)
update_available = await updater() # Assuming updater() returns a boolean or relevant info
if update_available: # This might need adjustment based on what updater() returns
buttons_to_send = Button.inline("Update Available", "updtavail")
if log_channel_id: # Only try to send if we have a valid log_channel_id
try:
spam_sent_msg = await asst.send_message(
log_channel_id, msg_text, file=photo_to_send, buttons=buttons_to_send
)
except (ValueError, TypeError) as ve_te: # Catch errors if log_channel_id is invalid for sending
LOGS.warning("Failed to send 'ready' message to LOG_CHANNEL %s (ValueError/TypeError): %s. Trying with main client to PM.", log_channel_id, ve_te)
try: # Fallback to main client sending to self if assistant fails or channel invalid
spam_sent_msg = await ultroid_bot.send_message(ultroid_bot.me.id, msg_text, file=photo_to_send, buttons=buttons_to_send)
except Exception as e_fallback:
LOGS.error("Fallback 'ready' message to PM also failed: %s", e_fallback)
except Exception as e_send: # Catch other Telethon errors
LOGS.error("Failed to send 'ready' message to LOG_CHANNEL %s: %s", log_channel_id, e_send)
# Fallback to PM may also be useful here
try:
spam_sent_msg = await ultroid_bot.send_message(ultroid_bot.me.id, msg_text, file=photo_to_send, buttons=buttons_to_send)
except Exception as e_fallback_pm:
LOGS.error("Fallback 'ready' message to PM (after general error) also failed: %s", e_fallback_pm)
if spam_sent_msg and not spam_sent_msg.media: # Only store ID if message sent and no media (or adjust logic)
udB.set_key("LAST_UPDATE_LOG_SPAM", spam_sent_msg.id)
try:
await ultroid_bot(JoinChannelRequest("TheUltroid"))
LOGS.info("Successfully joined @TheUltroid channel.")
except Exception as er: # Catch specific errors like UserAlreadyParticipantError, ChannelsTooMuchError
LOGS.warning("Could not join @TheUltroid channel: %s", er)
async def WasItRestart(udb):
restart_key = udb.get_key("_RESTART")
if not restart_key:
return
from .. import asst, ultroid_bot
LOGS.info("Processing restart message confirmation...")
try:
data_parts = restart_key.split("_")
if len(data_parts) < 3:
LOGS.error("Invalid _RESTART key format: %s", restart_key)
udb.del_key("_RESTART")
return
client_type, chat_id_str, msg_id_str = data_parts[0], data_parts[1], data_parts[2]
chat_id = int(chat_id_str)
msg_id = int(msg_id_str)
target_client = asst if client_type == "bot" else ultroid_bot
await target_client.edit_message(chat_id, msg_id, "__Restarted Successfully.__")
LOGS.info("Successfully edited restart confirmation message.")
except ValueError:
LOGS.error("Invalid chat_id or msg_id in _RESTART key: %s", restart_key)
except Exception as er: # Catch specific Telethon errors if possible
LOGS.error("Failed to edit restart message: %s", er, exc_info=True)
finally:
udb.del_key("_RESTART") # Always remove the key
def _version_changes(udb):
for _ in [
"BOT_USERS",
"BOT_BLS",
"VC_SUDOS",
"SUDOS",
"CLEANCHAT",
"LOGUSERS",
"PLUGIN_CHANNEL",
"CH_SOURCE",
"CH_DESTINATION",
"BROADCAST",
]:
key = udb.get_key(_)
if key and str(key)[0] != "[":
key = udb.get(_)
new_ = [
int(z) if z.isdigit() or (z.startswith("-") and z[1:].isdigit()) else z
for z in key.split()
]
udb.set_key(_, new_)
async def enable_inline(ultroid_bot, username):
bf = "BotFather"
await ultroid_bot.send_message(bf, "/setinline")
await asyncio.sleep(1)
await ultroid_bot.send_message(bf, f"@{username}")
await asyncio.sleep(1)
await ultroid_bot.send_message(bf, "Search")
await ultroid_bot.send_read_acknowledge(bf)