diff --git a/.env.dist b/.env.dist index 7722fb75..c8e60b89 100644 --- a/.env.dist +++ b/.env.dist @@ -16,9 +16,6 @@ STRINGSESSION={@string_session} # Set if you want to use music bot SECOND_SESSION={@second_session} -# Music handler for music bot -MUSIC_HANDLER={@music_handler} - # PM LIMIT for AntiPM plugin PM_LIMIT={@pm_limit} diff --git a/.github/workflows/cloud-image.yml b/.github/workflows/cloud-image.yml new file mode 100644 index 00000000..7b1299ec --- /dev/null +++ b/.github/workflows/cloud-image.yml @@ -0,0 +1,27 @@ +name: Cloud Docker Image CI + +on: + push: + branches: + - main +jobs: + push_to_registry: + name: Cloud Docker Image CI + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: ./ + file: ./Cloudfile + push: true + tags: qbtaumai/moonubcloud:latest-cloud diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7d3c5431..18e096f7 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -22,5 +22,6 @@ jobs: uses: docker/build-push-action@v4 with: context: ./ + file: ./Dockerfile push: true tags: qbtaumai/moonuserbot:latest diff --git a/Cloudfile b/Cloudfile new file mode 100644 index 00000000..f5d8283f --- /dev/null +++ b/Cloudfile @@ -0,0 +1,8 @@ +FROM python:3.11 +WORKDIR /app +COPY . /app +RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +RUN pip install --no-cache-dir -r requirements.txt +CMD ["bash", "cloud.sh"] \ No newline at end of file diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 00000000..c5bf3976 --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1,6 @@ +## Disclaimer +> [!WARNING] +> The use of this Telegram Userbot is entirely at your own risk. The developer of this Userbot is not responsible for any misuse, damage, or legal consequences that may arise from your use of this software. +>> It is your responsibility to ensure that you use this Userbot in accordance with all applicable laws and regulations, and that you do not engage in any activities that may cause harm to others or violate their privacy. This includes, but is not limited to, the use of this Userbot to send spam, harass others, or engage in any other form of unlawful or malicious activity. +>> The developer of this Userbot does not endorse or condone any such activities, and any such use of this software is strictly prohibited. By using this Userbot, you acknowledge that you are solely responsible for your own actions and that the developer of this Userbot shall not be held liable for any damages or consequences that may arise from your use of this software. +>> It is your responsibility to ensure that you have obtained all necessary permissions and consents before using this Userbot to interact with others, and that you respect their privacy and rights. The developer of this Userbot shall not be held liable for any breach of privacy or rights that may occur as a result of your use of this software. diff --git a/README.md b/README.md index 2acd35a7..b9a68b6e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![License](https://img.shields.io/badge/License-GPL-pink)](https://github.com/The-MoonTg-project/Moon-Userbot/blob/main/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com) -

