diff --git a/addons/afk.py b/addons/afk.py index 74e06eb..81b6b78 100644 --- a/addons/afk.py +++ b/addons/afk.py @@ -5,30 +5,21 @@ # PLease read the GNU Affero General Public License in # . -from . import get_help - -__doc__ = get_help("help_afk") - - import asyncio from telethon import events -from pyUltroid.dB.afk_db import add_afk, del_afk, is_afk -from pyUltroid.dB.base import KeyManager +from pyUltroid import asst, ultroid_bot +from pyUltroid.config import LOG_CHANNEL, NOSPAM_CHAT +from pyUltroid.db.afk_db import add_afk, del_afk, is_afk +from pyUltroid.db.base import KeyManager +from pyUltroid.fns.decorators import ultroid_cmd +from pyUltroid.fns.helper import get_help, get_string +from pyUltroid.fns.tools import mediainfo, upload_file +from pyUltroid.udB import udB + +__doc__ = get_help("help_afk") -from . import ( - LOG_CHANNEL, - NOSPAM_CHAT, - Redis, - asst, - get_string, - mediainfo, - udB, - ultroid_bot, - ultroid_cmd, - upload_file -) old_afk_msg = [] @@ -93,7 +84,11 @@ async def set_afk(event): async def remove_afk(event): - if event.is_private and udB.get_key("PMSETTING") and not is_approved(event.chat_id): + if ( + event.is_private + and udB.get_key("PMSETTING") + and not is_approved(event.chat_id) + ): return elif "afk" in event.text.lower(): return @@ -114,7 +109,11 @@ async def remove_afk(event): async def on_afk(event): - if event.is_private and Redis("PMSETTING") and not is_approved(event.chat_id): + if ( + event.is_private + and udB.get_key("PMSETTING") + and not is_approved(event.chat_id) + ): return elif "afk" in event.text.lower(): return diff --git a/plugins/bot.py b/plugins/bot.py index 91a02a6..85fd9ca 100644 --- a/plugins/bot.py +++ b/plugins/bot.py @@ -13,6 +13,7 @@ import os import re import sys import time +import asyncio from platform import python_version as pyver from random import choice @@ -378,6 +379,29 @@ async def get_updates(ulttext, repo_url): return repo, True, changelog +def launch_update_script(repo_url=None): + """Launch the external update script and shutdown the bot.""" + import subprocess + import sys + + script_path = "update_script.py" + + # Prepare command arguments + cmd = [sys.executable, script_path] + if repo_url: + cmd.append(repo_url) + + # Add original start arguments so the script knows how to restart + if len(sys.argv) > 1: + cmd.extend(sys.argv[1:]) + + # Launch the update script + subprocess.Popen(cmd, cwd=os.getcwd()) + + # Shutdown the bot + os._exit(0) + + @ultroid_cmd( pattern="update(.*)", command="update", @@ -389,12 +413,12 @@ async def updater(event): Description: Checks for updates for your userbot. • `{tr}update`: Checks for updates from your forked repo (if set), otherwise from original. -• `{tr}update now`: Forces an update from the configured repo. +• `{tr}update now`: Forces an immediate update using external script from the configured repo. • `{tr}update original`: Checks for updates from the official Ultroid repo. -• `{tr}update now original`: Forces an update from the official Ultroid repo. +• `{tr}update now original`: Forces an immediate update from the official Ultroid repo. Note: Use `{tr}setrepo ` to update from your own fork.""" - if Var.HEROKU_API and Var.HEROKU_APP_NAME: + if Var.HEROKU_API: return await event.eor( "Heroku user! Please update from Heroku dashboard.", ) @@ -411,10 +435,24 @@ Note: Use `{tr}setrepo ` to update from your own fork.""" or "https://github.com/ThePrateekBhatia/Ultroid" ) - off_repo, is_new, changelog = await get_updates( - ulttext, - repo_url=repo_url, - ) + if is_now: + # Use external script for immediate update + await ulttext.edit( + "🔄 **Starting update process...**\n\n" + f"📦 Repository: `{repo_url}`\n" + "⚡ Using external script for reliable update\n\n" + "🤖 Bot will shutdown and restart automatically after update completes." + ) + + # Wait a moment for the message to be sent + await asyncio.sleep(2) + + # Launch external update script and shutdown + launch_update_script(repo_url) + return + + # Regular update check (non-destructive) + off_repo, is_new, changelog = await get_updates(ulttext, repo_url=repo_url) if not off_repo: return @@ -422,41 +460,38 @@ Note: Use `{tr}setrepo ` to update from your own fork.""" branch = off_repo.active_branch.name if is_new: - if is_now: - await ulttext.edit("`Force updating...`") - try: - await bash(f"git config remote.upstream.url {repo_url} && git pull -f upstream {branch}") - await bash("pip3 install -r requirements.txt --break-system-packages") - call_back() - await ulttext.edit("`Update successful! Restarting...`") - os.execl(sys.executable, sys.executable, "-m", "pyUltroid") - except Exception as e: - await ulttext.edit(f"**Update failed!**\n\n**Error:**\n`{e}`") - finally: - try: - off_repo.delete_remote("upstream") - except Exception: - pass - return - + # Show update available with options + buttons = [ + [ + Button.inline("🔄 Update Now", data=f"update_now|{repo_url}"), + Button.inline("📋 View Changes", data=f"update_changelog|{repo_url}"), + ], + [Button.inline("❌ Dismiss", data="close_update")], + ] + m = await asst.send_message( udB.get_key("LOG_CHANNEL"), - changelog, - buttons=[ - Button.inline("Update Now", data=f"update_now|{repo_url}"), - Button.inline("Dismiss", data="close_update"), - ], + f"🆕 **Update Available!**\n\n" + f"📦 Repository: `{repo_url}`\n" + f"🌿 Branch: `{branch}`\n\n" + f"Use the buttons below to update or view changes.", + buttons=buttons, ) Link = m.message_link await ulttext.edit( - f'**Update available!**\n\nView changelog and update from your log channel.\n\n[View Changelog]({Link})', + f'**🆕 Update available!**\n\n' + f'📦 Repository: `{repo_url.replace(".git", "")}`\n' + f'🌿 Branch: `{branch}`\n\n' + f'[📋 View Options & Update]({Link})', parse_mode="md", link_preview=False, ) else: await ulttext.edit( - f'Your BOT is up-to-date with [{branch}].', - parse_mode="html", + f'✅ **Your bot is up-to-date!**\n\n' + f'📦 Repository: `{repo_url.replace(".git", "")}`\n' + f'🌿 Branch: `{branch}`', + parse_mode="md", link_preview=False, ) @@ -469,23 +504,56 @@ Note: Use `{tr}setrepo ` to update from your own fork.""" @callback(re.compile(b"update_now\\|(.*)")) async def update_now_callback(event): repo_url = event.data_match.group(1).decode("utf-8") - await event.edit("`Updating now...`") + await event.edit( + "🔄 **Starting update process...**\n\n" + f"📦 Repository: `{repo_url}`\n" + "⚡ Using external script for reliable update\n\n" + "🤖 Bot will shutdown and restart automatically after update completes." + ) + + # Wait a moment for the message to be sent + await asyncio.sleep(2) + + # Launch external update script and shutdown + launch_update_script(repo_url) + + +@callback(re.compile(b"update_changelog\\|(.*)")) +async def update_changelog_callback(event): + repo_url = event.data_match.group(1).decode("utf-8") + + # Get changelog try: repo = Repo() branch = repo.active_branch.name - await bash(f"git config remote.upstream.url {repo_url} || git remote add upstream {repo_url}") - await bash(f"git pull -f upstream {branch}") - await bash("pip3 install -r requirements.txt --break-system-packages") - call_back() - await event.edit("`Update successful! Restarting...`") - os.execl(sys.executable, sys.executable, "-m", "pyUltroid") - except Exception as e: - await event.edit(f"**Update failed!**\n\n**Error:**\n`{e}`") - finally: + + # Set up upstream remote try: - repo.delete_remote("upstream") - except Exception: - pass + upstream_remote = repo.remote("upstream") + upstream_remote.set_url(repo_url) + except ValueError: + upstream_remote = repo.create_remote("upstream", repo_url) + + # Fetch updates + upstream_remote.fetch(branch) + + # Generate changelog + changelog = f"**📋 Changelog for [{branch}]({repo_url.replace('.git', '')}/tree/{branch})**\n\n" + for commit in repo.iter_commits(f'{branch}..upstream/{branch}'): + changelog += f"• `{commit.summary}` by __{commit.author.name}__\n" + + # Cleanup + repo.delete_remote("upstream") + + await event.edit( + changelog, + buttons=[ + [Button.inline("🔄 Update Now", data=f"update_now|{repo_url}")], + [Button.inline("❌ Close", data="close_update")], + ], + ) + except Exception as e: + await event.edit(f"**Error getting changelog:**\n`{e}`") @callback("close_update") diff --git a/pyUltroid/fns/helper.py b/pyUltroid/fns/helper.py index 2931798..59bf32f 100644 --- a/pyUltroid/fns/helper.py +++ b/pyUltroid/fns/helper.py @@ -227,13 +227,17 @@ if run_as_module: f"{sys.executable} -m pip install --no-cache-dir -r requirements.txt" ) + @run_async - def gen_chlog(repo, diff): + def gen_chlog(repo, diff, repo_url=None): """Generate Changelogs...""" from .. import udB - UPSTREAM_REPO_URL = ( - udB.get_key("UPSTREAM_REPO") or repo.remotes[0].config_reader.get("url") - ).replace(".git", "") + if not repo_url: + UPSTREAM_REPO_URL = ( + udB.get_key("UPSTREAM_REPO") or repo.remotes[0].config_reader.get("url") + ).replace(".git", "") + else: + UPSTREAM_REPO_URL = repo_url.replace(".git", "") ac_br = repo.active_branch.name ch_log = tldr_log = "" ch = f"Ultroid {ultroid_version} updates for [{ac_br}]:" @@ -271,7 +275,7 @@ async def bash(cmd, run_code=0): # Will add in class -async def updater(): +async def updater(repo_url=None): from .. import LOGS, udB if not Repo: @@ -285,8 +289,7 @@ async def updater(): if isinstance(e, InvalidGitRepositoryError): repo = Repo.init() off_repo = ( - udB.get_key("UPSTREAM_REPO") - or "https://github.com/ThePrateekBhatia/Ultroid" + repo_url or udB.get_key("UPSTREAM_REPO") or "https://github.com/ThePrateekBhatia/Ultroid" ) if "upstream" not in repo.remotes: origin = repo.create_remote("upstream", off_repo) @@ -299,12 +302,13 @@ async def updater(): ac_br = repo.active_branch.name - off_repo = udB.get_key("UPSTREAM_REPO") or repo.remotes[0].config_reader.get("url") + if not repo_url: + repo_url = udB.get_key("UPSTREAM_REPO") or repo.remotes[0].config_reader.get("url") if "upstream" not in repo.remotes: - repo.create_remote("upstream", off_repo) + repo.create_remote("upstream", repo_url) else: - repo.remote("upstream").set_url(off_repo) + repo.remote("upstream").set_url(repo_url) ups_rem = repo.remote("upstream") @@ -314,7 +318,7 @@ async def updater(): LOGS.info(f"Failed to fetch from upstream remote: {e}") return False - changelog, tl_chnglog = await gen_chlog(repo, f"HEAD..upstream/{ac_br}") + changelog, tl_chnglog = await gen_chlog(repo, f"HEAD..upstream/{ac_br}", repo_url) return bool(changelog) diff --git a/update_script.py b/update_script.py new file mode 100644 index 0000000..5d50fce --- /dev/null +++ b/update_script.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +""" +Ultroid Update Script +This script handles updating the bot while it's not running. +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +def run_command(cmd, shell=True): + """Run a command and return success status.""" + try: + result = subprocess.run(cmd, shell=shell, capture_output=True, text=True) + print(f"Command: {cmd}") + print(f"Output: {result.stdout}") + if result.stderr: + print(f"Error: {result.stderr}") + return result.returncode == 0 + except Exception as e: + print(f"Error running command '{cmd}': {e}") + return False + +def main(): + """Main update function.""" + print("🔄 Starting Ultroid update process...") + + # Get script directory + script_dir = Path(__file__).parent.absolute() + os.chdir(script_dir) + + print(f"📁 Working directory: {script_dir}") + + # Check if we're in a git repository + if not (script_dir / ".git").exists(): + print("❌ Not a git repository. Cannot update.") + return False + + # Get the repository URL from command line args or database + repo_url = sys.argv[1] if len(sys.argv) > 1 else None + + # Fetch and pull updates + print("📥 Fetching updates from repository...") + + if repo_url: + print(f"🔗 Using repository: {repo_url}") + # Set up remote if needed + if not run_command("git remote get-url origin"): + run_command(f"git remote add origin {repo_url}") + else: + run_command(f"git remote set-url origin {repo_url}") + + # Fetch latest changes + if not run_command("git fetch origin"): + print("❌ Failed to fetch updates") + return False + + # Get current branch + result = subprocess.run("git branch --show-current", shell=True, capture_output=True, text=True) + current_branch = result.stdout.strip() or "main" + + print(f"🌿 Current branch: {current_branch}") + + # Pull updates + print("⬇️ Pulling updates...") + if not run_command(f"git pull origin {current_branch}"): + print("❌ Failed to pull updates") + return False + + # Update dependencies + print("📦 Installing/updating dependencies...") + if not run_command("pip3 install -r requirements.txt --upgrade"): + print("⚠️ Warning: Failed to update some dependencies") + + # Try alternative pip command + run_command("pip3 install -r requirements.txt --break-system-packages --upgrade") + + print("✅ Update completed successfully!") + return True + +def restart_bot(): + """Restart the bot after update.""" + print("🔄 Restarting Ultroid...") + + # Check if we have a virtual environment + venv_python = None + if os.path.exists("venv/bin/python"): + venv_python = "venv/bin/python" + elif os.path.exists("venv/Scripts/python.exe"): + venv_python = "venv/Scripts/python.exe" + + # Determine how to start the bot + if len(sys.argv) > 1 and sys.argv[-1] == "main.py": + # Started with main.py + if venv_python: + os.execv(venv_python, [venv_python, "main.py"]) + else: + os.execv(sys.executable, [sys.executable, "main.py"]) + else: + # Started as module + if venv_python: + os.execv(venv_python, [venv_python, "-m", "pyUltroid"]) + else: + os.execv(sys.executable, [sys.executable, "-m", "pyUltroid"]) + +if __name__ == "__main__": + print("🚀 Ultroid Update Script") + print("=" * 40) + + # Wait a moment for the bot to fully shutdown + time.sleep(2) + + # Perform update + if main(): + print("=" * 40) + restart_bot() + else: + print("❌ Update failed. Please check the errors above.") + sys.exit(1)