+ ***A Simple, Fast, Customizable, Ai powered Userbot for Telegram made after Dragon-Userbot abandoned*** @@ -79,6 +79,8 @@ - **YT Video [How to deploy on Koyeb]**: +**For Detailed Guide refer to [wiki](https://github.com/The-MoonTg-project/Moon-Userbot/wiki/Installation#koyeb-free)** + ## 🐳 Docker You can either use `docker run` or `docker compose`. @@ -90,14 +92,20 @@ You can either use `docker run` or `docker compose`. - Put your environment vars in `.env` file check [.env.dist](/.env.dist) for example format ### 👷‍♂️`docker run`: +We also push images to [Docker Hub](https://hub.docker.com/), so you can use the following commands to start and update the service: - Start: + 1. If you want to use normal image: ```shell docker run --env-file ./.env -d qbtaumai/moonuserbot:latest ``` + 2. If you want to use image with flask web (only recommended for heroku/koyeb/render etc.): + ```shell + docker run --env-file ./.env -d qbtaumai/moonubcloud:latest-cloud + ``` - Updating: ```shell - docker stop $(docker ps -q) && docker rm $(docker ps -a -q) + docker stop $(docker ps -q) ``` then re-run the start command @@ -129,22 +137,10 @@ If you're using Docker Compose version 2.x, use the following commands to start > Make Sure you add appropriate env vars ## 🖥️ Local Host -## Linux, Windows [only wsl] - -### Update the packages - -```shell -sudo apt update && sudo apt upgrade -y -``` +## 🐧 Linux (WSL compatible) ### Install Git - -> [!TIP] -> Ignore if already installed - -```shell -sudo apt install git -``` +There are instructions for installing on several different Unix distributions on the Git website, at https://git-scm.com/download/linux ### Clone the repo @@ -152,15 +148,25 @@ sudo apt install git git clone https://github.com/The-MoonTg-project/Moon-Userbot.git ``` -### Setup +### Installation ```shell -cd Moon-Userbot/ && sudo bash install.sh +cd Moon-Userbot +chmod +x install.sh +./install.sh ``` +**Installer tested on:** +- Arch +- Debian +- Ubuntu +- WSL (APT based distros) + +Feel free to test on other distros and let us know! + #### 📱 Termux > [!TIP] -> use [GitHub](https://github.com/termux/termux-app/releases) version +> Use [GitHub](https://github.com/termux/termux-app/releases) version ------------------------------------------------------------------------------- > **Full Installation instruction [Given here](https://telegra.ph/Moon-Userbot-Installation---Termux-02-09)** @@ -198,15 +204,15 @@ Contributions of any type are welcome like `custom_modules` etc. Feel free to do ## Licence ```plaintext -                    GNU GENERAL PUBLIC LICENSE -                        Version 3, 29 June 2007 + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -  Copyright (C) 2007 Free Software Foundation, Inc.  -  Everyone is permitted to copy and distribute verbatim copies -  of this license document, but changing it is not allowed. + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -                             Preamble + Preamble -   The GNU General Public License is a free, copyleft license for - software and other kinds of works. + The GNU General Public License is a free, copyleft license for + software and other kinds of works. ``` diff --git a/compose.yml b/compose.yml index 86b92cbc..5c01f341 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,8 @@ services: moonuserbot: - image: qbtaumai/moonuserbot:latest + build: + context: . + dockerfile: Dockerfile container_name: moonuserbot restart: unless-stopped env_file: diff --git a/docker-compose.yml b/docker-compose.yml index cf04c726..604020bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,9 @@ version: "3" services: moonuserbot: - image: qbtaumai/moonuserbot:latest + build: + context: . + dockerfile: Dockerfile container_name: moonuserbot restart: unless-stopped env_file: diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 index 31c2e58c..fcc74427 --- a/install.sh +++ b/install.sh @@ -1,154 +1,223 @@ -#!/bin/bash -if command -v termux-setup-storage; then - echo For termux, please use https://raw.githubusercontent.com/The-MoonTg-project/Moon-Userbot/main/termux-install.sh - exit 1 -fi +#!/usr/bin/env bash + +# Define color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +INPUT='\033[1;30m' +NC='\033[0m' # No Color + +PACKAGE_MANAGER="" +# Ensure the script is run with root privileges if [[ $UID != 0 ]]; then - echo Please run this script as root + printf "${YELLOW}This script requires root privileges.${NC}\n" # skipcq + printf "Please enter the root password to continue.\n" + exec sudo "$0" "$@" +else + printf "${YELLOW}Running with root privileges${NC}\n" # skipcq +fi + +# Detect available package manager +if command -v apt &>/dev/null; then + PACKAGE_MANAGER="apt" +elif command -v apk &>/dev/null; then + PACKAGE_MANAGER="apk" +elif command -v yum &>/dev/null; then + PACKAGE_MANAGER="yum" +elif command -v pacman &>/dev/null; then + PACKAGE_MANAGER="pacman" +else + printf "${RED}Unsupported package manager. Please use a compatible distribution or update the installer script.${NC}\n" # skipcq exit 1 fi -apt update -y -apt install python3 python3-pip git ffmpeg wget gnupg -y || exit 2 +if command -v termux-setup-storage; then + printf "${RED}For termux, please use https://raw.githubusercontent.com/The-MoonTg-project/Moon-Userbot/main/termux-install.sh${NC}\n" # skipcq + exit 1 +fi -su -c "python3 -m pip install -U pip" $SUDO_USER -su -c "python3 -m pip install -U wheel pillow" $SUDO_USER +# Install necessary packages based on detected package manager +case "$PACKAGE_MANAGER" in +apt) + apt update -y + apt install python3 python3-venv git wget -y || exit 2 + ;; +apk) + apk update + apk add python3 py3-virtualenv git wget || exit 2 # Packages here may be wrong, to verify + ;; +yum) + yum update -y + yum install python3 python3-venv git wget -y || exit 2 # Packages here may be wrong, to verify + ;; +pacman) + pacman -S --noconfirm python python-virtualenv git wget || exit 2 + ;; +esac -if [[ -d "Moon-Userbot" ]]; then - cd Moon-Userbot -elif [[ -f ".env.dist" ]] && [[ -f "main.py" ]] && [[ -d "modules" ]]; then - : +# Clone repository if not exists +if [[ -d "Moon-Userbot" && "$(basename "$PWD")" != "Moon-Userbot" ]]; then + cd Moon-Userbot || exit 2 +elif [[ "$(basename "$PWD")" == "Moon-Userbot" && -f ".env.dist" && -f "main.py" && -d "modules" ]]; then + printf "${BLUE}Already inside the Moon-Userbot repo, proceeding...${NC}\n" # skipcq else git clone https://github.com/The-MoonTg-project/Moon-Userbot || exit 2 cd Moon-Userbot || exit 2 fi if [[ -f ".env" ]] && [[ -f "my_account.session" ]]; then - echo "It seems that Moon-Userbot is already installed. Exiting..." + printf "${GREEN}It seems that Moon-Userbot is already installed. Exiting...${NC}\n" # skipcq exit fi -su -c "python3 -m pip install -U -r requirements.txt" $SUDO_USER || exit 2 +# Prompt user if they want to proceed with creating a virtual environment +printf "${YELLOW}It's recommended to use a virtual environment for Python projects.${NC}\n" # skipcq +printf "Note: If your drive resources are very limited, you might consider not creating a virtual environment, but it shouldn't be rejected otherwise unless you know what you're doing.\n" +printf "If you're unsure, it's better to create a virtual environment.\n" +printf "${INPUT}Would you like to create a virtual environment? (Y/n)${NC} > " # skipcq +read -r create_venv + +if [[ "$create_venv" != "n" ]] && [[ "$create_venv" != "N" ]]; then + # Create a virtual environment inside the cloned repository and activate it + python3 -m venv venv + . venv/bin/activate + + # Upgrade pip and install wheel and pillow + pip install -U pip wheel pillow +fi + +if [ -d ".venv" ]; then + . .venv/bin/activate +elif [ -d "venv" ]; then + . venv/bin/activate +fi -echo -echo "Enter API_ID and API_HASH" -echo "You can get it here -> https://my.telegram.org/" -echo "Leave empty to use defaults (please note that default keys significantly increases your ban chances)" +# Install Python requirements +pip install -U -r requirements.txt || exit 2 +# Prompt for API_ID and API_HASH +printf "Enter API_ID and API_HASH\n" +printf "You can get it here -> https://my.telegram.org/\n" +printf "Leave empty to use defaults (please note that using default keys is a ${RED}very bad idea${NC} and significantly increases your ban chances)\n" # skipcq read -r -p "API_ID > " api_id +# Default API_ID and API_HASH if [[ $api_id = "" ]]; then - api_id="2040" - api_hash="b18441a1ff607e10a989891a5462e627" + printf "${RED}You have chosen to use the default API_ID and API_HASH, which is strongly discouraged.${NC}\n" # skipcq + printf "${YELLOW}Please type${NC} '${BLUE}I agree${NC}'${YELLOW} to confirm that you understand the risks and still wish to proceed.${NC}\n" # skipcq + read -r -p "Confirmation > " confirmation + if [[ $confirmation = "I agree" ]]; then + api_id="2040" + api_hash="b18441a1ff607e10a989891a5462e627" + else + printf "${RED}Confirmation not provided. Exiting...${NC}\n" # skipcq + exit 1 + fi else read -r -p "API_HASH > " api_hash fi - -echo -echo "SET PM PERMIT warn limit" +# Prompt for PM PERMIT warn limit +# PM PERMIT warn limit is the number of messages a user can receive from others before giving them a warning, requires `antipm` plugin to be enabled +printf "SET PM PERMIT warn limit\n" +# Now below is more clear version: +printf "The number of messages others can send you before receiving a warning, and eventually a ban or leave empty for default (3), requires antipm plugin to be enabled\n" read -r -p "PM_LIMIT warn limit > " pm_limit if [[ $pm_limit = "" ]]; then pm_limit="3" - echo "limit not provided by user set to default" + printf "Limit not provided by user; set to default\n" fi -echo -echo "Do you want to use musicbot? (y/n)" -read -r -p "MUSIC_BOT > " musicbot +# Prompt for musicbot usage +printf "Do you want to use musicbot? (y/N)" +read -r musicbot if [[ $musicbot = "y" ]]; then - echo - echo "Enter SECOND_SESSION_STRING to be used by musicbot" + printf "Enter SECOND_SESSION_STRING to be used by musicbot\n" read -r -p "SECOND_SESSION > " second_session if [[ $second_session = "" ]]; then - echo "SECOND_SESSION not provided by user" + printf "SECOND_SESSION not provided by user\n" second_session="" - else - echo - echo "Please provide handler to be used by msuicbot" - read -r -p "MUSIC_HANDLER > " music_handler - if [[ $music_handler = "" ]]; then - echo "MUSIC_HANDLER not provided by user" - music_handler="" - fi fi fi -echo -echo "Enter APIFLASH_KEY for webshot plugin" -echo "You can get it here -> https://apiflash.com/dashboard/access_keys" +# Prompt for various API keys +printf "Enter APIFLASH_KEY for webshot plugin\n" +printf "You can get it here -> https://apiflash.com/dashboard/access_keys\n" read -r -p "APIFLASH_KEY > " apiflash_key if [[ $apiflash_key = "" ]]; then - echo "NOTE: API Not set you'll get errors with webshot & ws module" + printf "NOTE: API Not set; you'll get errors with webshot & ws module\n" fi -echo -echo "Enter RMBG_KEY for remove background module" -echo "You can get it here -> https://www.remove.bg/dashboard#api-key" +printf "Enter RMBG_KEY for remove background module\n" +printf "You can get it here -> https://www.remove.bg/dashboard#api-key\n" read -r -p "RMBG_KEY > " rmbg_key if [[ $rmbg_key = "" ]]; then - echo "NOTE: API Not set you'll not be able to use remove background modules" + printf "NOTE: API Not set; you'll not be able to use remove background modules\n" fi -echo -echo "Enter VT_KEY for VirusTotal" -echo "You can get it here -> https://www.virustotal.com/" +printf "Enter VT_KEY for VirusTotal\n" +printf "You can get it here -> https://www.virustotal.com/\n" read -r -p "VT_KEY > " vt_key if [[ $vt_key = "" ]]; then - echo "NOTE: API Not set you'll not be able to use VirusTotal module" + printf "NOTE: API Not set; you'll not be able to use VirusTotal module\n" fi -echo -echo "Enter GEMINI_KEY if you want to use AI" -echo "You can get it here -> https://makersuite.google.com/app/apikey" +printf "Enter GEMINI_KEY if you want to use AI\n" +printf "You can get it here -> https://makersuite.google.com/app/apikey\n" read -r -p "GEMINI_KEY > " gemini_key if [[ $gemini_key = "" ]]; then - echo "NOTE: API Not set you'll not be able to use Gemini AI modules" + printf "NOTE: API Not set; you'll not be able to use Gemini AI modules\n" fi -echo -echo "Enter COHERE_KEY if you want to use AI" -echo "You can get it here -> https://dashboard.cohere.com/api-keys" +printf "Enter COHERE_KEY if you want to use AI" +printf "You can get it here -> https://dashboard.cohere.com/api-keys\n" read -r -p "COHERE_KEY > " cohere_key if [[ $cohere_key = "" ]]; then - echo "NOTE: API Not set you'll not be able to use Coral AI modules" + printf "NOTE: API Not set; you'll not be able to use Coral AI modules\n" fi -echo -echo "Enter VCA_API_KEY for aiutils" -echo "Learn How to Get One --> https://github.com/VisionCraft-org/VisionCraft?tab=readme-ov-file#obtaining-an-api-key" +printf "Enter VCA_API_KEY for aiutils\n" +printf "Learn How to Get One --> https://github.com/VisionCraft-org/VisionCraft?tab=readme-ov-file#obtaining-an-api-key\n" read -r -p "VCA_API_KEY > " vca_api_key if [[ $vca_api_key = "" ]]; then - echo "NOTE: API Not set you'll not be able to use aiutils module/pligins" + printf "NOTE: API Not set; you'll not be able to use aiutils module/plugins\n" fi -echo -echo "Choose database type:" -echo "[1] MongoDB db_url" -echo "[2] MongoDB localhost" -echo "[3] Sqlite (default)" -read -r -p "> " db_type +while true; do + # Prompt for database type and database URL if MongoDB is selected + printf "${YELLOW}Choose database type:${NC}\n" # skipcq + printf "[1] MongoDB db_url\n" + printf "[2] MongoDB localhost\n" + printf "[3] Sqlite (default)\n" + read -r -p "> " db_type -echo -case $db_type in + case $db_type in 1) - echo "Please enter db_url" - echo "You can get it here -> https://mongodb.com/atlas" + printf "Please enter db_url\n" + printf "You can get it here -> https://mongodb.com/atlas\n" read -r -p "> " db_url db_name=Moon_Userbot db_type=mongodb + break ;; 2) + if ! command -v apt &>/dev/null; then + printf "This option requires apt package manager, which is not available on your system.\n" + printf "Please choose a different database type.\n" + continue + fi + if systemctl status mongodb; then wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add - source /etc/os-release - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${UBUNTU_CODENAME}/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list + printf "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu %s/mongodb-org/5.0 multiverse\n" "${UBUNTU_CODENAME}" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list apt update apt install mongodb -y systemctl daemon-reload @@ -159,14 +228,21 @@ case $db_type in db_url=mongodb://localhost:27017 db_name=Moon_Userbot db_type=mongodb + break ;; - *) + 3) db_name=db.sqlite3 db_type=sqlite3 + break ;; -esac + *) + printf "${RED}Invalid choice!${NC}\n" # skipcq + ;; + esac +done -cat > .env << EOL +# Generate .env file with collected variables +cat >.env < " install_type +# Configure the bot based on selected installation type +while true; do + # Prompt for installation type and execute accordingly + printf "${YELLOW}Choose installation type:${NC}\n" # skipcq + printf "[1] PM2\n" + printf "[2] Systemd service\n" + printf "[3] Custom (default)\n" + read -r -p "> " install_type -su -c "python3 install.py ${install_type}" $SUDO_USER || exit 3 - -case $install_type in + case $install_type in 1) - if ! command -v pm2; then + if ! command -v apt &>/dev/null; then + printf "This option requires apt package manager, which is not available on your system.\n" + printf "Please choose a different installation type.\n" + continue + fi + + if ! command -v pm2 &>/dev/null; then curl -fsSL https://deb.nodesource.com/setup_17.x | bash apt install nodejs -y npm install pm2 -g @@ -210,17 +292,17 @@ case $install_type in su -c "pm2 start main.py --name Moon --interpreter python3" $SUDO_USER su -c "pm2 save" $SUDO_USER - echo - echo "============================" - echo "Great! Moon-Userbot installed successfully and running now!" - echo "Installation type: PM2" - echo "Start with: \"pm2 start Moon\"" - echo "Stop with: \"pm2 stop Moon\"" - echo "Process name: Moon" - echo "============================" + printf "${GREEN}============================\\n" # skipcq + printf "Great! Moon-Userbot installed successfully and running now!\n" + printf "Installation type: PM2\n" + printf "Start with: \"pm2 start Moon\"\n" + printf "Stop with: \"pm2 stop Moon\"\n" + printf "Process name: Moon\n" + printf "============================${NC}\n" # skipcq + break ;; 2) - cat > /etc/systemd/system/Moon.service << EOL + cat >/etc/systemd/system/Moon.service <Beep boop. This is an automated message.\n" - f"I am not available right now.\n" - f"Last seen: {last_seen}\n" - f"Reason: {AFK_REASON.upper()}\n" - f"See you after I'm done doing whatever I'm doing." - ) + text = db.get("core.afk", "afk_msg", None) + if text is None: + text = ( + f"Beep boop. This is an automated message.\n" + f"I am not available right now.\n" + f"Last seen: {last_seen}\n" + f"Reason: {AFK_REASON.upper()}\n" + f"See you after I'm done doing whatever I'm doing." + ) + else: + last_seen = last_seen.replace("ago", "").strip() + text = text.format(last_seen=last_seen, reason=AFK_REASON) await bot.send_message( chat_id=GetChatID(message), text=text, @@ -144,6 +150,39 @@ async def afk_unset(_, message: Message): await message.delete() +@Client.on_message(filters.command("setafkmsg", prefix) & filters.me, group=3) +async def set_afk_msg(_, message: Message): + if not message.reply_to_message: + return await message.edit("Reply to a message to set it as your AFK message.") + + msg = message.reply_to_message + afk_msg = msg.text or msg.caption + + if not afk_msg: + return await message.edit( + "Reply to a text or caption message to set it as your AFK message." + ) + + if len(afk_msg) > 200: + return await message.edit( + "AFK message is too long. It should be less than 200 characters." + ) + if "{reason}" not in afk_msg: + return await message.edit( + "AFK message should contain {reason} to indicate where the reason will be placed." + ) + if "{last_seen}" not in afk_msg: + return await message.edit( + "AFK message should contain {last_seen} to indicate where the last seen time will be placed." + ) + + old_afk_msg = db.get("core.afk", "afk_msg", None) + if old_afk_msg: + db.remove("core.afk", "afk_msg") + db.set("core.afk", "afk_msg", afk_msg) + await message.edit(f"AFK message set to:\n\n{afk_msg}") + + @Client.on_message(filters.me, group=3) async def auto_afk_unset(_, message: Message): global AFK, AFK_TIME, AFK_REASON, USERS, GROUPS @@ -166,4 +205,5 @@ async def auto_afk_unset(_, message: Message): modules_help["afk"] = { "afk [reason]": "Go to AFK mode with reason as anything after .afk\nUsage: .afk ", "unafk": "Get out of AFK", + "setafkmsg [reply to message]*": "Set your AFK message. Use {reason} and {last_seen} to indicate where the reason and last seen time will be placed.", } diff --git a/modules/antipm.py b/modules/antipm.py index be51996c..6823d869 100644 --- a/modules/antipm.py +++ b/modules/antipm.py @@ -52,14 +52,18 @@ async def anti_pm_handler(client: Client, message: Message): user = await client.get_users(ids) u_f = user.first_name user_info = await client.resolve_peer(ids) - default_text = f"""Hello, {u_f}! + default_text = db.get("core.antipm", "antipm_msg", None) + if default_text is None: + default_text = f"""Hello, {u_f}! This is the Assistant Of {u_n}. My Boss is away or busy as of now, You can wait for him to respond. Do not spam further messages else I may have to block you! This is an automated message by the assistant. Currently You Have {warns} Warnings. -""" + """ + else: + default_text = default_text.format(user=u_f, my_name=u_n, warns=warns) if db.get("core.antipm", "spamrep", False): await client.invoke(functions.messages.ReportSpam(peer=user_info)) if db.get("core.antipm", "block", False): @@ -179,10 +183,52 @@ async def del_contact(_, message: Message): await message.edit("User DisApproved!") +@Client.on_message(filters.command(["setantipmmsg", "sam"], prefix) & filters.me) +async def set_antipm_msg(_, message: Message): + if not message.reply_to_message: + return await message.edit( + "Reply to a message to set it as your antipm message." + ) + + msg = message.reply_to_message + afk_msg = msg.text or msg.caption + + if not afk_msg: + return await message.edit( + "Reply to a text or caption message to set it as your antipm message." + ) + + if len(afk_msg) > 200: + return await message.edit( + "antipm message is too long. It should be less than 200 characters." + ) + + if "{user}" not in afk_msg: + return await message.edit( + "antipm message must contain {user} to mention the user." + ) + if "{my_name}" not in afk_msg: + return await message.edit( + "antipm message must contain {my_name} to mention your name." + ) + if "{warns}" not in afk_msg: + return await message.edit( + "antipm message must contain {warns} to mention the warns count." + ) + + old_afk_msg = db.get("core.antipm", "antipm_msg", None) + if old_afk_msg: + db.remove("core.antipm", "antipm_msg") + db.set("core.antipm", "antipm_msg", afk_msg) + await message.edit(f"antipm message set to:\n\n{afk_msg}") + + modules_help["antipm"] = { "antipm [enable|disable]*": "Enable Pm permit", "antipm_report [enable|disable]*": "Enable spam reporting", "antipm_block [enable|disable]*": "Enable user blocking", + "setantipmmsg [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.", + "sam [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.", "a": "Approve User", "d": "DisApprove User", } diff --git a/modules/help.py b/modules/help.py index a0edcbb9..7c0ff3de 100644 --- a/modules/help.py +++ b/modules/help.py @@ -21,13 +21,13 @@ total_pages = 0 -async def send_page(message, module_list, page, total_pages): +async def send_page(message, module_list, page, total_page): start_index = (page - 1) * 10 end_index = start_index + 10 page_modules = module_list[start_index:end_index] - text = f"Help for Moon-Userbot\n" + text = "Help for Moon-Userbot\n" text += f"For more help on how to use a command, type {prefix}help [module]\n\n" - text += f"Page {page}/{total_pages}\n\n" + text += f"Help Page No: {page}/{total_page}\n\n" for module_name in page_modules: commands = modules_help[module_name] text += f"• {module_name.title()}: {', '.join([f'{prefix + cmd_name.split()[0]}' for cmd_name in commands.keys()])}\n" @@ -68,7 +68,7 @@ async def help_cmd(_, message: Message): @Client.on_message(filters.command(["pn", "pp", "pq"], prefix) & filters.me) @with_reply async def handle_navigation(_, message: Message): - if message.reply_to_message: + if message.reply_to_message and "Help Page No:" in message.reply_to_message.text: global current_page if message.command[0].lower() == "pn": if current_page < total_pages: @@ -77,8 +77,7 @@ async def handle_navigation(_, message: Message): message, list(modules_help.keys()), current_page, total_pages ) return await message.reply_to_message.delete() - else: - await message.edit("No more pages available.") + await message.edit("No more pages available.") elif message.command[0].lower() == "pp": if current_page > 1: current_page -= 1 @@ -86,8 +85,7 @@ async def handle_navigation(_, message: Message): message, list(modules_help.keys()), current_page, total_pages ) return await message.reply_to_message.delete() - else: - return await message.edit("This is the first page.") + return await message.edit("This is the first page.") elif message.command[0].lower() == "pq": await message.reply_to_message.delete() return await message.edit("Help closed.") diff --git a/modules/loader.py b/modules/loader.py index 7c5ad3b6..56ba1e0d 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -29,6 +29,22 @@ BASE_PATH = os.path.abspath(os.getcwd()) +CATEGORIES = [ + "ai", + "dl", + "admin", + "anime", + "fun", + "images", + "info", + "misc", + "music", + "news", + "paste", + "rev", + "tts", + "utils", +] @Client.on_message(filters.command(["modhash", "mh"], prefix) & filters.me) @@ -73,25 +89,31 @@ async def loadmod(_, message: Message): module_name = url.lower() url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{module_name}.py" else: - modules_hashes = requests.get( - "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt" - ).text - resp = requests.get(url) - - if not resp.ok: - await message.edit( - f"Troubleshooting with downloading module {url}", - ) - return - - if hashlib.sha256(resp.content).hexdigest() not in modules_hashes: - return await message.edit( - "Only " - "verified modules or from the official " - "" - "custom_modules repository are supported!", - disable_web_page_preview=True, - ) + if "/" in url: + for category in CATEGORIES: + if url.startswith(f"{category}/"): + url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{url}.py" + break + else: + modules_hashes = requests.get( + "https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/modules_hashes.txt" + ).text + resp = requests.get(url) + + if not resp.ok: + await message.edit( + f"Troubleshooting with downloading module {url}", + ) + return + + if hashlib.sha256(resp.content).hexdigest() not in modules_hashes: + return await message.edit( + "Only " + "verified modules or from the official " + "" + "custom_modules repository are supported!", + disable_web_page_preview=True, + ) module_name = url.split("/")[-1].split(".")[0] diff --git a/modules/safone.py b/modules/safone.py deleted file mode 100644 index fe9c188e..00000000 --- a/modules/safone.py +++ /dev/null @@ -1,517 +0,0 @@ -# Moon-Userbot - telegram userbot -# Copyright (C) 2020-present Moon Userbot Organization -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import requests -import aiofiles -import base64 - -from pyrogram import Client, enums, filters -from pyrogram.types import Message, InputMediaPhoto -from pyrogram.errors import MediaCaptionTooLong, MessageTooLong - -from utils.misc import prefix, modules_help -from utils.scripts import format_exc, make_carbon - -url = "https://api.safone.co" - -headers = { - "Accept-Language": "en-US,en;q=0.9", - "Connection": "keep-alive", - "DNT": "1", - "Referer": "https://api.safone.dev/docs", - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-origin", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", - "accept": "application/json", - "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"Linux"', -} - - -async def telegraph(title, user_name, content): - formatted_content = "
".join(content.split("\n")) - formatted_content = "

" + formatted_content + "

" - - data = {"title": title, "content": formatted_content, "author_name": user_name} - - response = requests.post( - url=f"{url}/telegraph/text", headers=headers, json=data, timeout=5 - ) - - result = response.json() - - return result["url"] - - -async def voice_characters(): - response = requests.get(url=f"{url}/speech/characters", headers=headers, timeout=5) - - result = response.json() - - return ", ".join(result["characters"]) - - -async def make_rayso(code: str, title: str, theme: str): - data = { - "code": code, - "title": title, - "theme": theme, - "padding": 64, - "language": "auto", - "darkMode": False, - } - response = requests.post(f"{url}/rayso", data=data, headers=headers) - if response.status_code != 200: - return None - result = response.json() - try: - if result["error"] is not None: - return None - except KeyError: - pass - image_data = result["image"] - file_name = "rayso.png" - with open(file_name, "wb") as f: - f.write(base64.b64decode(image_data)) - return file_name - - -@Client.on_message(filters.command("asq", prefix) & filters.me) -async def asq(_, message: Message): - if len(message.command) > 1: - query = message.text.split(maxsplit=1)[1] - else: - await message.edit_text("Query not provided!") - return - await message.edit_text("Processing...") - response = requests.get(url=f"{url}/asq?query={query}", headers=headers, timeout=5) - if response.status_code != 200: - await message.edit_text("Something went wrong!") - return - - result = response.json() - - ans = result["answer"] - await message.edit_text( - f"Q. {query}\n A. {ans}", parse_mode=enums.ParseMode.MARKDOWN - ) - - -@Client.on_message(filters.command("sgemini", prefix) & filters.me) -async def sgemini(_, message: Message): - if len(message.command) > 1: - prompt = message.text.split(maxsplit=1)[1] - else: - await message.edit_text("prompt not provided!") - return - await message.edit_text("Processing...") - response = requests.get(url=f"{url}/bard?query={prompt}", headers=headers) - if response.status_code != 200: - await message.edit_text("Something went wrong!") - return - - result = response.json() - - ans = result["message"] - await message.edit_text( - f"Prompt: {prompt}\n Ans: {ans}", parse_mode=enums.ParseMode.MARKDOWN - ) - - -@Client.on_message(filters.command("app", prefix) & filters.me) -async def app(client: Client, message: Message): - try: - chat_id = message.chat.id - await message.edit_text("Processing...") - if len(message.command) > 1: - query = message.text.split(maxsplit=1)[1] - else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) - - response = requests.get( - url=f"{url}/apps?query={query}&limit=1", headers=headers, timeout=5 - ) - if response.status_code != 200: - await message.edit_text("Something went wrong") - return - - result = response.json() - - try: - coverImage_url = result["results"][0]["icon"] - coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: - await f.write(coverImage) - - except Exception: - coverImage = None - - description = result["results"][0]["description"] - developer = result["results"][0]["developer"] - IsFree = result["results"][0]["free"] - genre = result["results"][0]["genre"] - package_name = result["results"][0]["id"] - title = result["results"][0]["title"] - price = result["results"][0]["price"] - link = result["results"][0]["link"] - rating = result["results"][0]["rating"] - - await message.delete() - await client.send_media_group( - chat_id, - [ - InputMediaPhoto( - "coverImage.jpg", - caption=f"Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}", - ) - ], - ) - - except MediaCaptionTooLong: - description = description[:850] - await message.delete() - await client.send_media_group( - chat_id, - [ - InputMediaPhoto( - "coverImage.jpg", - caption=f"Title: {title}\nRating: {rating}\nIsFree: {IsFree}\nPrice: {price}\nPackage Name: {package_name}\nGenres: {genre}\nDeveloper: {developer}\nDescription: {description}\nLink: {link}", - ) - ], - ) - except Exception as e: - await message.edit_text(format_exc(e)) - finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") - - -@Client.on_message(filters.command("tsearch", prefix) & filters.me) -async def tsearch(client: Client, message: Message): - try: - chat_id = message.chat.id - limit = 10 - await message.edit_text("Processing...") - if len(message.command) > 1: - query = message.text.split(maxsplit=1)[1] - else: - message.edit_text( - "What should i search? You didn't provided me with any value to search" - ) - - response = requests.get( - url=f"{url}/torrent?query={query}&limit={limit}", headers=headers - ) - if response.status_code != 200: - await message.edit_text("Something went wrong") - return - - result = response.json() - - coverImage_url = result["results"][0]["thumbnail"] - description = result["results"][0]["description"] - genre = result["results"][0]["genre"] - category = result["results"][0]["category"] - title = result["results"][0]["name"] - link = result["results"][0]["magnetLink"] - link_result = await telegraph( - title=title, user_name=message.from_user.first_name, content=link - ) - language = result["results"][0]["language"] - size = result["results"][0]["size"] - - results = [] - - for i in range(min(limit, len(result["results"]))): - descriptions = result["results"][i]["description"] - genres = result["results"][i]["genre"] - categorys = result["results"][i]["category"] - titles = result["results"][i]["name"] - links = result["results"][i]["magnetLink"] - languages = result["results"][i]["language"] - sizes = result["results"][i]["size"] - - r = f"Title: {titles}\nCategory: {categorys}\nLanguage: {languages}\nSize: {sizes}\nGenres: {genres}\nDescription: {descriptions}\nMagnet Link: {links}
" - results.append(r) - - all_results_content = "
".join(results) - - link_results = await telegraph( - title="Search Results", - user_name=message.from_user.first_name, - content=all_results_content, - ) - - if coverImage_url is not None: - coverImage = requests.get(url=coverImage_url).content - async with aiofiles.open("coverImage.jpg", mode="wb") as f: - await f.write(coverImage) - - await message.delete() - await client.send_media_group( - chat_id, - [ - InputMediaPhoto( - "coverImage.jpg", - caption=f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here\nMore Results: Click Here", - ) - ], - ) - else: - await message.edit_text( - f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here\nMore Results: Click Here", - disable_web_page_preview=True, - ) - - except MediaCaptionTooLong: - description = description[:850] - await message.delete() - await client.send_media_group( - chat_id, - [ - InputMediaPhoto( - "coverImage.jpg", - caption=f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here", - ) - ], - ) - - except MessageTooLong: - description = description[:150] - await message.edit_text( - f"Title: {title}\nCategory: {category}\nLanguage: {language}\nSize: {size}\nGenres: {genre}\nDescription: {description}\nMagnet Link: Click Here", - disable_web_page_preview=True, - ) - - except Exception as e: - await message.edit_text(format_exc(e)) - finally: - if os.path.exists("coverImage.jpg"): - os.remove("coverImage.jpg") - - -@Client.on_message(filters.command("tts", prefix) & filters.me) -async def tts(client: Client, message: Message): - characters = await voice_characters() - await message.edit_text("Please Wait...") - try: - if len(message.command) > 2: - character, prompt = message.text.split(maxsplit=2)[1:] - if character not in characters: - await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" - ) - return - - elif message.reply_to_message and len(message.command) > 1: - character = message.text.split(maxsplit=1)[1] - if character in characters: - prompt = message.reply_to_message.text - else: - await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" - ) - return - - else: - await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" - ) - return - - data = {"text": prompt, "character": character} - response = requests.post(url=f"{url}/speech", headers=headers, json=data) - if response.status_code != 200: - await message.edit_text("Something went wrong") - return - - result = response.json() - audio_data = result["audio"] - audio_data = base64.b64decode(audio_data) - async with aiofiles.open(f"{prompt}.mp3", mode="wb") as f: - await f.write(audio_data) - - await message.delete() - await client.send_audio( - chat_id=message.chat.id, - audio=f"{prompt}.mp3", - caption=f"Characters: {character}\nPrompt: {prompt}", - ) - if os.path.exists(f"{prompt}.mp3"): - os.remove(f"{prompt}.mp3") - - except KeyError: - try: - error = result["error"] - await message.edit_text(error) - except KeyError: - await message.edit_text( - f"Usage: {prefix}tts [character]* [text/reply to text]*\n Available Characters:
{characters}
" - ) - except Exception as e: - await message.edit_text(format_exc(e)) - - -@Client.on_message( - filters.command(["carbonnowsh", "carboon", "carbon", "cboon"], prefix) & filters.me -) -async def carbon(client: Client, message: Message): - if message.reply_to_message: - text = message.reply_to_message.text - message_id = message.reply_to_message.id - elif len(message.command) > 1: - message_id = None - text = message.text.split(maxsplit=1)[1] - else: - await message.edit_text("Query not provided!") - return - await message.edit_text("Processing...") - - image_file = await make_carbon(text) - - await message.delete() - try: - await client.send_photo( - chat_id=message.chat.id, - photo=image_file, - caption=f"Text: {text}", - reply_to_message_id=message_id, - ) - except MediaCaptionTooLong: - cap = text[:850] - await client.send_photo( - chat_id=message.chat.id, - photo=image_file, - caption=f"Text: {cap}", - reply_to_message_id=message_id, - ) - except Exception as e: - await message.edit_text(format_exc(e)) - if os.path.exists("carbon.png"): - os.remove("carbon.png") - - -@Client.on_message(filters.command("ccgen", prefix) & filters.me) -async def ccgen(_, message: Message): - if len(message.command) > 1: - bins = message.text.split(maxsplit=1)[1] - else: - await message.edit_text("Code not provided!") - return - await message.edit_text("Processing...") - response = requests.get(url=f"{url}/ccgen?bins={bins}", headers=headers) - if response.status_code != 200: - await message.edit_text("Something went wrong") - return - - result = response.json() - - cards = result["results"][0]["cards"] - cards_str = "\n".join([f'"{card}"' for card in cards]) - bins = result["results"][0]["bin"] - await message.edit_text( - f"Bins: {bins}\nTotal: {len(cards)}\nCards: \n{cards_str}" - ) - - -@Client.on_message(filters.command("rayso", prefix) & filters.me) -async def rayso(client: Client, message: Message): - title = "Untitled" - themes = [ - "vercel", - "supabase", - "tailwind", - "clerk", - "mintlify", - "prisma", - "bitmap", - "noir", - "ice", - "sand", - "forest", - "mono", - "breeze", - "candy", - "crimson", - "falcon", - "meadow", - "midnight", - "raindrop", - "sunset", - ] - if message.reply_to_message: - text = message.reply_to_message.text - message_id = message.reply_to_message.id - if 2 <= len(message.command) <= 3: - title = message.text.split(maxsplit=2)[1] - theme = message.text.split(maxsplit=2)[2].lower() - if theme not in themes: - theme = "breeze" - elif len(message.command) > 1: - message_id = message.id - title = message.text.split(maxsplit=3)[1] - theme = message.text.split(maxsplit=3)[2] - if theme not in themes: - theme = "breeze" - text = message.text.split(maxsplit=3)[3] - else: - await message.edit_text("Query not provided!") - return - await message.edit_text("Processing...") - - image_file = await make_rayso(text, title, theme) - - if image_file is None: - await message.edit_text("Something went wrong") - return - try: - await client.send_photo( - chat_id=message.chat.id, - photo=image_file, - caption=f"Text: {text}", - reply_to_message_id=message_id, - ) - await message.delete() - except MediaCaptionTooLong: - cap = text[:850] - await client.send_photo( - chat_id=message.chat.id, - photo=image_file, - caption=f"Text: {cap}", - reply_to_message_id=message_id, - ) - await message.delete() - except Exception as e: - await message.edit_text(format_exc(e)) - if os.path.exists(image_file): - os.remove(image_file) - - -modules_help["safone"] = { - "asq [query]*": "Asq", - "app [query]*": "Search for an app on Play Store", - "tsearch [query]*": "Search Torrent", - "tts [character]* [text/reply to text]*": "Convert Text to Speech", - "sgemini [prompt]*": "Gemini Model through safone api", - "carbon [code/file/reply]": "Create beautiful image with your code", - "ccgen [bins]*": "Generate credit cards", - "rayso [title]* [theme]* [text/reply to text]*": "Create beautiful image with your text", -} diff --git a/start b/start new file mode 100755 index 00000000..cb333fff --- /dev/null +++ b/start @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -d ".venv" ]; then + . .venv/bin/activate +elif [ -d "venv" ]; then + . venv/bin/activate +fi + +python3 main.py diff --git a/utils/scripts.py b/utils/scripts.py index ac45ed88..89813ba3 100644 --- a/utils/scripts.py +++ b/utils/scripts.py @@ -24,13 +24,11 @@ import sys import time import traceback -from PIL import Image, ImageEnhance +from PIL import Image from io import BytesIO -import aiohttp from types import ModuleType from typing import Dict, Tuple -from PIL import Image import psutil from pyrogram import Client, errors, enums from pyrogram.errors import FloodWait, MessageNotModified, UserNotParticipant @@ -216,25 +214,6 @@ def text(message: Message) -> str: return message.text if message.text else message.caption -async def make_carbon(code): - url = "https://carbonara.solopov.dev/api/cook" - - async with aiohttp.ClientSession() as session: - async with session.post(url, json={"code": code}) as resp: - image_data = await resp.read() - - carbon_image = Image.open(BytesIO(image_data)) - - enhancer = ImageEnhance.Brightness(carbon_image) - bright_image = enhancer.enhance(1.0) - - output_image = BytesIO() - bright_image.save(output_image, format="PNG", quality=95) - output_image.name = "carbon.png" - - return output_image - - def restart() -> None: music_bot_pid = db.get("custom.musicbot", "music_bot_pid", None) if music_bot_pid is not None: