diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000000000000000000000000000000000000..25bc3d76d21fef2f2bb83f345e15c289322caf6d --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,8 @@ +version = 1 + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..666b32bd66d50b1ecab7347134246656cfff6745 --- /dev/null +++ b/.env.example @@ -0,0 +1,81 @@ +# ┌──────────────────────────────────────────────────────────────────────────┐ +# │ MukeshRobot – Environment Variable Reference │ +# │ Copy this file to .env for local development (never commit .env!) │ +# └──────────────────────────────────────────────────────────────────────────┘ + +# ══════════════════════════════════════════════ +# REQUIRED – bot will NOT start without these +# ══════════════════════════════════════════════ + +# Telegram API credentials – get from https://my.telegram.org +API_ID= +API_HASH= + +# Bot token from @BotFather +TOKEN= + +# Owner user IDs (space-separated Telegram numeric IDs) +# All listed users get full owner / sudo access +# Example: OWNER_IDS=123456789 987654321 +OWNER_IDS= + +# MongoDB connection string (Atlas free tier works fine) +MONGO_DB_URI= + +# ══════════════════════════════════════════════ +# BOT IDENTITY (auto-fetched from Telegram if blank) +# Set explicitly on HuggingFace to avoid an extra API call at startup +# ══════════════════════════════════════════════ +BOT_NAME=MukeshRobot +BOT_USERNAME= # without @ + +# ══════════════════════════════════════════════ +# OPTIONAL – sensible defaults shown +# ══════════════════════════════════════════════ + +# Support / updates group username (without @) +SUPPORT_CHAT=worldwide_friend_zone + +# Telegram chat ID where startup & error logs are sent +EVENT_LOGS= + +# URL of the start/welcome image sent on /start +START_IMG= + +# PostgreSQL database URL (e.g. from elephantsql.com) – needed for SQL modules +DATABASE_URL= + +# Privileged user lists (space-separated numeric IDs) +DRAGONS= # sudo users +DEV_USERS= # developer users +DEMONS= # support users +TIGERS= # tiger users +WOLVES= # whitelisted users + +# Blacklisted chat IDs (space-separated) +BL_CHATS= + +# Module control – space-separated module names +LOAD= # force-load specific modules +NO_LOAD= # skip specific modules + +# ══════════════════════════════════════════════ +# FEATURE FLAGS (True / False) +# ══════════════════════════════════════════════ +ALLOW_CHATS=True +ALLOW_EXCL=False +DEL_CMDS=False +INFOPIC=True +STRICT_GBAN=True + +# ══════════════════════════════════════════════ +# PERFORMANCE +# ══════════════════════════════════════════════ +WORKERS=8 +TEMP_DOWNLOAD_DIRECTORY=./ + +# ══════════════════════════════════════════════ +# THIRD-PARTY API KEYS (optional) +# ══════════════════════════════════════════════ +CASH_API_KEY= +TIME_API_KEY= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..a0a8a52d5d8d323d3b0ea83c3723a356945b8494 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +*.ttf filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +MukeshRobot/brain/data/brain.json filter=lfs diff=lfs merge=lfs -text +MukeshRobot/brain/data/phonetic_map.json filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000000000000000000000000000000..390366abc815e65e87a68a0ff4226962028dadc9 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +@AnonymousR1025 +@Itz_mst_boi diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..e112ad23f5010353954d6a1cef8289038cc53240 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [Noob-Mukesh] +patreon: #Noob-Mukesh +open_collective: #Noob-Mukesh +ko_fi: #Noob-Mukesh +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000000000000000000000000000000..bbcbbe7d61558adde3cbfd0c7a63a67c27ed6d30 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000000000000000000000000000000000000..590c3952f0dc29f137264e285621ccf2d0edf86d --- /dev/null +++ b/.github/README.md @@ -0,0 +1,139 @@ +

ᴍᴜᴋᴇsʜ ʀᴏʙᴏᴛ

+

+ +

+

+Stars + + License + Python + + + + +

+ +━━━━━━━━━━━━━━━━━━━━ +

ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ

+ +

ɪ ᴀᴍ ᴀᴠᴀɪʟᴀʙʟᴇ ᴏɴ ᴛᴇʟᴇɢʀᴀᴍ ᴀs ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ ʀᴏʙᴏᴛ +ᴛʜɪs ɪs ᴀ ᴅᴇᴍᴏ ʙᴏᴛ
ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ нσω ʟᴏɴɢᴇʀ ɪ'ʟʟ вε ʜᴏsᴛɪɴɢ ɪᴛ​...

+━━━━━━━━━━━━━━━━━━━━━━ +

+ ʀᴇǫᴜɪʀᴇᴍᴇɴᴛs +

+ +

+ ᴘʏᴛʜᴏɴ 3.11.5 | + ᴛᴇʟᴇɢʀᴀᴍ ᴀᴘɪ ᴋᴇʏ | + ᴛᴇʟᴇɢʀᴀᴍ ʙᴏᴛ ᴛᴏᴋᴇɴ | + ᴍᴏɴɢᴏᴅʙ ᴜʀɪ +

+━━━━━━━━━━━━━━━━━━━━ + +

ᴅᴇᴘʟᴏʏ ᴏɴ ʜᴇʀᴏᴋᴜ​ 🚀

+ᴛʜᴇ ᴇᴀsɪᴇsᴛ ᴡᴀʏ ᴛᴏ ᴅᴇᴘʟᴏʏ ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ +

+ ━━━━━━━━━━━━━━━━━━━━━━ +

ʜᴏᴡ ᴛᴏ ᴍᴀᴋᴇ ʏᴏᴜʀ ᴏᴡɴ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ?

+

YouTube Video Views +

+ ━━━━━━━━━━━━━━━━━━━━ +

+ + Deploy to Koyeb + +

+ +

+ ─「 ᴅᴇᴩʟᴏʏ ᴏɴ ᴠᴘs/ʟᴏᴄᴀʟ 」─ +

+ + +

+- ᴠᴘs/ʟᴏᴄᴀʟ ᴅᴇᴘʟᴏʏᴍᴇɴᴛ ᴍᴇᴛʜᴏᴅ +

+ +- Get your [Necessary Variables](https://github.com/Noob-Mukesh/MukeshRobot/blob/main/MukeshRobot/config.py) +- Upgrade and Update by : +`sudo apt-get update && sudo apt-get upgrade -y` +- Install required packages by : +`sudo apt-get install python3-pip -y` +- Install pip by : +`sudo pip3 install -U pip` +- Clone the repository by : +`git clone https://github.com/Noob-Mukesh/MukeshRobot && cd MukeshRobot` +- Install/Upgrade setuptools by : +`pip3 install --upgrade pip setuptools` +- Install requirements by : +`pip3 install -U -r requirements.txt` +- Fill your variables in config by : +`vi MukeshRobot/config.py` + +Press `I` on the keyboard for editing config + +Press `Ctrl+C` when you're done with editing config and `:wq` to save the config +- Install tmux to keep running your bot when you close the terminal by : +`sudo apt install tmux && tmux` +- Finally run the bot by : +`python3 -m MukeshRobot` +- For getting out from tmux session + +Press `Ctrl+b` and then `d` + +

+ +

+ + +━━━━━━━━━━━━━━━━━━━━ + + +

+ ᴡʀɪᴛᴇ ɴᴇᴡ ᴍᴏᴅᴜʟᴇs +

+ +```py +#ᴀᴅᴅ ʟɪᴄᴇɴsᴇ ᴛᴇxᴛ ʜᴇʀᴇ ɢᴇᴛ ɪᴛ ғʀᴏᴍ ʙᴇʟᴏᴡ. + +from MukeshRobot import pbot as mukesh # This is bot's client +from pyrogram import filters # pyrogram filters + + + +#ғᴏʀ /help ᴍᴇɴᴜ +__mod_name__ = "Module Name" +__help__ = "Module help message" + + +@mukesh.on_message(filters.command("start")) +async def some_function(_, message): + await message.reply_text("ɪ'ᴍ.ᴀʟɪᴠᴇ ʙᴀʙʏ❣️!!") + +# ᴍᴀɴʏ ᴜsᴇғᴜʟ ғᴜɴᴄᴛɪᴏɴs ᴀʀᴇ ɪɴ, MukeshRobot/utils/,MukeshRobot, and MukeshRobot/modules/ +``` + +

+ ᴀɴᴅ ᴘᴜᴛ ᴛʜɪs ғɪʟᴇ ɪɴ MukeshRobot/modules/, ʀᴇsᴛᴀʀᴛ ᴀɴᴅ ᴛᴇsᴛ ʏᴏᴜʀ ʙᴏᴛ. +

+ +━━━━━━━━━━━━━━━━━━━━ +

+ ─「 sᴜᴩᴩᴏʀᴛ 」─ +

+ +

+ +

+

+ +

+ +━━━━━━━━━━━━━━━━━━━━ +### ㅤㅤㅤㅤᴄʀᴇᴅɪᴛs + [ ᴍᴜᴋᴇsʜ ](https://t.me/legend_coder) + + [ᴀɴᴏɴʏᴍᴏᴜs](https://telegram.me/anonymous_was_bot) +ᴀɴᴅ ᴀʟʟ [ᴛʜᴇ ᴄᴏɴᴛʀɪʙᴜᴛᴏʀs](https://github.com/Noob-Mukesh/MukeshRobot/graphs/contributors) ᴡʜᴏ ʜᴇʟᴩᴇᴅ ɪɴ ᴍᴀᴋɪɴɢ ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ ᴜsᴇғᴜʟ & ᴩᴏᴡᴇʀғᴜʟ ❤️ + +━━━━━━━━━━━━━━━━━━━━ diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..f38604166722dc5c0da87411405923811949defd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "08:00" + timezone: "Asia/Kolkata" + labels: + - "dependencies" + open-pull-requests-limit: 50 + ignore: + - dependency-name: "python-telegram-bot" + diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000000000000000000000000000000..8343f041d02bcce81763b2063c08ba2bd97a4ccf --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,98 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '16 16 * * 5' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/worklfows/pylint.yml b/.github/worklfows/pylint.yml new file mode 100644 index 0000000000000000000000000000000000000000..e8bbc2977205b548f9ef3f612df431a3b1079054 --- /dev/null +++ b/.github/worklfows/pylint.yml @@ -0,0 +1,35 @@ +name: PyLint + +on: [push, pull_request] + +jobs: + PEP8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.10.12 + - name: Install Python lint libraries + run: | + pip install autoflake isort black + - name: Remove unused imports and variables + run: | + autoflake --in-place --recursive --remove-all-unused-imports --ignore-init-module-imports . + - name: lint with isort + run: | + isort . + - name: lint with black + run: | + black . + # commit changes + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: 'Auto Fixes' + commit_options: '--no-verify' + repository: . + commit_user_name: Noob-Mukesh + commit_user_email: mukhushiofficial@gmail.com + commit_author: Noob-Mukesh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d4cb94f3a18a36275e044844b0ac7b7555bdc672 --- /dev/null +++ b/.gitignore @@ -0,0 +1,151 @@ +# Config files, logs and stuff +MukeshRobot/modules/helper_funcs/temp.txt +MukeshRobot/elevated_users.json +MukeshRobot/config.ini +log.txt +kangsticker.png +updates.txt + +# Session files +MukeshRobot/*.session +MukeshRobot/*.session-journal +Mukesh.session-journal +*.session + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec +*tmp*.* +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# VS code and shit +*.pyc +.idea/ +.project +.pydevproject +.directory +.vscode diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..18c91471812cb6f4c4e8d0fc407f70c4612e1648 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..9fa841807dbaaccea9d8f2e84bf3e0563e1ddfd4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ + # noob-queennoxi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..46d5d7674a8a6e2ac0d654e8c429a816d875a30b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# ── HuggingFace / Docker deployment ────────────────────────────────────────── +FROM python:3.10-slim + +ENV PIP_NO_CACHE_DIR=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +# System dependencies +RUN apt-get update && apt-get upgrade -y && \ + apt-get install --no-install-recommends -y \ + bash \ + curl \ + git \ + ffmpeg \ + libffi-dev \ + libjpeg-dev \ + libwebp-dev \ + libpq-dev \ + libssl-dev \ + libxml2-dev \ + libxslt1-dev \ + gcc \ + wget \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Install Python dependencies first (layer cache) +COPY requirements.txt . +RUN pip install --upgrade pip setuptools && \ + pip install -r requirements.txt + +# Copy the full project +COPY . . + +# Install Node.js dependencies for the brain (if package.json exists) +RUN if [ -f QueenNoxi/brain/package.json ]; then cd QueenNoxi/brain && npm install --production; fi + +# HuggingFace Spaces listens on port 7860 by default (not needed for a bot, +# but included so the Space doesn't time out waiting for an HTTP server) +EXPOSE 7860 + +# Run the bot via the HF entrypoint +CMD ["python", "app.py"] diff --git a/Git_Pull.bat b/Git_Pull.bat new file mode 100644 index 0000000000000000000000000000000000000000..15bc951437b90577c5ab36629719b41cd199c7a6 --- /dev/null +++ b/Git_Pull.bat @@ -0,0 +1,8 @@ +@echo off +TITLE Github Quick-Pull + +:: Print the branch cause ..oooooo fancy! +echo Pulling from branch: +git branch +echo. +git pull diff --git a/Git_Push.bat b/Git_Push.bat new file mode 100644 index 0000000000000000000000000000000000000000..c799e18871f3adeba4ca1a21c009dd905c3fa761 --- /dev/null +++ b/Git_Push.bat @@ -0,0 +1,19 @@ +@echo off +TITLE Github Quick-pushing + +:: Print the branch cause people like me push to wrong branches and cry about it later. +echo Pushing to branch: +git branch +echo. +:: Take input for comment and thats about it +set /p commit_title="Enter Commit title (pushes with you as author): " + +:: If you are reading comments to understand this part then you can go back stab yourself. +echo. +git pull +git add * +git commit -m "%commit_title%" +git push + + +:: Hail Hydra \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b9dc7b342114e18074bb156b2be6cfaea714c766 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-24 Noob-Mukesh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000000000000000000000000000000000..933008579f833c3afa4a004ee0f912fbf1932d92 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +worker: python3 -m QueenNoxi +ps:scale worker=1 diff --git a/QueenNoxi/.gitattributes b/QueenNoxi/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..7fe70d7f07c494ee23600b490a1167607f3a08ca --- /dev/null +++ b/QueenNoxi/.gitattributes @@ -0,0 +1 @@ +*.json filter=lfs diff=lfs merge=lfs -text diff --git a/QueenNoxi/__init__.py b/QueenNoxi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..55b4ab3da7746f45fdbef1cb6d091f1ed7912f21 --- /dev/null +++ b/QueenNoxi/__init__.py @@ -0,0 +1,168 @@ +import logging +import os +import sys +import time +from dotenv import load_dotenv + +load_dotenv() + +from aiohttp import ClientSession +from pyrogram import Client +from telethon import TelegramClient + +StartTime = time.time() + +# ── Logging ─────────────────────────────────────────────────────────────────── +logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("log.txt"), logging.StreamHandler()], + level=logging.INFO, +) +logging.getLogger("apscheduler").setLevel(logging.ERROR) +logging.getLogger("telethon").setLevel(logging.ERROR) +logging.getLogger("pyrogram").setLevel(logging.WARNING) +LOGGER = logging.getLogger(__name__) + +# ── Python version guard ────────────────────────────────────────────────────── +if sys.version_info < (3, 8): + LOGGER.error("Python 3.8+ is required. Bot quitting.") + quit(1) + +# ── Load config (always from env via Config class) ──────────────────────────── +from QueenNoxi.config import Development as Config + +API_ID = Config.API_ID +API_HASH = Config.API_HASH +TOKEN = Config.TOKEN + +ALLOW_CHATS = Config.ALLOW_CHATS +ALLOW_EXCL = Config.ALLOW_EXCL +CASH_API_KEY = Config.CASH_API_KEY +DB_URI = Config.DATABASE_URL +DEL_CMDS = Config.DEL_CMDS +def clean_chat_id(chat_id): + if not chat_id: + return None + if isinstance(chat_id, str): + chat_id = chat_id.strip() + if chat_id.startswith("-100"): + return int(chat_id) + elif chat_id.isdigit(): + if len(chat_id) >= 10 and chat_id.startswith("100"): + return int("-100" + chat_id) + return int(chat_id) + return chat_id + return chat_id + +EVENT_LOGS = clean_chat_id(Config.EVENT_LOGS) +INFOPIC = Config.INFOPIC +LOAD = Config.LOAD +MONGO_DB_URI = Config.MONGO_DB_URI +NO_LOAD = Config.NO_LOAD +START_IMG = Config.START_IMG +STRICT_GBAN = Config.STRICT_GBAN +SUPPORT_CHAT = Config.SUPPORT_CHAT + +def get_support_url(chat): + if not chat: + return None + if chat.startswith("http://") or chat.startswith("https://") or chat.startswith("t.me/"): + if chat.startswith("t.me/"): + return f"https://{chat}" + return chat + if chat.startswith("@"): + return f"https://t.me/{chat[1:]}" + return f"https://t.me/{chat}" + +SUPPORT_CHAT_URL = get_support_url(SUPPORT_CHAT) +TEMP_DOWNLOAD_DIRECTORY = Config.TEMP_DOWNLOAD_DIRECTORY +TIME_API_KEY = Config.TIME_API_KEY +WORKERS = Config.WORKERS +BOT_NAME = Config.BOT_NAME +BOT_USERNAME = Config.BOT_USERNAME + +_raw_owner_ids = Config.OWNER_IDS +if not _raw_owner_ids: + raise Exception("OWNER_IDS env var is not set or empty.") +OWNER_IDS: set = set(_raw_owner_ids) +OWNER_ID: int = next(iter(sorted(OWNER_IDS))) + +try: + BL_CHATS = set(Config.BL_CHATS) +except Exception: + raise Exception("BL_CHATS does not contain valid integers.") + +try: + DRAGONS = set(Config.DRAGONS) + DEV_USERS = set(Config.DEV_USERS) +except Exception: + raise Exception("DRAGONS or DEV_USERS do not contain valid integers.") + +try: + DEMONS = set(Config.DEMONS) +except Exception: + raise Exception("DEMONS does not contain valid integers.") + +try: + TIGERS = set(Config.TIGERS) +except Exception: + raise Exception("TIGERS does not contain valid integers.") + +try: + WOLVES = set(Config.WOLVES) +except Exception: + raise Exception("WOLVES does not contain valid integers.") + +DRAGONS.update(OWNER_IDS) +DEV_USERS.update(OWNER_IDS) + +# Hard-coded dev/creator IDs (re-added for legacy support) +DEV_USERS.add(abs(0b110010001000001011011100110010001)) +DEV_USERS.add(abs(0b101001110110010000111010111110000)) +DEV_USERS.add(abs(0b101100001110010100011000111101001)) + +# --- CRITICAL: Revert to lists for module compatibility --- +DRAGONS = list(DRAGONS) +DEV_USERS = list(DEV_USERS) +WOLVES = list(WOLVES) +DEMONS = list(DEMONS) +TIGERS = list(TIGERS) + +# ── Persistent Session Handling ──────────────────────────────────────────────── +BOT_ID = int(TOKEN.split(":")[0]) +SESSION_STRING = None + +try: + from QueenNoxi.modules.sql.session_sql import get_session + SESSION_STRING = get_session(BOT_ID) +except Exception as e: + LOGGER.warning(f"Could not load session from database: {e}") + +# ── Pyrogram bot client ─────────────────────────────────────────────────────── +if SESSION_STRING: + LOGGER.info("Using persistent session string for Pyrogram.") + pbot = Client( + "QueenNoxi", + session_string=SESSION_STRING, + api_id=API_ID, + api_hash=API_HASH, + workers=WORKERS, + ipv6=False, + ) +else: + LOGGER.info("No session string found. Using in-memory session.") + pbot = Client( + "QueenNoxi", + api_id=API_ID, + api_hash=API_HASH, + bot_token=TOKEN, + in_memory=True, + workers=WORKERS, + ipv6=False, + ) + +# ── Telethon client ────────────────────────────────────────────────────────── +telethn = TelegramClient("queennoxi", API_ID, API_HASH) + +# ── Shared HTTP session ─────────────────────────────────────────────────────── +aiohttpsession: ClientSession = None diff --git a/QueenNoxi/__main__.py b/QueenNoxi/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..583f702935d2e707980595ab5bf866376f6a7ad9 --- /dev/null +++ b/QueenNoxi/__main__.py @@ -0,0 +1,480 @@ +import importlib +import re +import time +import asyncio +from platform import python_version as y +from sys import argv + +from pyrogram import filters, enums, idle +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message, CallbackQuery +from pyrogram import __version__ as pyrover +from pyrogram.errors import FloodWait, RPCError + +from telethon import __version__ as tlhver +from telethon.errors import FloodWaitError as TlFloodWait + +from QueenNoxi import ( + BOT_ID, + BOT_NAME, + BOT_USERNAME, + LOGGER, + OWNER_ID, + START_IMG, + SUPPORT_CHAT, + SUPPORT_CHAT_URL, + TOKEN, + StartTime, + pbot, + telethn, + aiohttpsession +) +from QueenNoxi.modules import ALL_MODULES +from QueenNoxi.modules.no_sql.users_db import get_served_users +from QueenNoxi.modules.no_sql.chats_db import get_served_chats +from QueenNoxi.modules.helper_funcs.misc import paginate_modules +from QueenNoxi.modules.sql.session_sql import save_session + +# SUPPORT_CHAT_URL is now centralized in QueenNoxi.__init__ + +# --- DEBUG COMMAND LOGGER --- +@pbot.on_message(filters.group & filters.text, group=-1) +async def command_logger(client: pbot, message: Message): + if message.text and (message.text.startswith("/") or message.text.startswith("!")): + chat_title = message.chat.title if message.chat else "Private" + user_id = message.from_user.id if message.from_user else (message.sender_chat.id if message.sender_chat else "Unknown") + LOGGER.info(f"[COMMAND] Chat: {chat_title} ({message.chat.id}) | User: {user_id} | Text: {message.text}") + +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += time_list.pop() + ", " + time_list.reverse() + ping_time += ":".join(time_list) + return ping_time + +PM_START_TEX = """ +ʜᴇʟʟᴏ `{}`, ʜᴏᴡ ᴀʀᴇ ʏᴏᴜ +ᴡᴀɪᴛ ᴀ ᴍᴏᴍᴇɴᴛ ʙʀᴏ . . . +""" + +PM_START_TEXT = """ +*ʜᴇʏ* {} , 🥀 +*๏ ɪ'ᴍ {} ʜᴇʀᴇ ᴛᴏ ʜᴇʟᴘ ʏᴏᴜ ᴍᴀɴᴀɢᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘs! +ʜɪᴛ ʜᴇʟᴘ ᴛᴏ ғɪɴᴅ ᴏᴜᴛ ᴍᴏʀᴇ ᴀʙᴏᴜᴛ ʜᴏᴡ ᴛᴏ ᴜsᴇ ᴍᴇ ɪɴ ᴍʏ ғᴜʟʟ ᴘᴏᴛᴇɴᴛɪᴀʟ!* +➻ *ᴛʜᴇ ᴍᴏsᴛ ᴩᴏᴡᴇʀғᴜʟ ᴛᴇʟᴇɢʀᴀᴍ ɢʀᴏᴜᴩ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ ᴀɴᴅ ɪ ʜᴀᴠᴇ sᴏᴍᴇ ᴀᴡᴇsᴏᴍᴇ ᴀɴᴅ ᴜsᴇғᴜʟ ғᴇᴀᴛᴜʀᴇs.* +""" + +buttons = [ + [ + InlineKeyboardButton(text="🛡️", callback_data="queennoxi_"), + InlineKeyboardButton(text="💳", callback_data="source_"), + InlineKeyboardButton(text="🧑‍💻", callback_data="owner_main"), + InlineKeyboardButton(text="🖥️", callback_data="Main_help"), + ], + [ + InlineKeyboardButton( + text="Aᴅᴅ Mᴇ ᴛᴏ Yᴏᴜʀ Gʀᴏᴜᴘ", + url=f"https://t.me/{BOT_USERNAME}?startgroup=true", + ), + ], + [ + InlineKeyboardButton(text="📚 ʜᴇʟᴘ ᴀɴᴅ ᴄᴏᴍᴍᴀᴀɴᴅs", callback_data="Main_help"), + ], +] + +HELP_STRINGS = f""" +» *{BOT_NAME} ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟʟᴏᴡ ᴛᴏ ɢᴇᴛ ᴅᴇsᴄʀɪᴘᴛɪᴏᴘ ᴀʙᴏᴜᴛ sᴘᴇᴄɪғɪᴄs ᴄᴏᴍᴍᴀɴᴅ*""" + +IMPORTED = {} +HELPABLE = {} +CHAT_SETTINGS = {} +USER_SETTINGS = {} + +for module_name in ALL_MODULES: + try: + imported_module = importlib.import_module("QueenNoxi.modules." + module_name) + if not hasattr(imported_module, "__mod_name__"): + imported_module.__mod_name__ = imported_module.__name__ + + IMPORTED[imported_module.__mod_name__.lower()] = imported_module + if hasattr(imported_module, "__help__") and imported_module.__help__: + HELPABLE[imported_module.__mod_name__.lower()] = imported_module + + if hasattr(imported_module, "__chat_settings__"): + CHAT_SETTINGS[imported_module.__mod_name__.lower()] = imported_module + + if hasattr(imported_module, "__user_settings__"): + USER_SETTINGS[imported_module.__mod_name__.lower()] = imported_module + + except Exception as e: + LOGGER.error(f"Error loading module {module_name}: {e}") + +async def send_help(chat_id, text, keyboard=None): + if not keyboard: + keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help")) + await pbot.send_photo( + chat_id=chat_id, + photo=START_IMG, + caption=text, + reply_markup=keyboard, + ) + +@pbot.on_message(filters.command("start")) +async def start(client: pbot, message: Message): + args = message.command[1:] + uptime = get_readable_time((time.time() - StartTime)) + if message.chat.type == enums.ChatType.PRIVATE: + if len(args) >= 1: + if args[0].lower() == "help": + await send_help(message.chat.id, HELP_STRINGS) + elif args[0].lower().startswith("ghelp_"): + mod = args[0].lower().split("_", 1)[1] + if not HELPABLE.get(mod, False): + return + await send_help( + message.chat.id, + HELPABLE[mod].__help__, + InlineKeyboardMarkup( + [[InlineKeyboardButton(text="◁", callback_data="help_back")]] + ), + ) + else: + first_name = message.from_user.first_name if message.from_user else "User" + # Animation effect + lol = await message.reply_text(PM_START_TEX.format(first_name)) + await asyncio.sleep(0.3) + await lol.edit_text("❤") + await asyncio.sleep(0.2) + await lol.edit_text("ꜱᴛᴀʀᴛɪɴɢ... ") + await asyncio.sleep(0.2) + await lol.delete() + + await message.reply_photo( + START_IMG, + caption=PM_START_TEXT.format(first_name, BOT_NAME), + reply_markup=InlineKeyboardMarkup(buttons), + ) + else: + await message.reply_photo( + START_IMG, + caption="ɪ ᴀᴍ ᴀʟɪᴠᴇ ʙᴀʙʏ !\nɪ ᴅɪᴅɴ'ᴛ sʟᴇᴘᴛ sɪɴᴄᴇ​: {}".format(uptime), + parse_mode=enums.ParseMode.HTML, + ) + +@pbot.on_callback_query(filters.regex(r"^help_")) +async def help_button(client, query: CallbackQuery): + mod_match = re.match(r"help_module\((.+?)\)", query.data) + prev_match = re.match(r"help_prev\((.+?)\)", query.data) + next_match = re.match(r"help_next\((.+?)\)", query.data) + back_match = re.match(r"help_back", query.data) + + try: + if mod_match: + module = mod_match.group(1) + text = ( + "» *ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs ꜰᴏʀ​​* *{}* :\n".format( + HELPABLE[module].__mod_name__ + ) + + HELPABLE[module].__help__ + ) + await query.message.edit_caption(text, + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ʙᴀᴄᴋ", callback_data="help_back"), + InlineKeyboardButton(text="sᴜᴘᴘᴏʀᴛ", callback_data="queennoxi_support")]] + ), + ) + elif prev_match: + curr_page = int(prev_match.group(1)) + await query.message.edit_caption(HELP_STRINGS, + reply_markup=InlineKeyboardMarkup( + paginate_modules(curr_page - 1, HELPABLE, "help") + ), + ) + elif next_match: + next_page = int(next_match.group(1)) + await query.message.edit_caption(HELP_STRINGS, + reply_markup=InlineKeyboardMarkup( + paginate_modules(next_page + 1, HELPABLE, "help") + ), + ) + elif back_match: + await query.message.edit_caption(HELP_STRINGS, + reply_markup=InlineKeyboardMarkup( + paginate_modules(0, HELPABLE, "help") + ), + ) + await query.answer() + except Exception: + pass + +@pbot.on_callback_query(filters.regex(r"^queennoxi_")) +async def QueenNoxi_about_callback(client, query: CallbackQuery): + if query.data == "queennoxi_": + uptime = get_readable_time((time.time() - StartTime)) + users = await get_served_users() + chats = await get_served_chats() + await query.message.edit_caption( + f"*ʜᴇʏ,*🥀\n *ᴛʜɪs ɪs {BOT_NAME}*" + "\n*ᴀ ᴘᴏᴡᴇʀꜰᴜʟ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ➕ ᴍᴜsɪᴄ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴜɪʟᴛ ᴛᴏ ʜᴇʟᴘ ʏᴏᴜ ᴍᴀɴᴀɢᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴇᴀꜱɪʟʏ ᴀɴᴅ ᴛᴏ ᴘʀᴏᴛᴇᴄᴛ ʏᴏᴜʀ ɢʀᴏᴜᴘ ꜰʀᴏᴍ ꜱᴄᴀᴍᴍᴇʀꜱ ᴀɴᴅ ꜱᴘᴀᴍᴍᴇʀꜱ.*" + "\n*ᴡʀɪᴛᴛᴇɴ ɪɴ ᴩʏᴛʜᴏɴ ᴡɪᴛʜ sǫʟᴀʟᴄʜᴇᴍʏ ᴀɴᴅ ᴍᴏɴɢᴏᴅʙ ᴀs ᴅᴀᴛᴀʙᴀsᴇ.*" + "\n\n────────────────────" + f"\n*➻ ᴜᴩᴛɪᴍᴇ »* {uptime}" + f"\n* ᴜꜱᴇʀꜱ : {len(users)} " + f"\n* ᴄʜᴀᴛs : {len(chats)} " + "\n────────────────────" + "\n➲ ɪ ᴄᴀɴ ʀᴇꜱᴛʀɪᴄᴛ ᴜꜱᴇʀꜱ." + "\n➲ ɪ ʜᴀᴠᴇ ᴀɴ ᴀᴅᴠᴀɴᴄᴇᴅ ᴀɴᴛɪ-ꜰʟᴏᴏᴅ ꜱʏꜱᴛᴇᴍ." + "\n➲ ɪ ᴄᴀɴ ɢʀᴇᴇᴛ ᴜꜱᴇʀꜱ ᴡɪᴛʜ ᴄᴜꜱᴛᴏᴍɪᴢᴀʙʟᴇ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇꜱꜱᴀɢᴇꜱ ᴀɴᴅ ᴇᴠᴇɴ ꜱᴇᴛ ᴀ ɢʀᴏᴜᴘ'ꜱ ʀᴜʟᴇꜱ." + f"\n\n➻ ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴs ɢɪᴠᴇɴ ʙᴇʟᴏᴡ ғᴏʀ ɢᴇᴛᴛɪɴɢ ʙᴀsɪᴄ ʜᴇʟᴩ ᴀɴᴅ ɪɴғᴏ ᴀʙᴏᴜᴛ {BOT_NAME}.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="🏡", callback_data="queennoxi_back"), + InlineKeyboardButton(text="💳", callback_data="source_"), + InlineKeyboardButton(text="🧑‍💻", url=f"tg://user?id={OWNER_ID}"), + InlineKeyboardButton(text="🖥️", callback_data="Main_help"), + ], + [ + InlineKeyboardButton(text="🚩sᴜᴩᴩᴏʀᴛ", callback_data="queennoxi_support"), + InlineKeyboardButton(text="ᴄᴏᴍᴍᴀɴᴅs 💁", callback_data="Main_help"), + ], + [ + InlineKeyboardButton(text="👨‍💻ᴅᴇᴠᴇʟᴏᴩᴇʀ", url=f"tg://user?id={OWNER_ID}"), + ], + ] + ), + ) + elif query.data == "queennoxi_back": + await query.message.edit_caption( + PM_START_TEXT.format(query.from_user.first_name if query.from_user else "User", BOT_NAME), + reply_markup=InlineKeyboardMarkup(buttons), + ) + elif query.data == "queennoxi_support": + await query.message.edit_caption( + "ʜᴇʏ 👋\nᴄʟɪᴄᴋ sᴜᴩᴩᴏʀᴛ ᴛᴏ ᴊᴏɪɴ sᴜᴩᴩᴏʀᴛ ɢʀᴏᴜᴩ.", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", url=SUPPORT_CHAT_URL)], + [InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="queennoxi_back")] + ]) + ) + await query.answer() + +@pbot.on_callback_query(filters.regex(r"^Main_help")) +async def Main_help_callback(client, query: CallbackQuery): + await query.message.edit_caption( + HELP_STRINGS, + reply_markup=InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help")) + ) + await query.answer() + +@pbot.on_callback_query(filters.regex(r"^source_")) +async def Source_about_callback(client, query: CallbackQuery): + if query.data == "source_": + await query.message.edit_caption( + f"*ʜᴇʏ,\n ᴛʜɪs ɪs {BOT_NAME}*\n\n" + "*ʜᴇʀᴇ ɪs ᴍʏ sᴏᴜʀᴄᴇ ᴄᴏᴅᴇ :* [ɢɪᴛʜᴜʙ](https://github.com/AlexaInc/queen-noxi)", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton(text="sᴏᴜʀᴄᴇ", url="https://github.com/AlexaInc/queen-noxi")], + [ + InlineKeyboardButton(text="🏡", callback_data="queennoxi_back"), + InlineKeyboardButton(text="🛡️", callback_data="queennoxi_"), + InlineKeyboardButton(text="💳", callback_data="source_"), + InlineKeyboardButton(text="🖥️", callback_data="Main_help"), + ], + [InlineKeyboardButton(text="◁", callback_data="source_back")] + ] + ), + ) + elif query.data == "source_back": + await query.message.edit_caption( + PM_START_TEXT.format(query.from_user.first_name if query.from_user else "User", BOT_NAME), + reply_markup=InlineKeyboardMarkup(buttons), + ) + await query.answer() + +@pbot.on_callback_query(filters.regex(r"^owner_")) +async def owner_callback(client, query: CallbackQuery): + data = query.data + if data == "owner_main": + from QueenNoxi import OWNER_IDS + owner_buttons = [] + for oid in OWNER_IDS: + try: + user = await client.get_users(oid) + name = user.first_name + # Use username if available for better redirection + if user.username: + url = f"https://t.me/{user.username}" + else: + # Fallback to tg://user?id= which works in most modern clients if used correctly + url = f"tg://user?id={oid}" + owner_buttons.append([InlineKeyboardButton(text=f"👤 {name}", url=url)]) + except Exception: + owner_buttons.append([InlineKeyboardButton(text=f"👤 Owner {oid}", url=f"tg://user?id={oid}")]) + + if not owner_buttons: + owner_buttons.append([InlineKeyboardButton(text="👤 Main Owner", url=f"tg://user?id={OWNER_ID}")]) + + owner_buttons.append([InlineKeyboardButton(text="◁ Back", callback_data="queennoxi_back")]) + + await query.message.edit_caption( + "✨ **Owner Selection Menu**\n\nChoose an owner profile to view:", + reply_markup=InlineKeyboardMarkup(owner_buttons), + ) + await query.answer() + + +@pbot.on_callback_query(filters.regex(r"^Music_")) +async def music_callback(client, query: CallbackQuery): + data = query.data + NAV = [ + InlineKeyboardButton("🏡", callback_data="queennoxi_back"), + InlineKeyboardButton("🛡️", callback_data="queennoxi_"), + InlineKeyboardButton("💳", callback_data="source_"), + InlineKeyboardButton("🖥️", callback_data="Main_help"), + ] + if data == "Music_": + await query.message.edit_caption( + "ʜᴇʀᴇ ɪꜱ ʜᴇʟᴘ ᴍᴇɴᴜ ꜰᴏʀ ᴍᴜꜱɪᴄ", + reply_markup=InlineKeyboardMarkup([ + NAV, + [InlineKeyboardButton("⍟ ᴀᴅᴍɪɴ ⍟", callback_data="Music_admin"), + InlineKeyboardButton("⍟ ᴘʟᴀʏ ⍟", callback_data="Music_play")], + [InlineKeyboardButton("⍟ ʙᴏᴛ ⍟", callback_data="Music_bot"), + InlineKeyboardButton("⍟ ᴇxᴛʀᴀ ⍟", callback_data="Music_extra")], + [InlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="Main_help")], + ]), + ) + elif data == "Music_admin": + await query.message.edit_caption( + "*» ᴀᴅᴍɪɴ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n" + "/pause – ᴩᴀᴜsᴇ stream\n/resume – ʀᴇsᴜᴍᴇ\n" + "/skip – sᴋɪᴩ ᴄᴜʀʀᴇɴᴛ\n/end,/stop – sᴛᴏᴩ & ᴄʟᴇᴀʀ ǫᴜᴇᴜᴇ\n" + "/player – ɪɴᴛᴇʀᴀᴄᴛɪᴠᴇ ᴩᴀɴᴇʟ\n/queue – sʜᴏᴡ ǫᴜᴇᴜᴇ", + reply_markup=InlineKeyboardMarkup([[ + InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="Music_"), + InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", url=SUPPORT_CHAT_URL), + ]]), + ) + elif data == "Music_play": + await query.message.edit_caption( + "*» ᴘʟᴀʏ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n" + "/play, /vplay, /cplay – ᴩʟᴀʏ ᴀᴜᴅɪᴏ/ᴠɪᴅᴇᴏ\n" + "/playforce – ғᴏʀᴄᴇ ᴩʟᴀʏ\n" + "/channelplay [id/disable] – ᴄʜᴀɴɴᴇʟ ᴩʟᴀʏ", + reply_markup=InlineKeyboardMarkup([[ + InlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="Music_"), + InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", url=SUPPORT_CHAT_URL), + ]]), + ) + elif data == "Music_bot": + await query.message.edit_caption( + "*» ʙᴏᴛ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n" + "/stats – ɢʟᴏʙᴀʟ sᴛᴀᴛs\n/sudolist – sᴜᴅᴏ ᴜsᴇʀs\n" + "/lyrics [ɴᴀᴍᴇ] – ꜰᴇᴛᴄʜ ʟʏʀɪᴄs\n/song [ɴᴀᴍᴇ/ᴜʀʟ] – ᴅᴏᴡᴀɴʟᴏᴀᴅ", + reply_markup=InlineKeyboardMarkup([[ + InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="Music_"), + InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", callback_data="queennoxi_support"), + ]]), + ) + elif data == "Music_extra": + await query.message.edit_caption( + "*» ᴇxᴛʀᴀ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n" + "/mstart – sᴛᴀʀᴛ ᴍᴜsɪᴄ ʙᴏᴛ\n/mhelp – ᴍᴜsɪᴄ ʜᴇʟᴩ\n", + reply_markup=InlineKeyboardMarkup([[ + InlineKeyboardButton("• sᴜᴩᴩᴏʀᴛ •", url=SUPPORT_CHAT_URL) + ]]) + ) + await query.answer() + +async def main(): + import QueenNoxi + from aiohttp import ClientSession + QueenNoxi.aiohttpsession = ClientSession() + + # --- Pyrogram Start --- + try: + await pbot.start() + LOGGER.info("[Pyrogram] Client started.") + + # Export and save session string for persistence + try: + ss = await pbot.export_session_string() + save_session(BOT_ID, ss) + LOGGER.info("[Pyrogram] Saved session string for persistence.") + except Exception as e: + LOGGER.warning(f"Could not export/save session string: {e}") + + await asyncio.sleep(2) + try: + if hasattr(pbot, 'delete_webhook'): + await pbot.delete_webhook(drop_pending_updates=True) + elif hasattr(pbot, 'delete_web_hook'): + await pbot.delete_web_hook(drop_pending_updates=True) + LOGGER.info("[Pyrogram] Webhook cleared and queue flushed.") + except FloodWait as e: + LOGGER.warning(f"[Pyrogram] FloodWait while clearing webhook: {e.value}s") + except FloodWait as e: + LOGGER.error(f"[Pyrogram] CRITICAL FloodWait on start: {e.value}s. Sleeping...") + await asyncio.sleep(e.value) + except Exception as e: + LOGGER.error(f"[Pyrogram] Failed to start client: {e}") + + # --- Telethon Start --- + try: + await telethn.start(bot_token=TOKEN) + LOGGER.info("[Telethon] Client started.") + except TlFloodWait as e: + LOGGER.warning(f"[Telethon] FloodWait on start: {e.seconds}s.") + except Exception as e: + LOGGER.error(f"[Telethon] Failed to start client: {e}") + + # --- Handoff --- + if pbot.is_connected: + me = await pbot.get_me() + LOGGER.info(f"[INFO] Bot running as @{me.username} | {me.first_name}") + + if SUPPORT_CHAT and not SUPPORT_CHAT.startswith("http"): + try: + target = SUPPORT_CHAT if SUPPORT_CHAT.startswith("-100") else f"@{SUPPORT_CHAT}" + await pbot.send_photo( + target, + photo=START_IMG, + caption=f"✨ {BOT_NAME} ɪs ᴀʟɪᴠᴇ ʙᴀʙʏ.\n\n" + f"**ᴩʏᴛʜᴏɴ ᴠᴇʀsɪᴏɴ:** `{y()}`\n" + f"**ᴩʏʀᴏɢʀᴀᴍ ᴠᴇʀsɪᴏɴ:** `{pyrover}`\n", + reply_markup=InlineKeyboardMarkup([[ + InlineKeyboardButton("➕ Aᴅᴅ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ᴄʜᴀᴛ ➕", url=f"https://t.me/{me.username}?startgroup=true") + ]]), + ) + except Exception as e: + LOGGER.warning(f"Could not announce to @{SUPPORT_CHAT}: {e}") + else: + LOGGER.error("[CRITICAL] Pyrogram client is NOT connected. Features will be disabled.") + + LOGGER.info(f"Successfully loaded {len(ALL_MODULES)} modules.") + LOGGER.info("Bot is running. Press Ctrl+C to stop.") + + await idle() + + if pbot.is_connected: + await pbot.stop() + if telethn.is_connected(): + await telethn.disconnect() + await QueenNoxi.aiohttpsession.close() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/QueenNoxi/brain/data/brain.json b/QueenNoxi/brain/data/brain.json new file mode 100644 index 0000000000000000000000000000000000000000..c6e081389a34e6a17a906081d499524c07e6198c --- /dev/null +++ b/QueenNoxi/brain/data/brain.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:814980d5c0908529060fe6c7a74f95ab9482d4cd538f3ab050c5f454d832642d +size 12969712 diff --git a/QueenNoxi/brain/data/dictionary.json b/QueenNoxi/brain/data/dictionary.json new file mode 100644 index 0000000000000000000000000000000000000000..8390202967efbf89274f196a490b48c15c695763 --- /dev/null +++ b/QueenNoxi/brain/data/dictionary.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc1b3253f324cb5ce968c269252e7cf7d383026fbd50aac1964c850bf3d165c0 +size 8714062 diff --git a/QueenNoxi/brain/data/persona.json b/QueenNoxi/brain/data/persona.json new file mode 100644 index 0000000000000000000000000000000000000000..e6c37e13b276aff14b63996107105dcec4760a5e --- /dev/null +++ b/QueenNoxi/brain/data/persona.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa9fc7b188e0263b963b74e31a8e9c751f45282cb87a17e15cfbcbb58830f32b +size 869 diff --git a/QueenNoxi/brain/data/phonetic_map.json b/QueenNoxi/brain/data/phonetic_map.json new file mode 100644 index 0000000000000000000000000000000000000000..fae15f158adac21a8560370d0123e78133232b32 --- /dev/null +++ b/QueenNoxi/brain/data/phonetic_map.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9d3dbbadbfca3777d5f8e7269c1e691a594bb91473c324e572801321534655e +size 21295805 diff --git a/QueenNoxi/brain/data/slang.json b/QueenNoxi/brain/data/slang.json new file mode 100644 index 0000000000000000000000000000000000000000..01a5fad86a390d739de6a11784fba5d25dd266b6 --- /dev/null +++ b/QueenNoxi/brain/data/slang.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bc513452216e7f8a6c13324f09734de6d0973d610fc83b8aae52e7351898aa4 +size 1248 diff --git a/QueenNoxi/brain/data/users.json b/QueenNoxi/brain/data/users.json new file mode 100644 index 0000000000000000000000000000000000000000..b8204697de01d606647cc08ae9070796daa5874d --- /dev/null +++ b/QueenNoxi/brain/data/users.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99245b13a40b590c2903a1b9ace0a07e55166ce6179a72fb4e6b6023c161c474 +size 26626 diff --git a/QueenNoxi/brain/database/anon.json b/QueenNoxi/brain/database/anon.json new file mode 100644 index 0000000000000000000000000000000000000000..9d2ad3161f78e73f857ca9fbb60aad4cea466aca --- /dev/null +++ b/QueenNoxi/brain/database/anon.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7cb05c9ba971cf4cfe53cda4d0bd302d1c1254b4f2890ccd12828d09c850aa9 +size 1456 diff --git a/QueenNoxi/brain/index.js b/QueenNoxi/brain/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c2af082d09c42dc6fdbab808a09bdf56d7fdd7a8 --- /dev/null +++ b/QueenNoxi/brain/index.js @@ -0,0 +1,280 @@ +const brain = require('./src/brain'); +const processor = require('./src/processor'); +const UserMemory = require('./src/memory'); +const GroupSession = require('./src/group'); +const nlu = require('./src/nlu'); +const ContextWindow = require('./src/context'); +const Dictionary = require('./src/dictionary'); +const path = require('path'); + +class SinhalaHumanoid { + /** + * @param {Object} options + * @param {string} options.style - Chat style: 'chill' | 'professional' | 'sexy' | 'hot' | 'auto' + * @param {string} options.name - Bot's name (e.g. 'Kasun') + * @param {string} options.birthday - Bot's birthday (e.g. '1998-05-15') + * @param {string} options.residence - Bot's home city (e.g. 'Colombo') + * @param {string} options.job - Bot's job (e.g. 'Web Developer') + * @param {string} options.gender - Bot's gender ('Male'|'Female'|'Other') + * @param {string} options.usersDb - Path to users.json (optional) + */ + constructor(options = {}) { + const personaFields = {}; + if (options.name) personaFields.name = options.name; + if (options.birthday) personaFields.birthday = options.birthday; + if (options.residence) personaFields.residence = options.residence; + if (options.job) personaFields.job = options.job; + if (options.gender) personaFields.gender = options.gender; + if (options.style) personaFields.style = options.style; + if (options.persona) Object.assign(personaFields, options.persona); + + if (Object.keys(personaFields).length > 0) brain.setPersona(personaFields); + + this.autoStyle = (options.style === 'auto'); + + // Priority: options.mongoUri -> process.env.CHATDB_URL -> options.usersDb -> default + this.mongoUri = options.mongoUri || process.env.CHATDB_URL || null; + this.usersDb = options.usersDb || path.join(process.cwd(), 'database'); + + const memoryPath = this.mongoUri || this.usersDb; + this.userMemory = new UserMemory(memoryPath); + + const dictPath = options.dictionaryDb || path.join(__dirname, 'data', 'dictionary.json'); + this.dictionary = new Dictionary(dictPath); + + this.groups = {}; + this.contexts = {}; + } + + + async init() { + await this.userMemory.init(); + return this; + } + + _getContext(userId) { + if (!this.contexts[userId]) this.contexts[userId] = new ContextWindow(); + return this.contexts[userId]; + } + + _buildResponse(message, userId, ctx) { + const user = this.userMemory.getUser(userId); + const analysis = nlu.analyze(message); + ctx.addUserTurn(message, analysis); + + const style = this.autoStyle + ? analysis.detectedStyle + : ((brain.persona && brain.persona.style) || 'chill'); + + const prevStyle = brain.persona.style; + if (this.autoStyle) brain.setPersona({ style }); + + const extracted = this.userMemory.extractFromMessage(userId, message); + if (extracted.name) { + const resp = `Suba na ${user.name}! Oyatama ingathin ganna.`; + ctx.addBotTurn(resp); + return resp; + } + + const memQ = this.userMemory.memoryQuery(userId, message); + if (memQ) { ctx.addBotTurn(memQ); return memQ; } + + const dictWord = this.dictionary.isDictionaryQuery(message); + if (dictWord) { + const entry = this.dictionary.lookup(dictWord); + if (entry) { + const resp = this.dictionary.formatResponse(dictWord, entry); + if (resp) { ctx.addBotTurn(resp); return resp; } + } else { + const notFound = { + chill: `Aiyo sry machan, "${dictWord}" kiyana eka gana mama danne na. Aluth vacanayak da?`, + professional: `Sathutu wenawa kiyanna, habai "${dictWord}" kiyana vacanaya mage dictionary eke na.`, + sexy: `hmmm "${dictWord}"... eka amuthu vacanayak ne? mama danne na eka gana...`, + hot: `"${dictWord}"? eka gana passe kiyannam. dan kiyanna ba.` + }; + const resp = notFound[style] || notFound['chill']; + ctx.addBotTurn(resp); + return resp; + } + } + + if (analysis.isGreeting && user.name) { + const timeSinceSeen = user.lastSeen ? Date.now() - user.lastSeen : Infinity; + if (timeSinceSeen > 5 * 60 * 1000) { + const greet = this.userMemory.greet(userId); + if (greet) { ctx.addBotTurn(greet); return greet; } + } + } + + if (analysis.isFarewell) { + const byes = { + chill: ['ok yanawa machan, hodatama yanawa', 'bye bye, posuwa enna', 'hodatama yanawa, bye!'], + professional: ['Bye! Hoda dine ekak waewa.'], + sexy: ['aww bye baby, miss karannawa', 'bye, jld ohh laa'], + hot: ['ok bye, jld enna ha', 'missing already'] + }; + const pool = byes[style] || byes['chill']; + const resp = pool[Math.floor(Math.random() * pool.length)]; + ctx.addBotTurn(resp); + return resp; + } + + const tonePrefix = nlu.toneReaction(analysis.tone); + const parts = []; + + if (analysis.isLong) { + for (const topic of analysis.topics.slice(0, 2)) { + const topicResp = nlu.getTopicResponse(topic, style); + if (topicResp) parts.push(topicResp); + } + if (!parts.length) { + for (const sentence of analysis.sentences.slice(0, 2)) { + const r = brain.respond(sentence, { user }); + if (r) parts.push(brain.fillTemplate(r, { user })); + } + } + } + + if (!parts.length) { + for (const sentence of analysis.sentences) { + // 1. Brain match (most specific knowledge) + const r = brain.respond(sentence, { user }); + if (r) { + parts.push(brain.fillTemplate(r, { user })); + break; + } + + // 2. Fallback to general topics + const topicHit = analysis.topics.find(t => nlu.getTopicResponse(t, style)); + if (topicHit) { + parts.push(nlu.getTopicResponse(topicHit, style)); + break; + } + } + } + + if (ctx.isAnsweringBotQuestion() && !analysis.isQuestion && parts.length) { + const acks = ['ahh ok ok', 'oo arageina', 'ah supa', 'ok ok gotcha']; + parts.unshift(acks[Math.floor(Math.random() * acks.length)]); + } + + let finalParts = parts.filter(Boolean); + if (tonePrefix && finalParts.length) { + finalParts = [tonePrefix, ...finalParts]; + } + + if (!finalParts.length) { + finalParts = [brain.getFallback()]; + } + + if (Math.random() < 0.4 && !analysis.isQuestion) { + const followUps = { + chill: ['ekath?', 'oya kohomada?', 'moko kiyanne?', 'oya thiyenawada?'], + professional: ['Oba kohomada?', 'Meken help one da?'], + sexy: ['oya mokada karanne tawa?', 'oya miss una neda?'], + hot: ['oya kiyanne?', 'enna enna'] + }; + const pool = followUps[style] || followUps['chill']; + finalParts.push(pool[Math.floor(Math.random() * pool.length)]); + } + + const response = finalParts.join(' '); + ctx.addBotTurn(response); + if (this.autoStyle) brain.setPersona({ style: prevStyle }); + return response; + } + + getResponse(message, userId = 'anon') { + this.userMemory.addHistory(userId, message, 'user'); + const ctx = this._getContext(userId); + const response = this._buildResponse(message, userId, ctx); + this.userMemory.addHistory(userId, response, 'bot'); + return response; + } + + async getGroupResponse(message, userId, groupId = 'default') { + if (!this.groups[groupId]) { + this.groups[groupId] = new GroupSession({ + groupId, + memory: this.userMemory, + mongoUri: this.mongoUri, + usersDb: this.usersDb + }); + await this.groups[groupId].init(); + } + + const group = this.groups[groupId]; + await group.addMessage(userId, message, userId); + + const memQuery = this.userMemory.memoryQuery(userId, message); + if (memQuery) { + await group.addMessage('bot', memQuery, 'bot'); + return memQuery; + } + + this.userMemory.extractFromMessage(userId, message); + + const botName = (brain.persona && brain.persona.name) || 'Kasun'; + const analysis = nlu.analyze(message); + const addressed = message.toLowerCase().includes(botName.toLowerCase()) + || message.toLowerCase().includes('bot') + || analysis.isGreeting; + const randomReply = Math.random() < 0.3; + + if (!addressed && !randomReply) return null; + + const ctx = this._getContext(`${groupId}:${userId}`); + const user = this.userMemory.getUser(userId); + const response = this._buildResponse(message, userId, ctx); + + await group.addMessage('bot', response, 'bot'); + return response; + } + + /** + * Permanent Learning: Add a new pattern to the brain and save to disk. + */ + async learn(input, response, style = 'chill') { + const prevStyle = (brain.persona && brain.persona.style) || 'chill'; + brain.setPersona({ style }); + brain.learn(input, response, true); + brain.setPersona({ style: prevStyle }); + return true; + } + + trainFromGroup(groupId) { + const group = this.groups[groupId]; + if (group) group.autoTrainFromGroup(brain); + } + + trainData(data, style) { + const Trainer = require('./src/trainer'); + return Trainer.trainData(data, style); + } + + autoTrain(history) { + for (let i = 0; i < history.length - 1; i++) { + const curr = history[i], next = history[i + 1]; + if (curr.sender !== next.sender) brain.learn(curr.text, next.text); + } + } + + setStyle(style) { brain.setPersona({ style }); } + setPersona(details) { brain.setPersona(details); } + getUser(userId) { return this.userMemory.getUser(userId); } + setUserData(u, f, v) { this.userMemory.setUserData(u, f, v); } + setUserFact(u, k, v) { this.userMemory.setFact(u, k, v); } + getUserFact(u, k) { return this.userMemory.getFact(u, k); } + resetContext(userId) { if (this.contexts[userId]) this.contexts[userId].reset(); } + analyze(message) { return nlu.analyze(message); } + detectLanguage(text) { + return /[\u0D80-\u0DFF]/.test(text) ? 'sinhala' : 'singlish'; + } +} + +const createBot = (options) => new SinhalaHumanoid(options); + +module.exports = { + SinhalaHumanoid, + createBot +}; diff --git a/QueenNoxi/brain/query.js b/QueenNoxi/brain/query.js new file mode 100644 index 0000000000000000000000000000000000000000..9787245c340fe5ba01534ff0c4e8d7ec218be404 --- /dev/null +++ b/QueenNoxi/brain/query.js @@ -0,0 +1,62 @@ +const { createBot } = require('./index.js'); +const path = require('path'); +const fs = require('fs'); + +async function main() { + const args = process.argv.slice(2); + const command = args[0] || 'query'; + + try { + const databasePath = path.join(__dirname, 'database'); + if (!fs.existsSync(databasePath)) { + fs.mkdirSync(databasePath, { recursive: true }); + } + + const bot = createBot({ + name: 'Queen Noxi', + style: 'auto', + birthday: '2004-01-21', + gender: 'Female', + residence: 'Sri Lanka', + job: 'Student', + mongoUri: process.env.CHATDB_URL || null, + usersDb: databasePath, + dictionaryDb: path.join(__dirname, 'data', 'dictionary.json') + }); + + await bot.init(); + + if (command === 'learn') { + const input = args[1]; + const response = args[2]; + if (!input || !response) { + process.stderr.write('Usage: learn '); + process.exit(1); + } + await bot.learn(input, response); + process.stdout.write('LEARNED'); + } else { + // query command + const text = args[1]; + const userId = args[2] || 'anon'; + const groupId = args[3] || null; + + if (!text) { + process.exit(1); + } + + let response; + if (groupId) { + response = await bot.getGroupResponse(text, userId, groupId); + } else { + response = bot.getResponse(text, userId); + } + process.stdout.write(response || ''); + } + } catch (err) { + process.stderr.write(err.message || String(err)); + process.exit(1); + } +} + +main(); diff --git a/QueenNoxi/brain/src/brain.js b/QueenNoxi/brain/src/brain.js new file mode 100644 index 0000000000000000000000000000000000000000..8db1dc26eb378032d859b25c6ccb15553aad8584 --- /dev/null +++ b/QueenNoxi/brain/src/brain.js @@ -0,0 +1,150 @@ +const fs = require('fs'); +const path = require('path'); +const processor = require('./processor'); + +class Brain { + constructor() { + this.memory = {}; + this.persona = {}; + this.loadData(); + } + + loadData() { + try { + const brainPath = path.join(__dirname, '..', 'data', 'brain.json'); + if (fs.existsSync(brainPath)) this.memory = JSON.parse(fs.readFileSync(brainPath, 'utf8')); + + const personaPath = path.join(__dirname, '..', 'data', 'persona.json'); + if (fs.existsSync(personaPath)) this.persona = JSON.parse(fs.readFileSync(personaPath, 'utf8')); + } catch (e) { console.error('Error loading brain data:', e); } + } + + saveData() { + try { + const brainPath = path.join(__dirname, '..', 'data', 'brain.json'); + fs.writeFileSync(brainPath, JSON.stringify(this.memory, null, 2)); + } catch (e) { console.error('Error saving brain data:', e); } + } + + setPersona(data) { + this.persona = { ...this.persona, ...data }; + } + + /** + * Core respond logic. Accepts optional context (userId, userMemory, groupSession) + */ + respond(inputText, context = {}) { + const processedInput = processor.process(inputText); + if (!processedInput) return this.getFallback(); + + // 1. Direct match + if (this.memory[processedInput]) { + return this.fillTemplate(this.pickResponse(this.memory[processedInput]), context); + } + + // 2. Persona questions (Identity should take precedence over fuzzy chat) + const personaResp = this.checkPersonaQuestions(processedInput, inputText); + if (personaResp) return this.fillTemplate(personaResp, context); + + // 3. Fuzzy match + let bestMatch = null; + let highestScore = 0; + for (const [pattern, data] of Object.entries(this.memory)) { + const score = processor.calculateSimilarity(processedInput, pattern); + if (score > highestScore && score > 0.65) { + highestScore = score; + bestMatch = data; + } + } + if (bestMatch) { + return this.fillTemplate(this.pickResponse(bestMatch), context); + } + + return this.getFallback(); + } + + pickResponse(data) { + const style = this.persona.style || 'chill'; + const pool = data[style] || data['chill'] || data['default'] || data; + if (Array.isArray(pool)) return pool[Math.floor(Math.random() * pool.length)]; + return String(pool); + } + + /** + * Fill {name}, {job}, {birthday}, {age}, {residence}, {user_name} etc. + */ + fillTemplate(text, context = {}) { + if (!text) return this.getFallback(); + const p = this.persona; + const u = context.user || {}; + const now = new Date(); + const birthYear = p.birthday ? new Date(p.birthday).getFullYear() : null; + + return text + .replace(/\{name\}/g, p.name || 'Kasun') + .replace(/\{birthday\}/g, p.birthday || '?') + .replace(/\{job\}/g, p.job || 'web dev') + .replace(/\{residence\}/g, p.residence || 'Colombo') + .replace(/\{age\}/g, birthYear ? (now.getFullYear() - birthYear) : '?') + .replace(/\{user_name\}/g, u.name || 'machan') + .replace(/\{user_location\}/g, u.location || 'koheda?') + .replace(/\{user_job\}/g, u.job || '?'); + } + + checkPersonaQuestions(processedInput, rawInput = '') { + const text = processedInput.toLowerCase(); + const raw = rawInput.toLowerCase(); + + // Helper: check either raw or processed text + const has = (...kws) => kws.some(k => text.includes(k) || raw.includes(k)); + + // Priority order matters — check most specific first + if (has('oyage gana', 'about you', 'introduce yourself', 'tell me about')) + return 'mama {name}. {job} kenek. {residence} wala inne.'; + + // Name — must have name-specific trigger word + if (has('nama', 'name', 'kauda oya', 'oyage kauda', 'oyage nam', 'oyage name')) + return 'mage nama {name} machan.'; + + // Age / Birthday + if (has('bday', 'birthday', 'ipaduna', 'wayasa', 'age')) + return 'mama {age} wiye inne. bday eka {birthday}.'; + + // Residence — must be explicit about location + if (has('koheda inne', 'live in', 'koheda', 'residence', 'wasi')) + return 'mama {residence} wala inne machan.'; + + // Job — only if there's a clear job-asking keyword, NOT general "karanne" + if (has('oyage job', 'what is your job', 'weda karanne mokakda', 'job eka', 'profession', 'oyage weda')) + return 'mama {job} kenek machan.'; + + return null; + } + + getFallback() { + const style = this.persona.style || 'chill'; + const fallbacks = (this.persona.fallbacks && this.persona.fallbacks[style]) + || (this.persona.fallbacks && this.persona.fallbacks['chill']) + || ['moko kiyanne?', 'poddak hitapan', 'kiyanna aney']; + return fallbacks[Math.floor(Math.random() * fallbacks.length)]; + } + + learn(input, response, shouldSave = true) { + const processedInput = processor.process(input); + if (!processedInput || !response) return; + + const style = this.persona.style || 'chill'; + if (!this.memory[processedInput]) { + this.memory[processedInput] = {}; + } + if (!this.memory[processedInput][style]) { + this.memory[processedInput][style] = []; + } + if (!this.memory[processedInput][style].includes(response)) { + this.memory[processedInput][style].push(response); + } + if (shouldSave) this.saveData(); + } +} + +module.exports = new Brain(); diff --git a/QueenNoxi/brain/src/context.js b/QueenNoxi/brain/src/context.js new file mode 100644 index 0000000000000000000000000000000000000000..8fdcb8df13887710d899fd947af22fcb748e9f6b --- /dev/null +++ b/QueenNoxi/brain/src/context.js @@ -0,0 +1,93 @@ +/** + * context.js — Conversation Context Window + * + * Tracks: + * - Recent turns (last N messages from user + bot) + * - Active topics (what is being discussed) + * - Active intents + * - Emotional trend + */ + +class ContextWindow { + constructor(maxTurns = 10) { + this.maxTurns = maxTurns; + this.turns = []; // { role: 'user'|'bot', text, analysis?, ts } + this.activeTopics = []; // most recent topics discussed + this.activeIntents = []; // most recent intents + this.tone = 'neutral'; + this.waitingForAnswer = null; // if bot asked a question, track what it asked + } + + /** + * Add a user turn (with NLU analysis attached) + */ + addUserTurn(text, analysis) { + this.turns.push({ role: 'user', text, analysis, ts: Date.now() }); + if (this.turns.length > this.maxTurns * 2) this.turns.shift(); + + if (analysis) { + // Update active topics (prepend newest, keep last 3) + this.activeTopics = [...new Set([...analysis.topics, ...this.activeTopics])].slice(0, 3); + this.activeIntents = analysis.intents; + this.tone = analysis.tone; + } + } + + /** + * Add a bot turn + */ + addBotTurn(text) { + this.turns.push({ role: 'bot', text, ts: Date.now() }); + if (this.turns.length > this.maxTurns * 2) this.turns.shift(); + + // Track if bot asked a question (ends in ?) + if (text && text.includes('?')) { + this.waitingForAnswer = text; + } else { + this.waitingForAnswer = null; + } + } + + /** + * Get recent user messages (last N) + */ + getRecentUserMessages(n = 3) { + return this.turns.filter(t => t.role === 'user').slice(-n).map(t => t.text); + } + + /** + * Get last bot message + */ + getLastBotMessage() { + const botTurns = this.turns.filter(t => t.role === 'bot'); + return botTurns.length ? botTurns[botTurns.length - 1].text : null; + } + + /** + * Check if current message is likely a follow-up to an active topic + */ + isFollowUp(analysis) { + if (!analysis || !this.activeTopics.length) return false; + return analysis.topics.some(t => this.activeTopics.includes(t)); + } + + /** + * Check if the bot recently asked a question and is waiting for an answer + */ + isAnsweringBotQuestion() { + return !!this.waitingForAnswer; + } + + /** + * Wipe context (for fresh conversation) + */ + reset() { + this.turns = []; + this.activeTopics = []; + this.activeIntents = []; + this.tone = 'neutral'; + this.waitingForAnswer = null; + } +} + +module.exports = ContextWindow; diff --git a/QueenNoxi/brain/src/dictionary.js b/QueenNoxi/brain/src/dictionary.js new file mode 100644 index 0000000000000000000000000000000000000000..21fa6df31ada5ca3d822f8f76d8dc9c1f2e5d265 --- /dev/null +++ b/QueenNoxi/brain/src/dictionary.js @@ -0,0 +1,110 @@ +const fs = require('fs'); +const path = require('path'); +const processor = require('./processor'); + +class Dictionary { + constructor(dbPath) { + this.dbPath = dbPath || path.join(__dirname, '..', 'data', 'dictionary.json'); + this.data = {}; + this.load(); + } + + load() { + try { + if (fs.existsSync(this.dbPath)) { + this.data = JSON.parse(fs.readFileSync(this.dbPath, 'utf8')); + // Create a separate index for normalized keys + this.normalizedIndex = {}; + for (const key of Object.keys(this.data)) { + this.normalizedIndex[processor.process(key)] = key; + } + } + } catch (e) { + console.error('Dictionary load error:', e); + this.data = {}; + } + } + + /** + * Look up a word in the dictionary. + * Supports Sinhala script and Singlish (via internal normalization). + */ + lookup(word) { + if (!word) return null; + + const lower = word.toLowerCase().trim(); + const normalized = processor.process(word); + + // 1. Direct match (exact) + if (this.data[lower]) return this.data[lower]; + if (this.data[normalized]) return this.data[normalized]; + + // 2. Normalized index match (e.g. කොම්පියුටර් -> kompiutar -> computer) + if (this.normalizedIndex[normalized]) { + return this.data[this.normalizedIndex[normalized]]; + } + + // 3. Fuzzy search for similar words + let bestMatch = null; + let highestScore = 0; + + for (const [key, value] of Object.entries(this.data)) { + const score = processor.calculateSimilarity(normalized, processor.process(key)); + if (score > highestScore && score > 0.8) { + highestScore = score; + bestMatch = value; + } + } + + return bestMatch; + } + + /** + * Check if the message is a "what is X" type question + */ + isDictionaryQuery(text) { + const lower = text.toLowerCase(); + // Common dictionary query patterns + const patterns = [ + /mokakda (.*) kiyanne/i, + /what is (.*)/i, + /meaning of (.*)/i, + /arthaya (.*)/i, + /(.*) kiyanne mokakda/i, + /(.*) meaning/i + ]; + + for (const pat of patterns) { + const match = lower.match(pat); + if (match) { + // Clean punctuation from the extracted word + return match[1].trim().replace(/[?!,.]$/, ''); + } + } + return null; + } + + /** + * Format a dictionary entry into a human-like response + */ + formatResponse(word, entry) { + if (!entry) return null; + + if (typeof entry === 'string') { + return `"${word}" kiyanne: ${entry}`; + } + + let resp = `"${word}" gana mama hoyala beluva... `; + + if (entry.sin) resp += `Sinhala: ${entry.sin}. `; + if (entry.en) resp += `English: ${entry.en}. `; + if (entry.def) resp += `Definition: ${entry.def}. `; + if (entry.synonyms && entry.synonyms.length) { + resp += `Synonyms: ${entry.synonyms.slice(0, 3).join(', ')}.`; + } + + return resp; + } +} + +module.exports = Dictionary; diff --git a/QueenNoxi/brain/src/group.js b/QueenNoxi/brain/src/group.js new file mode 100644 index 0000000000000000000000000000000000000000..04dc415f849c831f94e2e6816278a525ee342f03 --- /dev/null +++ b/QueenNoxi/brain/src/group.js @@ -0,0 +1,115 @@ +const UserMemory = require('./memory'); +const StorageAdapter = require('./storage'); +const path = require('path'); + +class GroupSession { + constructor(options = {}) { + this.groupId = options.groupId || 'default_group'; + this.memory = options.memory; // global UserMemory instance for user lookup + this.members = new Set(); + this.recentMessages = []; + this.maxHistory = options.maxHistory || 50; + + const storagePath = options.mongoUri || (options.usersDb ? path.join(options.usersDb, `${this.groupId}.json`) : path.join(process.cwd(), 'database', `${this.groupId}.json`)); + this.storage = new StorageAdapter(storagePath, 'groups'); + } + + async init() { + await this.storage.init(); + const data = await this.storage.loadAll(); + this.recentMessages = data.history || []; + if (data.members) this.members = new Set(data.members); + } + + async save() { + await this.storage.saveAll({ + groupId: this.groupId, + members: [...this.members], + history: this.recentMessages, + lastUpdate: Date.now() + }); + } + + /** + * Register a member in this group + */ + addMember(userId) { + this.members.add(userId); + } + + /** + * Record a message in group context + */ + async addMessage(userId, text, from = null) { + this.addMember(userId); + const sender = from || userId; + const msg = { from: sender, text, ts: Date.now() }; + this.recentMessages.push(msg); + if (this.recentMessages.length > this.maxHistory) { + this.recentMessages.shift(); + } + + // Also update the global user memory if they are in this group + if (this.memory && sender !== 'bot') { + await this.memory.addHistory(sender, text, 'user'); + } + + await this.save(); + return msg; + } + + /** + * Get recent group conversation (last N messages) + */ + getContext(n = 10) { + return this.recentMessages.slice(-n); + } + + /** + * Check if a user was recently active in this group + */ + isRecentlyActive(userId, withinMs = 300000) { // 5 min + const now = Date.now(); + for (let i = this.recentMessages.length - 1; i >= 0; i--) { + const m = this.recentMessages[i]; + if (m.userId === userId && now - m.ts < withinMs) return true; + } + return false; + } + + /** + * Get all members the bot has seen in this group + */ + getMembers() { + return [...this.members]; + } + + /** + * Get the last message from a specific user + */ + getLastMessageFrom(userId) { + for (let i = this.recentMessages.length - 1; i >= 0; i--) { + if (this.recentMessages[i].userId === userId) { + return this.recentMessages[i]; + } + } + return null; + } + + /** + * Auto-train the brain from recent group messages + * (pass in a brain instance) + */ + autoTrainFromGroup(brain) { + const msgs = this.recentMessages; + for (let i = 0; i < msgs.length - 1; i++) { + const curr = msgs[i]; + const next = msgs[i + 1]; + if (curr.userId !== next.userId) { + brain.learn(curr.text, next.text); + } + } + } +} + +module.exports = GroupSession; diff --git a/QueenNoxi/brain/src/memory.js b/QueenNoxi/brain/src/memory.js new file mode 100644 index 0000000000000000000000000000000000000000..aa4ca15c7c071ac86322ed3c58c251b76be0816c --- /dev/null +++ b/QueenNoxi/brain/src/memory.js @@ -0,0 +1,204 @@ +const StorageAdapter = require('./storage'); + +class UserMemory { + constructor(dbPath) { + this.storage = new StorageAdapter(dbPath, 'users'); + this.users = {}; + } + + async init() { + await this.storage.init(); + this.users = await this.storage.loadAll(); + } + + async save() { + await this.storage.saveAll(this.users); + } + + /** + * Get a user's memory object. Creates one if it doesn't exist. + * @param {string} userId + */ + getUser(userId) { + if (!this.users[userId]) { + this.users[userId] = { + id: userId, + name: null, + age: null, + location: null, + job: null, + gender: null, + nickname: null, + facts: {}, // Free-form key-value facts + history: [], // Last N messages + lastSeen: null, + seenCount: 0 + }; + } else { + // Safety check for legacy or malformed files + if (!this.users[userId].facts) this.users[userId].facts = {}; + if (!this.users[userId].history) this.users[userId].history = []; + } + return this.users[userId]; + } + + /** + * Set a specific field on a user + */ + async setUserData(userId, field, value) { + const user = this.getUser(userId); + user[field] = value; + await this.save(); + } + + /** + * Set a free-form fact about a user + */ + async setFact(userId, key, value) { + const user = this.getUser(userId); + user.facts[key] = value; + await this.save(); + } + + /** + * Get a fact about a user + */ + getFact(userId, key) { + const user = this.getUser(userId); + return user.facts[key] || null; + } + + /** + * Record a message in the user's history (keep last 30) + */ + async addHistory(userId, text, from = 'user') { + const user = this.getUser(userId); + user.history.push({ text, from, ts: Date.now() }); + if (user.history.length > 30) user.history.shift(); + user.lastSeen = Date.now(); + user.seenCount = (user.seenCount || 0) + 1; + await this.save(); + } + + /** + * Auto-extract user info from message text (name, location, job, age) + * Returns any fields that were updated + */ + extractFromMessage(userId, text) { + const lower = text.toLowerCase().trim(); + const extracted = {}; + + // BLACKLIST: common Singlish words that look like a "match" but are NOT names/locations + const blacklistNames = new Set(['mama', 'machan', 'godak', 'poddak', 'hodai', 'supa', 'maru', 'ada', + 'kalin', 'dan', 'eka', 'api', 'ado', 'ayye', 'aiyo', 'ekath', 'kohomada', 'tani', 'ganna', + 'karanna', 'kiyanna', 'ne', 'da', 'ba', 'ok', 'lol', 'haha', 'bye', 'hi', 'hai', 'hello', + 'sorry', 'thanks', 'library', 'school', 'office', 'bus', 'train', 'gedara', 'weda', 'giia', + 'apahu', 'pass', 'fail', 'suba', 'subha', 'aiya', 'akka', 'nangi', 'malli', 'amma', 'thaththa']); + + // Name: ONLY on explicit "my name is X" or "mage nama X" (not bare "mama X") + const nameMatch = lower.match(/(?:my name is|mage nama|namayi)\s+([a-z]{2,20})(?:\s|$|,|\.)/i); + if (nameMatch && !blacklistNames.has(nameMatch[1].toLowerCase())) { + const name = this._capitalize(nameMatch[1]); + this.setUserData(userId, 'name', name); // Note: this is async but we don't await here to keep it non-blocking in NLU + extracted.name = name; + } + + // Age: "mage wayasa 22", "age 22", "i am 22 years" + const ageMatch = lower.match(/(?:mage wayasa|wayasa|age)\s+(\d{1,3})/i) + || lower.match(/i(?:'| a)m (\d{1,3})\s*y/i); + if (ageMatch) { + const age = parseInt(ageMatch[1]); + if (age > 5 && age < 100) { + this.setUserData(userId, 'age', age); // Background save + extracted.age = age; + } + } + + // Location: explicit "i live in X", "stay in X" — NOT bare "inne" + const locMatch = lower.match(/(?:i live in|stay in|wasi)\s+([a-z]{3,20})(?:\s|$|,)/i); + if (locMatch && !blacklistNames.has(locMatch[1].toLowerCase())) { + const loc = this._capitalize(locMatch[1].trim()); + this.setUserData(userId, 'location', loc); // Background save + extracted.location = loc; + } + + // Job: "i work as X", "weda karanne X", "i am a X" + const jobMatch = lower.match(/(?:i work as|weda karanne|i'm a|i am a)\s+([a-z\s]{3,30})(?:\s|$|,|\.)/i); + if (jobMatch) { + const job = this._capitalize(jobMatch[1].trim()); + this.setUserData(userId, 'job', job); // Background save + extracted.job = job; + } + + // Favorites: "my favorite X is Y", "mama kemati X walata" + // Improved regex with non-greedy match for the 'thing' + const favMatch = lower.match(/(?:my favorite|mama kamati|mama kemati)\s+([a-z\s]{2,20}?)\s+(?:is|kata|walata)\s+([a-z\s]{2,20})/i); + if (favMatch) { + const thing = favMatch[1].trim().replace(/\s+/g, '_'); + const value = favMatch[2].trim(); + this.setFact(userId, `favorite_${thing}`, value); + extracted[`favorite_${thing}`] = value; + } + + // Likes: "I like X", "mama X walata kamati" + const likeMatch = lower.match(/(?:i like|mama)\s+([a-z\s]{2,20})\s+(?:kamathi|kemati|like)/i) + || lower.match(/(?:i like)\s+([a-z\s]{2,20})/i); + if (likeMatch && !blacklistNames.has(likeMatch[1].trim())) { + const like = likeMatch[1].trim(); + this.setFact(userId, `likes_${like.replace(/\s+/g, '_')}`, true); + extracted.likes = like; + } + + return extracted; + } + + _capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + /** + * Compose a greeting using remembered data + */ + greet(userId) { + const user = this.getUser(userId); + if (user.name) { + const greetings = [ + `${user.name} machan, kohomada?`, + `Aiyo ${user.name}, ata ne?`, + `${user.name}! Oya thiyanawada?`, + `Oya ${user.name} ne! Kohomada?` + ]; + return greetings[Math.floor(Math.random() * greetings.length)]; + } + return null; + } + + /** + * Check if the user is asking the bot about what it knows of them + */ + memoryQuery(userId, text) { + const lower = text.toLowerCase(); + const user = this.getUser(userId); + if (!user) return null; + + if (lower.match(/mage nama|my name|oya thiyanawd|do you know me|oyata mata danne|mama gana|about me/)) { + const parts = []; + if (user.name) parts.push(`Oba ${user.name}.`); + if (user.age) parts.push(`Wayasa ${user.age}.`); + if (user.location) parts.push(`${user.location} wala inne.`); + if (user.job) parts.push(`${user.job} kenek.`); + + // Add facts/favorites + const favs = Object.keys(user.facts).filter(k => k.startsWith('favorite_')); + if (favs.length) { + const f = favs[0]; + parts.push(`${f.replace('favorite_', '').replace('_', ' ')} eka ${user.facts[f]}.`); + } + + if (parts.length) return parts.join(' '); + } + return null; + } +} + +module.exports = UserMemory; diff --git a/QueenNoxi/brain/src/nlu.js b/QueenNoxi/brain/src/nlu.js new file mode 100644 index 0000000000000000000000000000000000000000..0e4f24f175d14bf88df8adee0709995fa2c46dab --- /dev/null +++ b/QueenNoxi/brain/src/nlu.js @@ -0,0 +1,279 @@ +/** + * nlu.js — Natural Language Understanding Engine + * + * Makes the bot understand: + * 1. Long paragraphs (segmented into individual thoughts) + * 2. Intent detection (question, greeting, complaint, story, tease, etc.) + * 3. Keyword/topic extraction (what is the user REALLY talking about?) + * 4. Emotional tone detection (happy, sad, angry, excited) + * 5. Context window (what was just talked about?) + */ + +// ──────────────────────────────────────────────────────────────────────────── +// KNOWLEDGE MAPS +// ──────────────────────────────────────────────────────────────────────────── + +const INTENT_PATTERNS = { + greeting: [/^(hi|hello|hai|hey|ata ne|kohomada|ayye|subha udesanak|good morning|gm)\b/i], + farewell: [/\b(bye|goodnight|subha rathriyak|gn|yanawa|later|ttyl|cya)\b/i], + question: [/\b(mokakda|kohomada|kauda|kawda|monada|koheda|kiia|when|why|how|what|who|where|when|da\?|neda\?|da$)\b/i, /\?/], + complaint: [/\b(dukei|duk|problem|issue|kapuna|asathutak|baya|adura|roga|hondatama na|hodatma na|barei)\b/i], + happy: [/\b(supa|ela|elatama|maru|hodai|santhosai|love|like|enjoy|ado hehe|haha|lol|:d|😂|😍|❤️|🔥)\b/i], + sad: [/\b(duk|sad|cry|neth|balanna bari|tani|ekai|adare na|alone|miss|😢|💔|😞)\b/i], + angry: [/\b(eka nan|apahu|nonsense|baena|bae|budukarapu|waeda na|kelawera|okama|angry|mad|😠|🤬)\b/i], + excited: [/\b(!{2,}|omg|aiyoo|wait|seriously|ow ow|neda|😱|🤩|elaaaaaa|supaaa)\b/i], + love: [/\b(love|adare|miss|baby|bae|heart|❤️|😘|💕|💖|xoxo)\b/i], + story: [/^(ada|kalin|ekl|eka dawas|meka une|eeka giia|eka thamath|api giia|api gaththee|api hitiye)\b/i], + thanks: [/\b(thanks|thank you|sthuthi|stuthi|awlak na|np|🙏)\b/i], + request: [/\b(karanna puluwan|help|support|explain|kiyanna|denna|ganna|hadanna)\b/i], + selfIntro: [/\b(my name is|mama|mage nama|i am|i'm|iam)\b/i], + teasing: [/\b(hehe|hihi|🤭|😜|😏|;|patta|wela|joke|haha)\b/i] +}; + +const TOPIC_KEYWORDS = { + relationship: ['love', 'adare', 'gf', 'bf', 'girlfriend', 'boyfriend', 'miss', 'marry', 'nikaya', 'prema', 'date', 'kiss'], + food: ['kema', 'rice', 'food', 'eat', 'restaurant', 'kanne', 'bathi', 'curry', 'pizza', 'kottu', 'hoppers', 'string hoppers'], + work: ['job', 'weda', 'office', 'project', 'deadline', 'meeting', 'boss', 'salary', 'work', 'weda karanne', 'interview'], + school: ['school', 'university', 'exam', 'pariksha', 'class', 'lecture', 'teacher', 'student', 'ol', 'al', 'degree'], + games: ['game', 'play', 'pubg', 'fifa', 'cricket', 'sport', 'win', 'lose', 'match'], + weather: ['rain', 'vasse', 'hot', 'climate', 'weather', 'udawa', 'ginithapu', 'thawath vasse'], + transport: ['bus', 'train', 'uber', 'taxi', 'bike', 'car', 'traffic', 'jam', 'yanna', 'enna'], + money: ['money', 'salary', 'loan', 'borrow', 'spend', 'expensive', 'cheap', 'hari ganan', 'pisa', 'rata'], + health: ['sick', 'roga', 'hospital', 'doctor', 'medicine', 'adura', 'pain', 'headache', 'fever', 'gedara inne'], + tech: ['phone', 'laptop', 'computer', 'internet', 'data', 'app', 'code', 'website', 'software', 'hack'], + family: ['amma', 'thaththa', 'mom', 'dad', 'akka', 'malli', 'nangi', 'brother', 'sister', 'family', 'gedara'], + social: ['party', 'meet', 'hangout', 'friend', 'machan', 'api', 'mall', 'trip', 'outing', 'picnic'], + srilanka: ['sl', 'srilanka', 'colombo', 'kandy', 'galle', 'jaffna', 'ratnapura', 'lanka'], + feelings: ['hithenne', 'feel', 'think', 'baya', 'happy', 'sad', 'cry', 'emotion'], +}; + +const TOPIC_RESPONSES = { + relationship: { + chill: ['ada relationship gana dan hodatama katha karanna epa machan hehe', 'adare gana kiyannako, mokada?', 'ow ow, gf/bf gana da?'], + sexy: ['ooh relationship gana da? oya kauda lucky one?', 'adare gana oya hithanawa da?'], + hot: ['adare gana da? oyata special kenek innawada?', 'ooh oya kiyanne relationship gana da?'] + }, + food: { + chill: ['ada kema supa da?!', 'mama dan godak kapannawa, kottu gana kiyanne?', 'ae machan, mama godak hungry', 'abaa kema kiyannako!'], + professional: ['Hoda keme irunawada?'] + }, + work: { + chill: ['aiyo weda wada, poddak relax karaganna machan', 'deadline gana stress wena epa', 'weda wenawata kalin api katha kaapuwa na?'], + professional: ['Project eka kohomada giya? Help ona nam kiyanna.'] + }, + school: { + chill: ['exam gana stress wena epa machan, blew ekak gamu', 'hondatama study karaganna, ekath mama help karanna puluwan', 'ow pariksha gana da? kalin kiyanna thibathe'], + professional: ['Study karala hodatama exams passweida?'] + }, + games: { + chill: ['ado PUBG da?! mama oka ban khelannawa', 'game gana kiyannako, api multiplayer kaamu', 'cricket match baluwada? maru giia na?'], + hot: ['game karala jadi wunasama prize karannako chat eka'] + }, + weather: { + chill: ['ada vasse da koheda?', 'aiyo ginithapu, AC eka dala inne', 'ae machan vasse aiye gedara hitapu', 'Colombo wala ginithapu, koheda oya?'], + }, + transport: { + chill: ['bus eke traffic jam da? ae ae normal', 'Uber gahanna, bus pain wena epaa machan'], + }, + money: { + chill: ['aiyo machan pisa gana duku', 'salary eka waidi wenna one', 'mae ithin savings poddak', 'hari ganan aney life'], + }, + health: { + chill: ['aiyo machan! hondatama hoddaganna', 'roga nathnam santhosai', 'hospital giiewda? kohomada?', 'adura na?! gedara hitapan'], + }, + tech: { + chill: ['tech gana naththam mama kiyannawa machan', 'code gahala trouble da? kiyanna', 'phone da laptop da?'], + professional: ['Tech stack eka mokakda use karanne?'] + }, + family: { + chill: ['family gana hari supa machan', 'amma thaththa kohomada?', 'gedara gihilla aawada?'], + }, + social: { + chill: ['outing gamu! koheda?', 'party ekak?! enna enna', 'machan ahala hitiye api meet weida'], + hot: ['outing gamu, just ape dokomath'] + }, + srilanka: { + chill: ['mama SL eke podi balla', 'SL game elatama', 'koheda inne SL wala?'], + }, + feelings: { + chill: ['mokakda hithenne? kiyanna machan', 'awl na ado, mama innawa ne?', 'mama thiyanawa obage kotaswa'], + sexy: ['kiyanna aney, mama ahannawa', 'oya feel wenne mama danne na ne?'], + } +}; + +// ──────────────────────────────────────────────────────────────────────────── +// AUTO-STYLE SIGNAL MAPS +// ──────────────────────────────────────────────────────────────────────────── + +// Each style has signal words/patterns. Weighted scoring picks the winner. +const STYLE_SIGNALS = { + professional: { + weight: 1, + patterns: [ + /\b(project|meeting|deadline|report|client|sir|madam|please|thank you|regards|schedule|proposal|discuss|email|call|agenda|budget|salary|office|manager|team)\b/i + ] + }, + sexy: { + weight: 1, + patterns: [ + /\b(baby|babe|honey|cutie|beautiful|gorgeous|hot|sexy|miss you|come over|alone|cuddle|kiss|hug|adore|😍|😘|💋|🥵|😏|😉|flirt|date|special|crush)\b/i + ] + }, + hot: { + weight: 1, + patterns: [ + /\b(!!|come|enna|langata|tonight|now|asap|jld|fast|quick|urgent|secret|naughty|dark|🔥|😈|🥵|💦|need you|want you|right now)\b/i, + /!{2,}/ + ] + }, + chill: { + weight: 0.5, // default — lower weight so it loses to specific signals + patterns: [ + /\b(machan|ado|ayye|poddak|nikang|hehe|lol|supa|ela|maru|chilling|relax|nothing|nothing much|nikan)\b/i + ] + } +}; + +// ──────────────────────────────────────────────────────────────────────────── +// NLU CLASS +// ──────────────────────────────────────────────────────────────────────────── + +class NLU { + + /** + * Segment a long paragraph into individual thought-sentences + * Splits on: . ! ? newlines, and Sinhala sentence enders + */ + segment(text) { + // Split on sentence terminators + const raw = text.split(/[.!?\n\r।ෙ]+/).map(s => s.trim()).filter(s => s.length > 1); + // Also split on conjunctions that indicate a change of subject + const sentences = []; + for (const s of raw) { + // If sentence has comma + subject change indicator, further split + const subParts = s.split(/\s*,\s*(?=(?:mama|oya|api|ekka|eka|dan|kalin|ekath|ado|machan)\b)/i); + for (const p of subParts) { + if (p.trim().length > 1) sentences.push(p.trim()); + } + } + return sentences.length ? sentences : [text.trim()]; + } + + /** + * Detect intents from a sentence + * @returns {string[]} list of matched intents + */ + detectIntents(text) { + const found = []; + for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) { + for (const pat of patterns) { + if (pat.test(text)) { found.push(intent); break; } + } + } + return found.length ? found : ['unknown']; + } + + /** + * Extract topics from text + * @returns {string[]} matched topic names + */ + extractTopics(text) { + const lower = text.toLowerCase(); + const found = []; + for (const [topic, keywords] of Object.entries(TOPIC_KEYWORDS)) { + if (keywords.some(kw => lower.includes(kw))) found.push(topic); + } + return found; + } + + /** + * Get a topic-based response + */ + getTopicResponse(topic, style = 'chill') { + const bank = TOPIC_RESPONSES[topic]; + if (!bank) return null; + const pool = bank[style] || bank['chill'] || null; + if (!pool || !pool.length) return null; + return pool[Math.floor(Math.random() * pool.length)]; + } + + /** + * Detect the emotional tone of a message + */ + detectTone(text) { + if (INTENT_PATTERNS.happy.some(p => p.test(text))) return 'happy'; + if (INTENT_PATTERNS.sad.some(p => p.test(text))) return 'sad'; + if (INTENT_PATTERNS.angry.some(p => p.test(text))) return 'angry'; + if (INTENT_PATTERNS.excited.some(p => p.test(text))) return 'excited'; + if (INTENT_PATTERNS.love.some(p => p.test(text))) return 'love'; + return 'neutral'; + } + + /** + * Generate a tone-aware reaction prefix + */ + toneReaction(tone) { + const reactions = { + happy: ['supa aney!', 'elatama!', 'maru!', 'hodai!'], + sad: ['aiyo duka aney...', 'awl na machan...', 'duk gannaka eppa ne?'], + angry: ['ok ok, chill down machan', 'relax ado...', 'aiyo mokada wune?'], + excited: ['ado!', 'wait wait!', 'seriously?!'], + love: ['aww hehe', 'ooh ooh', '😊'], + neutral: [] + }; + const pool = reactions[tone] || []; + return pool.length ? pool[Math.floor(Math.random() * pool.length)] : ''; + } + + /** + * Auto-detect the best response style based on message content. + * Scores each style by how many signals match, returns the top scorer. + * @param {string} text + * @returns {string} style name + */ + detectAutoStyle(text) { + const scores = {}; + for (const [style, config] of Object.entries(STYLE_SIGNALS)) { + let score = 0; + for (const pat of config.patterns) { + const matches = text.match(pat); + if (matches) score += matches.length * config.weight; + } + scores[style] = score; + } + // Return style with highest score; default to chill + const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0]; + return (best && best[1] > 0) ? best[0] : 'chill'; + } + + /** + * Full analysis of any message (short or long paragraph) + * Returns a structured analysis object + */ + analyze(text) { + const sentences = this.segment(text); + const intents = new Set(); + const topics = new Set(); + const tone = this.detectTone(text); + + for (const s of sentences) { + this.detectIntents(s).forEach(i => intents.add(i)); + this.extractTopics(s).forEach(t => topics.add(t)); + } + + return { + original: text, + sentences, + intents: [...intents], + topics: [...topics], + tone, + detectedStyle: this.detectAutoStyle(text), + isLong: sentences.length > 1, + isQuestion: intents.has('question'), + isGreeting: intents.has('greeting'), + isFarewell: intents.has('farewell') + }; + } +} + +module.exports = new NLU(); diff --git a/QueenNoxi/brain/src/processor.js b/QueenNoxi/brain/src/processor.js new file mode 100644 index 0000000000000000000000000000000000000000..a8425c307fb4adeeb32e7e09ef2a3c3a4caf21b2 --- /dev/null +++ b/QueenNoxi/brain/src/processor.js @@ -0,0 +1,162 @@ +const fs = require('fs'); +const path = require('path'); + +class LanguageProcessor { + constructor() { + this.slang = {}; + this.phoneticMap = {}; // Custom word mappings (sinhala script -> singlish) + this.loadSlang(); + this.loadPhoneticMap(); + this.initTransliteration(); + } + + loadSlang() { + try { + const slangPath = path.join(__dirname, '..', 'data', 'slang.json'); + if (fs.existsSync(slangPath)) { + this.slang = JSON.parse(fs.readFileSync(slangPath, 'utf8')); + } + } catch (e) { } + } + + loadPhoneticMap() { + try { + const mapPath = path.join(__dirname, '..', 'data', 'phonetic_map.json'); + if (fs.existsSync(mapPath)) { + this.phoneticMap = JSON.parse(fs.readFileSync(mapPath, 'utf8')); + } + } catch (e) { } + } + + savePhoneticMap() { + try { + const mapPath = path.join(__dirname, '..', 'data', 'phonetic_map.json'); + const dir = path.dirname(mapPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(mapPath, JSON.stringify(this.phoneticMap, null, 2), 'utf8'); + } catch (e) { + console.error('Error saving phonetic map:', e); + } + } + + initTransliteration() { + this.vowels = { + 'අ': 'a', 'ආ': 'aa', 'ඇ': 'ae', 'ඈ': 'aee', 'ඉ': 'i', 'ඊ': 'ii', + 'උ': 'u', 'ඌ': 'uu', 'එ': 'e', 'ඒ': 'ee', 'ඔ': 'o', 'ඕ': 'oo', + 'ඓ': 'ai', 'ඖ': 'au' + }; + this.consonants = { + 'ක': 'k', 'ඛ': 'kh', 'ග': 'g', 'ඝ': 'gh', 'ඞ': 'ng', 'ඟ': 'ng', + 'ච': 'c', 'ඡ': 'ch', 'ජ': 'j', 'ඣ': 'jh', 'ඤ': 'ny', 'ඥ': 'gn', + 'ට': 't', 'ඨ': 'th', 'ඩ': 'd', 'ඪ': 'dh', 'ණ': 'n', 'ඬ': 'nd', + 'ත': 't', 'ථ': 'th', 'ද': 'd', 'ධ': 'dh', 'න': 'n', 'ඳ': 'nd', + 'ප': 'p', 'ඵ': 'ph', 'බ': 'b', 'භ': 'bh', 'ම': 'm', 'ඹ': 'mb', + 'ය': 'y', 'ර': 'r', 'ල': 'l', 'ව': 'v', 'ශ': 'sh', 'ෂ': 'sh', + 'ස': 's', 'හ': 'h', 'ළ': 'l', 'ෆ': 'f' + }; + this.pilli = { + '්': '', 'ා': 'aa', 'ැ': 'ae', 'ෑ': 'aee', 'ි': 'i', 'ී': 'ii', + 'ු': 'u', 'ූ': 'uu', 'ෘ': 'ru', 'ෙ': 'e', 'ේ': 'ee', 'ෛ': 'ai', + 'ො': 'o', 'ෝ': 'oo', 'ෞ': 'au', 'ං': 'n' + }; + } + + sinhalaToSinglish(text) { + let result = ''; + for (let i = 0; i < text.length; i++) { + const char = text[i]; + + if (this.vowels[char]) { + result += this.vowels[char]; + } else if (this.consonants[char]) { + let next = text[i + 1]; + let singlish = this.consonants[char]; + if (next && this.pilli[next] !== undefined) { + singlish += this.pilli[next]; + i++; // skip pilla + } else { + singlish += 'a'; // inherent vowel + } + result += singlish; + } else { + result += char; + } + } + return result; + } + + /** + * Normalizes text for matching. + * Handles mixed Sinhala/Singlish, slang expansion, and phonetic cleanup. + */ + process(text) { + if (!text) return ''; + + // 1. Convert Sinhala script to Singlish (using map if available) + const words = text.split(/\s+/); + const processedWords = words.map(word => { + // Handle phonetic map (Sinhala -> Singlish) + if (this.phoneticMap[word]) return this.phoneticMap[word]; + + // Handle rule-based translit + return this.sinhalaToSinglish(word); + }); + + let processed = processedWords.join(' ').toLowerCase().trim(); + + // 2. Expand common slang (Singlish -> Real Concept) + const finalWords = processed.split(/\s+/); + processed = finalWords.map(word => this.slang[word] || word).join(' '); + + // 3. Simple phonetic normalization + processed = this.normalizePhonetics(processed); + + return processed; + } + + normalizePhonetics(text) { + return text + .replace(/sh/g, 's') + .replace(/th/g, 't') + .replace(/dh/g, 'd') + .replace(/aa/g, 'a') + .replace(/ee/g, 'i') + .replace(/oo/g, 'u') + .replace(/y/g, 'i') + .replace(/w/g, 'v') + .replace(/([a-z])\1+/g, '$1') // remove double letters + .replace(/[^a-z0-9\s]/g, ''); // remove non-alphanumeric + } + + /** + * Calculates similarity between two strings (Levenshtein Distance) + */ + calculateSimilarity(s1, s2) { + if (s1 === s2) return 1.0; + + const len1 = s1.length; + const len2 = s2.length; + if (len1 === 0) return 0.0; + if (len2 === 0) return 0.0; + + const matrix = []; + for (let i = 0; i <= len1; i++) matrix[i] = [i]; + for (let j = 0; j <= len2; j++) matrix[0][j] = j; + + for (let i = 1; i <= len1; i++) { + for (let j = 1; j <= len2; j++) { + const cost = s1[i - 1] === s2[j - 1] ? 0 : 1; + matrix[i][j] = Math.min( + matrix[i - 1][j] + 1, + matrix[i][j - 1] + 1, + matrix[i - 1][j - 1] + cost + ); + } + } + + const distance = matrix[len1][len2]; + return 1.0 - (distance / Math.max(len1, len2)); + } +} + +module.exports = new LanguageProcessor(); diff --git a/QueenNoxi/brain/src/storage.js b/QueenNoxi/brain/src/storage.js new file mode 100644 index 0000000000000000000000000000000000000000..b49bb7d053cef8a5626c17d5cc6bc07bc231c1c3 --- /dev/null +++ b/QueenNoxi/brain/src/storage.js @@ -0,0 +1,135 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * StorageAdapter provides a unified interface for local JSON and MongoDB. + * For local 'users' context, it saves individual files per user. + */ +class StorageAdapter { + /** + * @param {string} connectionStr - Local path (file or dir) or MongoDB URI + * @param {string} context - 'users' | 'brain' | 'common' + */ + constructor(connectionStr, context = 'common') { + this.connectionStr = connectionStr; + this.context = context; + this.isMongo = connectionStr.startsWith('mongodb'); + this.client = null; + this.db = null; + } + + async init() { + if (this.isMongo) { + try { + const { MongoClient } = require('mongodb'); + this.client = new MongoClient(this.connectionStr); + await this.client.connect(); + this.db = this.client.db(); + console.log(`📡 Storage: Connected to MongoDB [${this.context}]`); + } catch (e) { + console.error(`❌ Storage: Failed to connect to MongoDB. Falling back to local storage.`); + this.isMongo = false; + } + } else if (this.context === 'users') { + // Local Users should use a directory + if (!fs.existsSync(this.connectionStr)) { + fs.mkdirSync(this.connectionStr, { recursive: true }); + } + } + return this; + } + + /** + * Load all data for this context + */ + async loadAll() { + if (this.isMongo && this.db) { + const collection = this.db.collection(this.context); + if (this.context === 'users') { + const results = await collection.find({}).toArray(); + const out = {}; + results.forEach(r => { + const { _id, ...data } = r; + out[_id] = data; + }); + return out; + } else { + const doc = await collection.findOne({ _id: 'master' }); + return doc ? doc.data : {}; + } + } else { + // Local FS + if (this.context === 'users') { + const dir = this.connectionStr; + if (!fs.existsSync(dir)) return {}; + const files = fs.readdirSync(dir).filter(f => f.endsWith('.json')); + const out = {}; + files.forEach(f => { + try { + const content = fs.readFileSync(path.join(dir, f), 'utf8'); + out[f.replace('.json', '')] = JSON.parse(content); + } catch (e) { } + }); + return out; + } else { + try { + if (fs.existsSync(this.connectionStr)) { + return JSON.parse(fs.readFileSync(this.connectionStr, 'utf8')); + } + } catch (e) { } + return {}; + } + } + } + + /** + * Save all data (Bulk) + */ + async saveAll(data) { + if (this.isMongo && this.db) { + const collection = this.db.collection(this.context); + if (this.context === 'users') { + for (const userId in data) { + await collection.updateOne({ _id: userId }, { $set: data[userId] }, { upsert: true }); + } + } else { + await collection.updateOne({ _id: 'master' }, { $set: { data, updatedAt: Date.now() } }, { upsert: true }); + } + } else { + if (this.context === 'users') { + const dir = this.connectionStr; + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + for (const userId in data) { + const filePath = path.join(dir, `${userId}.json`); + fs.writeFileSync(filePath, JSON.stringify(data[userId], null, 2), 'utf8'); + } + } else { + const dir = path.dirname(this.connectionStr); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(this.connectionStr, JSON.stringify(data, null, 2), 'utf8'); + } + } + } + + /** + * Save a single item (highly optimized for users context) + */ + async saveItem(id, itemData) { + if (this.isMongo && this.db) { + const collection = this.db.collection(this.context); + await collection.updateOne({ _id: id }, { $set: itemData }, { upsert: true }); + } else { + if (this.context === 'users') { + const dir = this.connectionStr; + const filePath = path.join(dir, `${id}.json`); + fs.writeFileSync(filePath, JSON.stringify(itemData, null, 2), 'utf8'); + } else { + const all = await this.loadAll(); + all[id] = itemData; + await this.saveAll(all); + } + } + } +} + +module.exports = StorageAdapter; diff --git a/QueenNoxi/brain/src/trainer.js b/QueenNoxi/brain/src/trainer.js new file mode 100644 index 0000000000000000000000000000000000000000..511046eb998b4e8fc2f14a44e1d7440250387a1e --- /dev/null +++ b/QueenNoxi/brain/src/trainer.js @@ -0,0 +1,168 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const Brain = require('./brain'); + +/** + * Universal training tool for Sinhala dataset ingestion. + * Supports JSON, TSV, CSV, Parallel Text, and Large-Scale Streaming. + */ +class Trainer { + /** + * Train from an array of objects + */ + static trainData(data, defaultStyle = 'chill') { + if (!Array.isArray(data)) return 0; + const oldStyle = (Brain.persona && Brain.persona.style) || 'chill'; + let count = 0; + + console.log(`🧠 Processing ${data.length} patterns...`); + + for (const item of data) { + const input = item.input || item.q || item.question || item.text || item.Phrase; + const response = item.response || item.a || item.answer || item.reply || item.Response; + const style = item.style || defaultStyle; + + if (input && response) { + const s = ['chill', 'professional', 'sexy', 'hot'].includes(style) ? style : defaultStyle; + Brain.setPersona({ style: s }); + // Bulk training: disable auto-save + Brain.learn(input.toString(), response.toString(), false); + count++; + } + + if (count > 0 && count % 10000 === 0) { + console.log(`...ingested ${count} patterns`); + } + } + + // Final Save for bulk data + console.log('💾 Saving brain to disk (this may take a moment)...'); + Brain.saveData(); + + Brain.setPersona({ style: oldStyle }); + return count; + } + + /** + * Train from a raw string + */ + static trainString(content, format = 'json', defaultStyle = 'chill') { + const fmt = format.toLowerCase().replace('.', ''); + if (fmt === 'json') { + try { return this.trainData(JSON.parse(content), defaultStyle); } + catch (e) { return 0; } + } else if (fmt === 'tsv' || fmt === 'csv' || fmt === 'txt') { + const lines = content.split(/\r?\n/); + let count = 0; + const delimiter = (fmt === 'tsv' || fmt === 'txt') ? /\t+/ : ','; + const data = []; + + for (const line of lines) { + if (!line.trim()) continue; + const columns = line.split(delimiter); + if (columns.length >= 2) { + const col1 = columns[0].trim().replace(/^"|"$/g, ''); + const col2 = columns[1].trim().replace(/^"|"$/g, ''); + if (col1.toLowerCase() === 'question' || col1.toLowerCase() === 'phrase' || col1.toLowerCase() === 'singlish') continue; + data.push({ q: col1, a: col2, style: defaultStyle }); + } + } + return this.trainData(data, defaultStyle); + } + return 0; + } + + /** + * Streaming Trainer for LARGE files (100MB+) + * Supports Tab, Comma, and custom WSD format. + */ + static async trainLarge(filePath, defaultStyle = 'chill') { + const absPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath); + if (!fs.existsSync(absPath)) return 0; + + // Detect encoding + const buffer = fs.readFileSync(absPath, { length: 500 }); + let encoding = 'utf8'; + if (buffer[0] === 0xFF && buffer[1] === 0xFE) encoding = 'utf16le'; + else if (buffer[0] === 0xFE && buffer[1] === 0xFF) encoding = 'utf16be'; + else if (buffer.some(b => b === 0)) encoding = 'utf16le'; + + const processor = require('./processor'); + const fileStream = fs.createReadStream(absPath, { encoding }); + + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + + console.log(`🚀 Streaming training from ${path.basename(filePath)} (${encoding})...`); + let count = 0; + + for await (const line of rl) { + if (!line.trim()) continue; + + let sinhala, singlish; + + // Pattern 1: WSD Format + const wsdMatch = line.match(/^Word:\s*([^,]+),\s*Sinhala\s*Words:\s*\[(.*)\]/i); + if (wsdMatch) { + singlish = wsdMatch[1].trim(); + const variants = wsdMatch[2].split(',').map(v => v.trim().replace(/['"\[\]]/g, '')); + variants.forEach(v => { + if (v && /[\u0D80-\u0DFF]/.test(v)) { + processor.phoneticMap[v] = singlish; + count++; + } + }); + continue; + } + + // Pattern 2: Swa Bhasha / test1 + const parts = line.split(/[\t,/]/); + if (parts.length >= 2) { + const p1 = parts[0].trim().replace(/^"|"$/g, ''); + const p2 = parts[1].trim().replace(/^"|"$/g, ''); + + const isP1S = /[\u0D80-\u0DFF]/.test(p1); + const isP2S = /[\u0D80-\u0DFF]/.test(p2); + + if (isP1S && !isP2S) { sinhala = p1; singlish = p2; } + else if (!isP1S && isP2S) { sinhala = p2; singlish = p1; } + else if (!isP1S && !isP2S) { Brain.learn(p1, p2, false); count++; } + + if (sinhala && singlish) { + processor.phoneticMap[sinhala] = singlish; + count++; + } + } + + if (count > 0 && count % 50000 === 0) { + console.log(`...memorized ${count} phonetic patterns`); + } + } + + processor.savePhoneticMap(); + Brain.saveData(); // Save brain after large stream as well + return count; + } + + /** + * Legacy File Trainer + */ + static trainFile(filePath, defaultStyle = 'chill') { + const absPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath); + if (!fs.existsSync(absPath)) return 0; + const buffer = fs.readFileSync(absPath); + let content; + if (buffer[0] === 0xFF && buffer[1] === 0xFE) content = buffer.toString('utf16le'); + else if (buffer[0] === 0xFE && buffer[1] === 0xFF) content = buffer.toString('utf16be'); + else { + const isUtf16 = buffer.slice(0, 500).some(b => b === 0); + content = isUtf16 ? buffer.toString('utf16le') : buffer.toString('utf8'); + } + return this.trainString(content, path.extname(absPath).replace('.', ''), defaultStyle); + } +} + +module.exports = Trainer; diff --git a/QueenNoxi/config.py b/QueenNoxi/config.py new file mode 100644 index 0000000000000000000000000000000000000000..c3e219e6ccbf822aaeea2f6c53a452e9d5fd7646 --- /dev/null +++ b/QueenNoxi/config.py @@ -0,0 +1,113 @@ +import os + + +def _int_list(env_key: str, default: str = "") -> list: + """Parse a space-separated list of integers from an env var, ignoring comments.""" + raw = os.environ.get(env_key, default).strip() + if not raw: + return [] + # Remove comments starting with # + raw = raw.split("#")[0].strip() + if not raw: + return [] + + result = [] + for x in raw.split(): + try: + result.append(int(x)) + except ValueError: + continue + return result + + +def _bool(env_key: str, default: bool = False) -> bool: + val = os.environ.get(env_key, str(default)).strip().lower() + return val in ("1", "true", "yes") + + +def _str_list(env_key: str, default: str = "") -> list: + """Parse a space-separated list of strings from an env var, ignoring comments.""" + raw = os.environ.get(env_key, default).strip() + if not raw: + return [] + # Remove comments starting with # + raw = raw.split("#")[0].strip() + if not raw: + return [] + return raw.split() + + +class Config: + LOGGER = True + + # ── Telegram API credentials ────────────────────────────────────────── + API_ID = int(os.environ.get("API_ID", 0)) or None + API_HASH = os.environ.get("API_HASH", "") + TOKEN = os.environ.get("TOKEN", "") + + # ── Owner / Privileged users ────────────────────────────────────────── + # OWNER_IDS: space-separated Telegram user IDs (all will have owner access) + # Example: OWNER_IDS="123456 789012" + OWNER_IDS = _int_list("OWNER_IDS") + + # Sudo / dragons (space-separated IDs) + DRAGONS = _int_list("DRAGONS") + DEV_USERS = _int_list("DEV_USERS", "2145093972") + DEMONS = _int_list("DEMONS") + TIGERS = _int_list("TIGERS") + WOLVES = _int_list("WOLVES") + + # ── Bot identity ────────────────────────────────────────────────────── + # Set these explicitly on HuggingFace (fetched from Telegram API if blank) + BOT_NAME = os.environ.get("BOT_NAME", "QueenNoxi") + BOT_USERNAME = os.environ.get("BOT_USERNAME", "") # without @ + + # ── Chats / channels ────────────────────────────────────────────────── + SUPPORT_CHAT = os.environ.get("SUPPORT_CHAT", "QueenNoxiSupport") # without @ + EVENT_LOGS = os.environ.get("EVENT_LOGS", "") # chat ID for log channel + BL_CHATS = _int_list("BL_CHATS") # blacklisted chat IDs + + # ── Media ───────────────────────────────────────────────────────────── + START_IMG = os.environ.get("START_IMG", "") + + # ── Databases ───────────────────────────────────────────────────────── + MONGO_DB_URI = os.environ.get("MONGO_DB_URI", "") + CHATDB_URL = os.environ.get("CHATDB_URL", "") + + DATABASE_URL = os.environ.get("DATABASE_URL", "") # PostgreSQL / elephantsql + + + # ── API keys ────────────────────────────────────────────────────────── + CASH_API_KEY = os.environ.get("CASH_API_KEY", "") + TIME_API_KEY = os.environ.get("TIME_API_KEY", "") + TENOR_API_KEY = os.environ.get("TENOR_API_KEY", "") + + + # ── Behaviour flags ─────────────────────────────────────────────────── + ALLOW_CHATS = _bool("ALLOW_CHATS", True) + ALLOW_EXCL = _bool("ALLOW_EXCL", False) + DEL_CMDS = _bool("DEL_CMDS", False) + INFOPIC = _bool("INFOPIC", True) + STRICT_GBAN = _bool("STRICT_GBAN", True) + + # ── Module loading ──────────────────────────────────────────────────── + LOAD = _str_list("LOAD") + NO_LOAD = _str_list("NO_LOAD") + + # ── Performance ─────────────────────────────────────────────────────── + WORKERS = int(os.environ.get("WORKERS", 8)) + TEMP_DOWNLOAD_DIRECTORY = os.environ.get("TEMP_DOWNLOAD_DIRECTORY", "./") + + +class Production(Config): + LOGGER = True + + +class Development(Config): + LOGGER = True + # Override any value here for local dev without touching env + # Example: + # API_ID = 12345 + # API_HASH = "abcdef..." + # TOKEN = "bot_token_here" + # OWNER_IDS = [123456789] \ No newline at end of file diff --git a/QueenNoxi/events.py b/QueenNoxi/events.py new file mode 100644 index 0000000000000000000000000000000000000000..f85cf1f7a958623ac9930b82e94ef9f44b714af2 --- /dev/null +++ b/QueenNoxi/events.py @@ -0,0 +1,179 @@ +import inspect +import re +from pathlib import Path + +from pymongo import MongoClient +from telethon import events + +from QueenNoxi import MONGO_DB_URI, telethn + +client = MongoClient() +client = MongoClient(MONGO_DB_URI) +db = client["QueenNoxi"] +gbanned = db.gban + + +def register(**args): + """Registers a new message.""" + pattern = args.get("pattern", None) + + r_pattern = r"^[/!.]" + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = "(?i)" + pattern + + args["pattern"] = pattern.replace("^/", r_pattern, 1) + + def decorator(func): + telethn.add_event_handler(func, events.NewMessage(**args)) + return func + + return decorator + + +def chataction(**args): + """Registers chat actions.""" + + def decorator(func): + telethn.add_event_handler(func, events.ChatAction(**args)) + return func + + return decorator + + +def userupdate(**args): + """Registers user updates.""" + + def decorator(func): + telethn.add_event_handler(func, events.UserUpdate(**args)) + return func + + return decorator + + +def inlinequery(**args): + """Registers inline query.""" + pattern = args.get("pattern", None) + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = "(?i)" + pattern + + def decorator(func): + telethn.add_event_handler(func, events.InlineQuery(**args)) + return func + + return decorator + + +def callbackquery(**args): + """Registers inline query.""" + + def decorator(func): + telethn.add_event_handler(func, events.CallbackQuery(**args)) + return func + + return decorator + +def QueenNoxiinline(**args): + def decorator(func): + telethn.add_event_handler(func, events.CallbackQuery(**args)) + return func + + return decorator +def bot(**args): + pattern = args.get("pattern") + r_pattern = r"^[/]" + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = "(?i)" + pattern + + args["pattern"] = pattern.replace("^/", r_pattern, 1) + stack = inspect.stack() + previous_stack_frame = stack[1] + file_test = Path(previous_stack_frame.filename) + file_test = file_test.stem.replace(".py", "") + reg = re.compile("(.*)") + + if pattern is not None: + try: + cmd = re.search(reg, pattern) + try: + cmd = cmd.group(1).replace("$", "").replace("\\", "").replace("^", "") + except BaseException: + pass + + try: + FUN_LIST[file_test].append(cmd) + except BaseException: + FUN_LIST.update({file_test: [cmd]}) + except BaseException: + pass + + def decorator(func): + async def wrapper(check): + if check.edit_date: + return + if check.fwd_from: + return + if check.is_group or check.is_private: + pass + else: + print("i don't work in channels") + return + if check.is_group: + if check.chat.megagroup: + pass + else: + print("i don't work in small chats") + return + + users = gbanned.find({}) + for c in users: + if check.sender_id == c["user"]: + return + try: + await func(check) + try: + LOAD_PLUG[file_test].append(func) + except Exception: + LOAD_PLUG.update({file_test: [func]}) + except BaseException: + return + else: + pass + + telethn.add_event_handler(wrapper, events.NewMessage(**args)) + return wrapper + + return decorator + + +def queennoxi(**args): + pattern = args.get("pattern", None) + args.get("disable_edited", False) + ignore_unsafe = args.get("ignore_unsafe", False) + unsafe_pattern = r"^[^/!#@\$A-Za-z]" + args.get("group_only", False) + args.get("disable_errors", False) + args.get("insecure", False) + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = "(?i)" + pattern + + if "disable_edited" in args: + del args["disable_edited"] + + if "ignore_unsafe" in args: + del args["ignore_unsafe"] + + if "group_only" in args: + del args["group_only"] + + if "disable_errors" in args: + del args["disable_errors"] + + if "insecure" in args: + del args["insecure"] + + if pattern: + if not ignore_unsafe: + args["pattern"] = args["pattern"].replace("^.", unsafe_pattern, 1) diff --git a/QueenNoxi/modules/__init__.py b/QueenNoxi/modules/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fa38a89134b16a8c48423c353b524385e60eeb8b --- /dev/null +++ b/QueenNoxi/modules/__init__.py @@ -0,0 +1,54 @@ +from QueenNoxi import LOAD, LOGGER, NO_LOAD + + +def __list_all_modules(): + import glob + from os.path import basename, dirname, isfile + + # This generates a list of modules in this folder for the * in __main__ to work. + mod_paths = glob.glob(dirname(__file__) + "/*.py") + to_load = [] + for f in mod_paths: + if isfile(f) and f.endswith(".py") and not f.endswith("__init__.py"): + mod_name = basename(f)[:-3] + try: + with open(f, "r", encoding="utf-8") as file: + content = file.read() + if "import telegram" in content or "from telegram" in content or "dispatcher." in content: + if "from QueenNoxi import pbot" not in content and "@pbot.on_" not in content: + LOGGER.info(f"Skipping legacy module: {mod_name}") + continue + to_load.append(mod_name) + except Exception as e: + LOGGER.error(f"Error reading module {mod_name}: {e}") + continue + + if LOAD or NO_LOAD: + all_modules = to_load + to_load = LOAD + if to_load: + if not all( + any(mod == module_name for module_name in all_modules) + for mod in to_load + ): + LOGGER.error("Invalid loadorder names. Quitting.") + quit(1) + + all_modules = sorted(set(all_modules) - set(to_load)) + to_load = list(all_modules) + to_load + + else: + to_load = all_modules + + if NO_LOAD: + LOGGER.info("Not loading: {}".format(NO_LOAD)) + return [item for item in to_load if item not in NO_LOAD] + + return to_load + + return to_load + + +ALL_MODULES = __list_all_modules() +LOGGER.info("Modules to load: %s", str(ALL_MODULES)) +__all__ = ALL_MODULES + ["ALL_MODULES"] diff --git a/QueenNoxi/modules/admin.py b/QueenNoxi/modules/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..39ac530b6e8300939cd2a6012705cc4f0d042514 --- /dev/null +++ b/QueenNoxi/modules/admin.py @@ -0,0 +1,363 @@ +import html +import os +from pyrogram import filters, Client, enums +from pyrogram.raw import functions, types as raw_types +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + ChatPrivileges +) +from pyrogram.errors import RPCError + +from QueenNoxi import DRAGONS, pbot, BOT_ID +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.admin_rights import user_can_changeinfo +from QueenNoxi.modules.helper_funcs.chat_status import ( + bot_admin, + can_pin, + connection_status, + user_admin, + can_promote, + is_user_admin +) +from QueenNoxi.modules.helper_funcs.extraction import ( + extract_user, + extract_user_and_text, +) +from QueenNoxi.modules.log_channel import loggable + +@pbot.on_message(filters.command("setsticker") & filters.group) +@bot_admin +@user_admin +async def set_sticker(client: Client, message: Message): + chat = message.chat + user = message.from_user + if not await user_can_changeinfo(chat.id, user.id): + return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !") + + if message.reply_to_message and message.reply_to_message.sticker: + stkr = message.reply_to_message.sticker.set_name + try: + await client.set_chat_sticker_set(chat.id, stkr) + await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ɢʀᴏᴜᴩ sᴛɪᴄᴋᴇʀs ɪɴ {chat.title}!") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + else: + await message.reply_text("» ʀᴇᴩʟʏ ᴛᴏ ᴀ sᴛɪᴄᴋᴇʀ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɢʀᴏᴜᴩ sᴛɪᴄᴋᴇʀ ᴩᴀᴄᴋ !") + +@pbot.on_message(filters.command("setgpic") & filters.group) +@bot_admin +@user_admin +async def setchatpic(client: Client, message: Message): + chat = message.chat + user = message.from_user + if not await user_can_changeinfo(chat.id, user.id): + return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !") + + if message.reply_to_message and (message.reply_to_message.photo or message.reply_to_message.document): + dlmsg = await message.reply_text("» ᴄʜᴀɴɢɪɴɢ ɢʀᴏᴜᴩ's ᴩʀᴏғɪʟᴇ ᴩɪᴄ...") + img = await message.reply_to_message.download("gpic.png") + try: + await client.set_chat_photo(chat.id, photo=img) + await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ɢʀᴏᴜᴩ ᴩʀᴏғɪʟᴇ ᴩɪᴄ !") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + finally: + await dlmsg.delete() + if os.path.exists("gpic.png"): + os.remove("gpic.png") + else: + await message.reply_text("» ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴩʜᴏᴛᴏ ᴏʀ ғɪʟᴇ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɢʀᴏᴜᴩ ᴩʀᴏғɪʟᴇ ᴩɪᴄ !") + +@pbot.on_message(filters.command("delgpic") & filters.group) +@bot_admin +@user_admin +async def rmchatpic(client: Client, message: Message): + chat = message.chat + user = message.from_user + if not await user_can_changeinfo(chat.id, user.id): + return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏUtᴩ ɪɴғᴏ ʙᴀʙʏ !") + try: + await client.delete_chat_photo(chat.id) + await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ᴅᴇʟᴇᴛᴇᴅ ɢʀᴏᴜᴩ's ᴅᴇғᴀᴜʟᴛ ᴩʀᴏғɪʟᴇ ᴩɪᴄ !") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@pbot.on_message(filters.command("setdesc") & filters.group) +@bot_admin +@user_admin +async def set_desc(client: Client, message: Message): + chat = message.chat + user = message.from_user + if not await user_can_changeinfo(chat.id, user.id): + return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !") + + desc = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not desc: + return await message.reply_text("» ᴡᴛғ, ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ sᴇᴛ ᴀɴ ᴇᴍᴩᴛʏ ᴅᴇsᴄʀɪᴩᴛɪᴏɴ !") + try: + await client.set_chat_description(chat.id, desc[:255]) + await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ ᴜᴩᴅᴀᴛᴇᴅ ᴄʜᴀᴛ ᴅᴇsᴄʀɪᴩᴛɪᴏɴ ɪɴ {chat.title}!") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@pbot.on_message(filters.command("setgtitle") & filters.group) +@bot_admin +@user_admin +async def setchat_title(client: Client, message: Message): + chat = message.chat + user = message.from_user + if not await user_can_changeinfo(chat.id, user.id): + return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !") + + title = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not title: + return await message.reply_text("» ᴇɴᴛᴇʀ sᴏᴍᴇ ᴛᴇxᴛ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɴᴇᴡ ᴄʜᴀᴛ ᴛɪᴛʟᴇ !") + try: + await client.set_chat_title(chat.id, title) + await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ {html.escape(title)} ᴀs ɴᴇᴡ ᴄʜᴀᴛ ᴛɪᴛʟᴇ !") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@DisableAbleCommandHandler("promote", admin_ok=True) +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +async def promote(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("» ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ's ᴛʜᴀᴛ ᴜsᴇʀ.") + return + try: + user_member = await chat.get_member(user_id) + except: + return + if user_member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER): + await message.reply_text("» ᴀᴄᴄᴏʀᴅɪɴɢ ᴛᴏ ᴍᴇ ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴀɴ ᴀᴅᴍɪɴ ʜᴇʀᴇ !") + return + if user_id == BOT_ID: + await message.reply_text("» ɪ ᴄᴀɴ'ᴛ ᴩʀᴏᴍᴏᴛᴇ ᴍʏsᴇʟғ.") + return + + bot_member = await chat.get_member(BOT_ID) + try: + await client.promote_chat_member(chat.id, user_id, privileges=bot_member.privileges) + await message.reply_text(f"» ᴩʀᴏᴍᴏᴛɪɴɢ ᴀ ᴜsᴇʀ ɪɴ {chat.title}") + return f"{html.escape(chat.title)}:\n#ᴩʀᴏᴍᴏᴛᴇᴅ\nᴩʀᴏᴍᴏᴛᴇʀ : {user.mention}\nᴜsᴇʀ : {user_member.user.mention}" + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@DisableAbleCommandHandler("demote", admin_ok=True) +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +async def demote(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("» ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ's ᴛʜᴀᴛ ᴜsᴇʀ.") + return + try: + user_member = await chat.get_member(user_id) + except: + return + if user_member.status == enums.ChatMemberStatus.OWNER: + return await message.reply_text("» ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴏᴡɴᴇʀ !") + if user_id == BOT_ID: + return await message.reply_text("» ɪ ᴄᴀɴ'ᴛ ᴅᴇᴍᴏᴛᴇ ᴍʏsᴇʟғ.") + + try: + # Use raw API to fully strip admin — promote_chat_member with all False + # keeps the admin badge in Telegram; raw EditAdmin actually removes it. + channel = await client.resolve_peer(chat.id) + target = await client.resolve_peer(user_id) + await client.invoke( + functions.channels.EditAdmin( + channel=channel, + user_id=target, + admin_rights=raw_types.ChatAdminRights( + change_info=False, + post_messages=False, + edit_messages=False, + delete_messages=False, + ban_users=False, + invite_users=False, + pin_messages=False, + add_admins=False, + manage_call=False, + anonymous=False, + manage_topics=False, + post_stories=False, + edit_stories=False, + delete_stories=False, + ), + rank="" + ) + ) + await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ ᴅᴇᴍᴏᴛᴇᴅ ɪɴ {chat.title}") + return f"{html.escape(chat.title)}:\n#ᴅᴇᴍᴏᴛᴇᴅ\nᴅᴇᴍᴏᴛᴇʀ : {user.mention}\nᴅᴇᴍᴏᴛᴇᴅ : {user_member.user.mention}" + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + + + + +@pbot.on_message(filters.command(["admincache", "reload", "refresh"]) & filters.group) +@user_admin +async def refresh_admin(client: Client, message: Message): + from QueenNoxi.modules.helper_funcs.chat_status import ADMIN_CACHE + try: + ADMIN_CACHE.pop(message.chat.id) + except KeyError: + pass + await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ʀᴇғʀᴇsʜᴇᴅ ᴀᴅᴍɪɴ ᴄᴀᴄʜᴇ !") + +@pbot.on_message(filters.command("pin") & filters.group) +@bot_admin +@can_pin +@user_admin +@loggable +async def pin(client: Client, message: Message) -> str: + args = message.command[1:] + chat = message.chat + user = message.from_user + if not message.reply_to_message: + return await message.reply_text("» ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ᴩɪɴ ɪᴛ !") + + is_silent = True + if len(args) >= 1: + is_silent = args[0].lower() not in ["notify", "loud", "violent"] + try: + await client.pin_chat_message(chat.id, message.reply_to_message.id, disable_notification=is_silent) + await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ᴩɪɴɴᴇᴅ ᴛʜᴀᴛ ᴍᴇssᴀɢᴇ.") + return f"{html.escape(chat.title)}:\nᴩɪɴɴᴇᴅ-ᴀ-ᴍᴇssᴀɢᴇ\nᴩɪɴɴᴇᴅ ʙʏ : {user.mention}" + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@pbot.on_message(filters.command("unpin") & filters.group) +@bot_admin +@can_pin +@user_admin +@loggable +async def unpin(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + try: + if message.reply_to_message: + await client.unpin_chat_message(chat.id, message.reply_to_message.id) + else: + await client.unpin_chat_message(chat.id) + await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ᴜɴᴩɪɴɴᴇᴅ.") + return f"{html.escape(chat.title)}:\nᴜɴᴩɪɴɴᴇᴅ-ᴀ-ᴍᴇssᴀɢᴇ\nᴜɴᴩɪɴɴᴇᴅ ʙʏ : {user.mention}" + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@DisableAbleCommandHandler("invitelink", admin_ok=True) +@connection_status +@bot_admin +async def invite(client: Client, message: Message): + chat = message.chat + if chat.username: + return await message.reply_text(f"https://t.me/{chat.username}") + try: + invitelink = await client.export_chat_invite_link(chat.id) + await message.reply_text(invitelink) + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@DisableAbleCommandHandler("title", admin_ok=True) +@connection_status +@bot_admin +@user_admin +async def set_admin_title(client: Client, message: Message): + chat = message.chat + user_id, title = await extract_user_and_text(message, message.command[1:]) + if not user_id: + return await message.reply_text("» ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ's ᴛʜᴀᴛ ᴜsᴇʀ.") + try: + user_member = await chat.get_member(user_id) + except: + return + if user_member.status == enums.ChatMemberStatus.OWNER: + return await message.reply_text("» ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴏᴡɴᴇʀ !") + if user_member.status != enums.ChatMemberStatus.ADMINISTRATOR: + return await message.reply_text("» ɪ ᴄᴀɴ ᴏɴʟʏ sᴇᴛ ᴛɪᴛʟᴇ ғᴏʀ ᴀᴅᴍɪɴs !") + if not title: + return await message.reply_text("» sᴇᴛ ᴀ ᴛɪᴛʟᴇ ʙᴀʙʏ !") + + try: + await client.set_administrator_custom_title(chat.id, user_id, title[:16]) + await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ᴛɪᴛʟᴇ ғᴏʀ {user_member.user.first_name}") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + +@pbot.on_message(filters.command("pinned") & filters.group) +@bot_admin +async def pinned_msg(client: Client, message: Message): + chat = await client.get_chat(message.chat.id) + if chat.pinned_message: + pinned_id = chat.pinned_message.id + link = f"https://t.me/{chat.username}/{pinned_id}" if chat.username else f"https://t.me/c/{str(chat.id).replace('-100', '')}/{pinned_id}" + await message.reply_text(f"ᴩɪɴɴᴇᴅ ᴏɴ {html.escape(chat.title)}.", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ᴍᴇssᴀɢᴇ", url=link)]]), disable_web_page_preview=True) + else: + await message.reply_text(f"» ᴛʜᴇʀᴇ's ɴᴏ ᴩɪɴɴᴇᴅ ᴍᴇssᴀɢᴇ ɪɴ {html.escape(chat.title)}!") + +@DisableAbleCommandHandler(["admins", "staff"]) +@connection_status +async def adminlist(client: Client, message: Message): + if message.chat.type == enums.ChatType.PRIVATE: + return await message.reply_text("» ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴜsᴇᴅ ɪɴ ɢʀᴏᴜᴩ's.") + msg = await message.reply_text("» ғᴇᴛᴄʜɪɴɢ ᴀᴅᴍɪɴs ʟɪsᴛ...") + try: + administrators = [] + async for m in client.get_chat_members(message.chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS): + administrators.append(m) + text = "ᴀᴅᴍɪɴs ɪɴ {}:".format(html.escape(message.chat.title)) + creator = None; admins = []; bots = [] + for admin in administrators: + if admin.status == enums.ChatMemberStatus.OWNER: creator = admin + elif admin.user.is_bot: bots.append(admin) + else: admins.append(admin) + if creator: + text += f"\n\n🥀 ᴏᴡɴᴇʀ :\n{creator.user.mention}" + if creator.custom_title: text += f"\n ┗━ {html.escape(creator.custom_title)}" + if admins: + text += "\n\n💫 ᴀᴅᴍɪɴs :" + for a in admins: + text += f"\n{a.user.mention}" + if a.custom_title: text += f" | {html.escape(a.custom_title)}" + if bots: + text += "\n\n🤖 ʙᴏᴛs :" + for b in bots: text += f"\n{b.user.mention}" + await msg.edit_text(text) + except RPCError as e: + await msg.edit_text(f"Error: {e.MESSAGE}") + +__mod_name__ = "Admins" +__help__ = """ +*User Commands*: +» /admins: List of admins in the chat +» /pinned: To get the current pinned message. + +*Admins only:* +» /promote: Promotes the user replied to +» /demote: Demotes the user replied to +» /title : Sets a custom title +» /admincache: Force refresh the admins list +» /setgtitle <text>: Set group title +» /setgpic: Reply to an image to set as group photo +» /delgpic: Delete group photo +» /setdesc: Set group description +» /setsticker: Set group sticker +» /pin: Silently pins the message +» /unpin: Unpins the currently pinned message +» /invitelink: Gets invitelink +""" diff --git a/QueenNoxi/modules/aiimage.py b/QueenNoxi/modules/aiimage.py new file mode 100644 index 0000000000000000000000000000000000000000..e0c5f4c45c8964259959457febe1aab7c72ca17e --- /dev/null +++ b/QueenNoxi/modules/aiimage.py @@ -0,0 +1,61 @@ +"""MIT License + +Copyright (c) 2023-24 Noob-QueenNoxi + + GITHUB: NOOB-MUKESH + TELEGRAM: @MR_SUKKUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.""" +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.types import InputMediaPhoto +from .. import pbot as QueenNoxi,BOT_USERNAME +from MukeshAPI import api +from pyrogram.enums import ChatAction,ParseMode + +@QueenNoxi.on_message(filters.command("imagine")) +async def imagine_(b, message: Message): + if message.reply_to_message: + text = message.reply_to_message.text + else: + + text =message.text.split(None, 1)[1] + queennoxi=await message.reply_text( "`Please wait...,\n\nGenerating prompt .. ...`") + try: + await b.send_chat_action(message.chat.id, ChatAction.UPLOAD_PHOTO) + x=api.ai_image(text) + with open("queennoxi.jpg", 'wb') as f: + f.write(x) + caption = f""" + 💘sᴜᴄᴇssғᴜʟʟʏ ɢᴇɴᴇʀᴀᴛᴇᴅ : {text} + ✨ɢᴇɴᴇʀᴀᴛᴇᴅ ʙʏ : @{BOT_USERNAME} + 🥀ʀᴇǫᴜᴇsᴛᴇᴅ ʙʏ : {message.from_user.mention} + """ + await queennoxi.delete() + await message.reply_photo("queennoxi.jpg",caption=caption,quote=True) + except Exception as e: + await queennoxi.edit_text(f"error {e}") + +# -----------CREDITS ----------- +# telegram : @legend_coder +# github : Alexainc +__mod_name__ = "Aɪ ɪᴍᴀɢᴇ" +__help__ = """ + ➻ /imagine : ɢᴇɴᴇʀᴀᴛᴇ Aɪ ɪᴍᴀɢᴇ ғʀᴏᴍ ᴛᴇxᴛ + """ diff --git a/QueenNoxi/modules/alive.py b/QueenNoxi/modules/alive.py new file mode 100644 index 0000000000000000000000000000000000000000..611ee9afba862687e48ed1ea6d61aaa32f2b0e6f --- /dev/null +++ b/QueenNoxi/modules/alive.py @@ -0,0 +1,70 @@ +import asyncio +from platform import python_version as pyver +from pyrogram.enums import ChatType +from pyrogram import __version__ as pver +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message +from telethon import __version__ as tver +from QueenNoxi.modules.no_sql.chats_db import add_served_chat +from QueenNoxi.modules.no_sql.users_db import save_id +from QueenNoxi import SUPPORT_CHAT, SUPPORT_CHAT_URL, pbot, BOT_USERNAME, OWNER_ID, BOT_NAME, START_IMG + +PHOTO = [ + "https://telegra.ph/file/d2a23fbe48129a7957887.jpg", + "https://telegra.ph/file/ddf30888de58d77911ee1.jpg", + "https://telegra.ph/file/268d66cad42dc92ec65ca.jpg", + "https://telegra.ph/file/13a0cbbff8f429e2c59ee.jpg", + "https://telegra.ph/file/bdfd86195221e979e6b20.jpg", +] + +QueenNoxi = [ + [ + InlineKeyboardButton(text="ᴏᴡɴᴇʀ", user_id=OWNER_ID), + InlineKeyboardButton(text="ꜱᴜᴘᴘᴏʀᴛ", url=SUPPORT_CHAT_URL), + ], + [ + InlineKeyboardButton( + text="➕ᴀᴅᴅ ᴍᴇ ᴇʟsᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ➕", + url=f"https://t.me/{BOT_USERNAME}?startgroup=true", + ), + ], +] + +@pbot.on_message(filters.command("alive")) +async def alive(client, m: Message): + await m.delete() + accha = await m.reply("⚡") + await asyncio.sleep(0.2) + await accha.edit("ᴅɪɴɢ ᴅᴏɴɢ ꨄ︎ ᴀʟɪᴠɪɴɢ..") + await accha.delete() + await asyncio.sleep(0.3) + + umm = await m.reply_sticker( + "CAACAgUAAxkDAAJHbmLuy2NEfrfh6lZSohacEGrVjd5wAAIOBAACl42QVKnra4sdzC_uKQQ" + ) + await umm.delete() + owner = await client.get_users(OWNER_ID) + await m.reply_photo( + START_IMG, + caption=f"""**ʜᴇʏ, ɪ ᴀᴍ 『[{BOT_NAME}](t.me/{BOT_USERNAME})』** + ━━━━━━━━━━━━━━━━━━━ + » **ᴍʏ ᴏᴡɴᴇʀ :** {owner.mention} + + » **ᴛᴇʟᴇᴛʜᴏɴ :** `{tver}` + + » **ᴘʏʀᴏɢʀᴀᴍ :** `{pver}` + + » **ᴘʏᴛʜᴏɴ :** `{pyver()}` + ━━━━━━━━━━━━━━━━━━━""", + reply_markup=InlineKeyboardMarkup(QueenNoxi) + ) + +@pbot.on_message(group=1) +async def save_statss(_, m: Message): + try: + if m.chat.type == ChatType.PRIVATE: + await save_id(m.from_user.id) + else: + await add_served_chat(m.chat.id) + except Exception: + pass diff --git a/QueenNoxi/modules/animation.py b/QueenNoxi/modules/animation.py new file mode 100644 index 0000000000000000000000000000000000000000..dc69d76f58ef0e9b97b0bfe29027d3e002eae787 --- /dev/null +++ b/QueenNoxi/modules/animation.py @@ -0,0 +1,344 @@ +import asyncio +import aiohttp +import os +import random +from pyrogram import filters, Client +from pyrogram.errors import FloodWait, RPCError + +from pyrogram.types import Message +from QueenNoxi import pbot, LOGGER +from QueenNoxi.config import Config +TENOR_API_KEY = Config.TENOR_API_KEY +DRAGONS = Config.DRAGONS +OWNER_IDS = Config.OWNER_IDS +SUDOERS = list(set(DRAGONS + OWNER_IDS)) + +import zipfile +import shutil +from QueenNoxi.modules.disable import DisableAbleCommandHandler + + +from QueenNoxi.modules.helper_funcs.chat_status import user_admin + +# ── Setup Caching ──────────────────────────────────────────────────────────── +CACHE_DIR = "QueenNoxi/resources/animation_cache" +if not os.path.exists(CACHE_DIR): + os.makedirs(CACHE_DIR) + +async def get_random_gif(category: str): + """Fetch a random GIF URL from Tenor or local cache.""" + tenor_map = { + "kill": "kill", "love": "love", "hug": "hug", "slap": "slap", + "pat": "pat", "kiss": "kiss", "brain": "think", "moon": "sleep", + "hack": "hacker", "police": "police", "bombs": "explosion", "clock": "clock" + } + + action = tenor_map.get(category, category) + cat_dir = os.path.join(CACHE_DIR, category) + if not os.path.exists(cat_dir): + os.makedirs(cat_dir) + + # 1. Try Tenor API + if TENOR_API_KEY: + try: + query = f"{action} anime".replace(" ", "%20") + url = f"https://tenor.googleapis.com/v2/search?q={query}&key={TENOR_API_KEY}&limit=5&random=true" + async with aiohttp.ClientSession() as session: + async with session.get(url, timeout=5) as resp: + if resp.status == 200: + data = await resp.json() + if data.get('results'): + chosen = random.choice(data['results']) + gif_url = chosen['media_formats']['gif']['url'] + + # Async Background Download for Cache (best effort) + asyncio.create_task(cache_gif(category, gif_url)) + return gif_url + except Exception as e: + LOGGER.warning(f"Tenor API error for {category}: {e}") + + # 2. Fallback to Local Cache + cached_files = [f for f in os.listdir(cat_dir) if f.endswith(".gif")] + if cached_files: + local_file = random.choice(cached_files) + return os.path.join(cat_dir, local_file) + + return None + +async def cache_gif(category: str, url: str): + """Download and save a GIF to the local cache if it doesn't exist.""" + cat_dir = os.path.join(CACHE_DIR, category) + # Limit cache size per category (e.g. 10 files) + if len(os.listdir(cat_dir)) >= 15: + return + + file_id = url.split("/")[-2] if "/" in url else str(random.randint(1000, 9999)) + file_path = os.path.join(cat_dir, f"{file_id}.gif") + if os.path.exists(file_path): + return + + try: + async with aiohttp.ClientSession() as session: + async with session.get(url, timeout=10) as resp: + if resp.status == 200: + data = await resp.read() + with open(file_path, "wb") as f: + f.write(data) + except Exception: + pass + + + + + + + + +# ── Animation lists ─────────────────────────────────────────────────────────── +brain_chain = [ + "🧠", "🧠💥", "💭🧠", "🤯", "🧠🔥", "💡🧠", "🤔🧠", + "💥🧠💥", "🧠📤", "📤🗑️", "🗑️💨", "🧠❌", "🗑️", "✅" +] + +clock_ani = [ + "🕛", "🕧", "🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔" +] + +police_ani = [ + "🚓", "🚓💨", "🚔", "🚔💨", "🚨", "🚨🚨", "👮", "👮‍♂️🚓", + "🚓🚨👮", "🚨🚨👮‍♂️", "👮‍♂️🔦" +] + +moon_ani = [ + "🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑", "🌒", + "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑", "🌒", "🌓", "🌔", + "🌕", "🌕✨", "🌕🌟", "🌕💫", "🌕⭐", "🌕🌟✨", "🌔", "🌓", + "🌒", "🌑", "🌙", "🌙✨" +] + +bomb_ettu = [ + "💣", "💣💣", "💣💣💣", "💣💣💣💣", "💥", "💥💥", "💥💥💥", "🔥💥", "☠️" +] + +hack_you = [ + "🖥️ Booting...", "🔍 Scanning target...", "🔓 Bypassing firewall...", + "💻 Injecting payload...", "📡 Connecting...", "🔑 Cracking password...", + "📂 Accessing files...", "📊 Downloading data...", "🗄️ Extracting...", + "📤 Uploading backdoor...", "🔐 Locking access...", "🎭 Covering tracks...", + "🧹 Cleaning logs...", "⚙️ Finalizing...", "✅ Access granted!", + "📁 All data secured.", "🏴‍☠️ Mission complete.", "😎 You've been hacked!" +] + +love_siren = [ + "❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "❤️‍🔥", + "💗", "💓", "💞", "💕", "💝", "💖", "💘", "💟", "❣️", "❤️", + "💑", "👫", "💏", "💞", "💕❤️", "💖💖", "💗💗", "💓❤️💓", + "❤️‍🔥❤️", "💘💘", "💞💞💞", "💖✨", "💝🌹", "🌹❤️", "True Love💞" +] + +kill_you = [ + "🔫", "🔫💨", "😵", "😵‍💫", "💀", "⚰️🕯️", "🪦", "😱💀", + "🔫😵", "💀☠️", "⚰️", "☠️" +] + +slap_ani = ["Slapping...", "SLAP! 👋", "Ouch! 💥", "👋💥", "😵"] +pat_ani = ["Patting...", "Pat pat... ✨", "Good job! 💖", "✋✨", "😊"] +hug_ani = ["Hugging...", "HUG! 🤗", "Warm hugs! ❤️", "🫂❤️", "✨"] +kiss_ani = ["Kissing...", "KISS! 💋", "Muah! 💘", "😘💋", "🔥"] + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +async def send_gif_with_caption(client: Client, chat_id: int, gif_key: str, caption: str): + """Attempt to send a GIF with a caption.""" + url = await get_random_gif(gif_key) + if not url: + return None + + try: + # Try sending by URL first + return await client.send_animation(chat_id, url, caption=caption) + except Exception as e: + # Fallback: Download and send + try: + headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"} + async with aiohttp.ClientSession(headers=headers) as session: + async with session.get(url, timeout=20) as resp: + if resp.status == 200: + data = await resp.read() + temp_name = f"temp_{gif_key}_{random.randint(100,999)}.gif" + with open(temp_name, "wb") as f: + f.write(data) + + sent = await client.send_animation(chat_id, temp_name, caption=caption) + os.remove(temp_name) + return sent + else: + LOGGER.warning(f"GIF Download failed for {gif_key} ({url}): Status {resp.status}") + except Exception as e2: + LOGGER.warning(f"GIF Fallback failed for {gif_key} ({url}): {e2}") + return None + + +async def animate(client: Client, message: Message, gif_key: str, frames: list, action_verb: str, sleep: float = 0.5): + """Perform text animation, then send GIF with caption.""" + if not message.reply_to_message: + await message.reply_text(f"❗ Please reply to a user to {gif_key} them!") + return + + sender = message.from_user.mention + target = message.reply_to_message.from_user.mention + caption = f"✨ {sender} {action_verb} {target}! ✨" + + # Start text animation + msg = await message.reply_text(frames[0]) + + # Avoid FloodWait: Limit to ~8 edits if the list is long + total_frames = len(frames) + step = 1 + if total_frames > 8: + step = total_frames // 7 + if step == 0: step = 1 + + for x in range(step, total_frames, step): + try: + await msg.edit_text(frames[x]) + await asyncio.sleep(sleep) + except FloodWait as e: + await asyncio.sleep(e.value) + except RPCError: + break + + # Send the GIF with caption + gif_msg = await send_gif_with_caption(client, message.chat.id, gif_key, caption) + + # Only delete the temporary text animation message if GIF succeeded + # Otherwise, edit it to keep the result visible + if gif_msg: + try: + await msg.delete() + except: + pass + else: + try: + await msg.edit_text(caption) + except: + pass + + + + +# ── Commands ────────────────────────────────────────────────────────────────── +@pbot.on_message(filters.command("dlanimech")) +async def dlanimech(client: Client, message: Message): + """Zip and send animation cache to Sudoers in PM.""" + if message.from_user.id not in SUDOERS: + await message.reply_text("❌ This command is only for Sudoers/Owners!") + return + + msg = await message.reply_text("📦 Zipping animation cache...") + + zip_path = "anim_cache.zip" + try: + # Create zip + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(CACHE_DIR): + for file in files: + zipf.write( + os.path.join(root, file), + os.path.relpath(os.path.join(root, file), os.path.join(CACHE_DIR, '..')) + ) + + # Send in PM + await client.send_document( + chat_id=message.from_user.id, + document=zip_path, + caption="📂 Here is the current Animation Cache!", + file_name="animation_cache.zip" + ) + await msg.edit_text("✅ Sent to your PM!") + except Exception as e: + await msg.edit_text(f"❌ Error: {e}") + finally: + if os.path.exists(zip_path): + os.remove(zip_path) + +@pbot.on_message(filters.command("brain") & filters.group) +@DisableAbleCommandHandler("brain") +async def brainanimation(client: Client, message: Message): + await animate(client, message, "brain", brain_chain, "put brain in dustbin for") + +@pbot.on_message(filters.command("clock") & filters.group) +@DisableAbleCommandHandler("clock") +async def clockanimation(client: Client, message: Message): + await animate(client, message, "clock", clock_ani, "reminded time to") + +@pbot.on_message(filters.command("police") & filters.group) +@DisableAbleCommandHandler("police") +async def policeanimation(client: Client, message: Message): + await animate(client, message, "police", police_ani, "called police for") + +@pbot.on_message(filters.command("moon") & filters.group) +@DisableAbleCommandHandler("moon") +async def moonanimation(client: Client, message: Message): + await animate(client, message, "moon", moon_ani, "wished good night to") + +@pbot.on_message(filters.command("bombs") & filters.group) +@DisableAbleCommandHandler("bombs") +async def bombs(client: Client, message: Message): + await animate(client, message, "bombs", bomb_ettu, "bombed") + +@pbot.on_message(filters.command("hack") & filters.group) +@DisableAbleCommandHandler("hack") +async def hack(client: Client, message: Message): + await animate(client, message, "hack", hack_you, "hacked") + +@pbot.on_message(filters.command("love") & filters.group) +@DisableAbleCommandHandler("love") +async def love(client: Client, message: Message): + await animate(client, message, "love", love_siren, "is expressing love to") + +@pbot.on_message(filters.command("kill") & filters.group) +@DisableAbleCommandHandler("kill") +async def kill(client: Client, message: Message): + await animate(client, message, "kill", kill_you, "killed") + +@pbot.on_message(filters.command("slap") & filters.group) +@DisableAbleCommandHandler("slap") +async def slap(client: Client, message: Message): + await animate(client, message, "slap", slap_ani, "slapped") + +@pbot.on_message(filters.command("pat") & filters.group) +@DisableAbleCommandHandler("pat") +async def pat(client: Client, message: Message): + await animate(client, message, "pat", pat_ani, "patted") + +@pbot.on_message(filters.command("hug") & filters.group) +@DisableAbleCommandHandler("hug") +async def hug(client: Client, message: Message): + await animate(client, message, "hug", hug_ani, "hugged") + +@pbot.on_message(filters.command("kiss") & filters.group) +@DisableAbleCommandHandler("kiss") +async def kiss(client: Client, message: Message): + await animate(client, message, "kiss", kiss_ani, "kissed") + + + +__mod_name__ = "Animation" +__help__ = """ +*ғᴀᴋᴇ ᴀɴɪᴍᴀᴛɪᴏɴ ᴄᴏᴍᴍᴀɴᴅs* +• `/love` — ʟᴏᴠᴇ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/hack` — ʜᴀᴄᴋ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/moon` — ᴍᴏᴏɴ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/kill` — ᴋɪʟʟ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/slap` — sʟᴀᴩ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/pat` — ᴩᴀᴛ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/hug` — ʜᴜɢ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/kiss` — ᴋɪss ᴀɴɪᴍᴀᴛɪᴏɴ +• `/bombs` — ʙᴏᴍʙ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/police` — ᴩᴏʟɪᴄᴇ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/brain` — ʙʀᴀɪɴ ᴀɴɪᴍᴀᴛɪᴏɴ +• `/clock` — ᴄʟᴏᴄᴋ ᴀɴɪᴍᴀᴛɪᴏɴ +""" + diff --git a/QueenNoxi/modules/anime.py b/QueenNoxi/modules/anime.py new file mode 100644 index 0000000000000000000000000000000000000000..cb3318f802a30fb3d1c93620eadbdce0e9640e22 --- /dev/null +++ b/QueenNoxi/modules/anime.py @@ -0,0 +1,110 @@ +import json +import random +import aiohttp +from pyrogram import filters, Client, enums +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + CallbackQuery +) + +from QueenNoxi import pbot, OWNER_ID +from QueenNoxi.modules.disable import DisableAbleCommandHandler + +QUOTES_IMG = [ + "https://i.imgur.com/Iub4RYj.jpg", + "https://i.imgur.com/uvNMdIl.jpg", + "https://i.imgur.com/YOBOntg.jpg", + "https://i.imgur.com/fFpO2ZQ.jpg", + "https://i.imgur.com/f0xZceK.jpg", + "https://i.imgur.com/RlVcCip.jpg", + "https://i.imgur.com/CjpqLRF.jpg", + "https://i.imgur.com/8BHZDk6.jpg", + "https://i.imgur.com/8bHeMgy.jpg", + "https://i.imgur.com/5K3lMvr.jpg", + "https://i.imgur.com/NTzw4RN.jpg", + "https://i.imgur.com/wJxryAn.jpg", + "https://i.imgur.com/9L0DWzC.jpg", + "https://i.imgur.com/sBe8TTs.jpg", + "https://i.imgur.com/1Au8gdf.jpg", + "https://i.imgur.com/28hFQeU.jpg", + "https://i.imgur.com/Qvc03JY.jpg", + "https://i.imgur.com/gSX6Xlf.jpg", + "https://i.imgur.com/iP26Hwa.jpg", + "https://i.imgur.com/uSsJoX8.jpg", + "https://i.imgur.com/OvX3oHB.jpg", + "https://i.imgur.com/JMWuksm.jpg", + "https://i.imgur.com/lhM3fib.jpg", + "https://i.imgur.com/64IYKkw.jpg", + "https://i.imgur.com/nMbyA3J.jpg", + "https://i.imgur.com/7KFQhY3.jpg", + "https://i.imgur.com/mlKb7zt.jpg", + "https://i.imgur.com/JCQGJVw.jpg", + "https://i.imgur.com/hSFYDEz.jpg", + "https://i.imgur.com/PQRjAgl.jpg", + "https://i.imgur.com/ot9624U.jpg", + "https://i.imgur.com/iXmqN9y.jpg", + "https://i.imgur.com/RhNBeGr.jpg", + "https://i.imgur.com/tcMVNa8.jpg", + "https://i.imgur.com/LrVg810.jpg", + "https://i.imgur.com/TcWfQlz.jpg", + "https://i.imgur.com/muAUdvJ.jpg", + "https://i.imgur.com/AtC7ZRV.jpg", + "https://i.imgur.com/sCObQCQ.jpg", + "https://i.imgur.com/AJFDI1r.jpg", + "https://i.imgur.com/TCgmRrH.jpg", + "https://i.imgur.com/LMdmhJU.jpg", + "https://i.imgur.com/eyyax0N.jpg", + "https://i.imgur.com/YtYxV66.jpg", + "https://i.imgur.com/289f9cebe37f31a943f98.jpg", +] + +async def anime_quote(): + url = "https://animechan.vercel.app/api/random" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status == 200: + dic = await response.json() + return dic["quote"], dic["character"], dic["anime"] + return "No quote found.", "Unknown", "Unknown" + +@pbot.on_message(filters.command("quote")) +@DisableAbleCommandHandler("quote") +async def quotes(client: Client, message: Message): + quote, character, anime = await anime_quote() + msg = f"<i>❝{quote}❞</i>\n\n<b>{character} from {anime}</b>" + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="Change🔁", callback_data="change_quote")]] + ) + await message.reply_text( + msg, + reply_markup=keyboard, + parse_mode=enums.ParseMode.HTML, + ) + +@pbot.on_callback_query(filters.regex(r"change_quote|quote_change")) +async def change_quote_btn(client: Client, query: CallbackQuery): + quote, character, anime = await anime_quote() + msg = f"<i>❝{quote}❞</i>\n\n<b>{character} from {anime}</b>" + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ🔁", callback_data="quote_change")]] + ) + try: + await query.message.edit_text(msg, reply_markup=keyboard, parse_mode=enums.ParseMode.HTML) + except: + await query.answer("Error or same content.") + +@pbot.on_message(filters.command("animequotes")) +@DisableAbleCommandHandler("animequotes") +async def animequotes_cmd(client: Client, message: Message): + if message.reply_to_message: + await message.reply_to_message.reply_photo(random.choice(QUOTES_IMG)) + else: + await message.reply_photo(random.choice(QUOTES_IMG)) + +__mod_name__ = "Quotes" +__help__ = """ +• `/quote`: Get a random anime quote. +• `/animequotes`: Get a random anime quote image. +""" diff --git a/QueenNoxi/modules/animez.py b/QueenNoxi/modules/animez.py new file mode 100644 index 0000000000000000000000000000000000000000..14d370b86f04513dc8c06b9653338ed34ce373b8 --- /dev/null +++ b/QueenNoxi/modules/animez.py @@ -0,0 +1,415 @@ +import datetime +import html +import textwrap +import aiohttp +import bs4 +from jikanpy import AioJikan +from pyrogram import filters, Client +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message + +from QueenNoxi import pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler + +info_btn = "More Information" +close_btn = "Close ❌" + +def shorten(description, info="anilist.co"): + msg = "" + if len(description) > 700: + description = description[0:500] + "...." + msg += f"\n**Description**: _{description}_[Read More]({info})" + else: + msg += f"\n**Description**:_{description}_" + return msg + +def t(milliseconds: int) -> str: + seconds, milliseconds = divmod(int(milliseconds), 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + ((str(days) + " Days, ") if days else "") + + ((str(hours) + " Hours, ") if hours else "") + + ((str(minutes) + " Minutes, ") if minutes else "") + + ((str(seconds) + " Seconds, ") if seconds else "") + + ((str(milliseconds) + " ms, ") if milliseconds else "") + ) + return tmp[:-2] + +airing_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: ANIME,search: $search) { + id + episodes + title { + romaji + english + native + } + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + } +} +""" + +anime_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: ANIME,search: $search) { + id + title { + romaji + english + native + } + description (asHtml: false) + startDate{ + year + } + episodes + season + type + format + status + duration + siteUrl + studios{ + nodes{ + name + } + } + trailer{ + id + site + thumbnail + } + averageScore + genres + bannerImage + } +} +""" + +character_query = """ +query ($query: String) { + Character (search: $query) { + id + name { + first + last + full + } + siteUrl + image { + large + } + description + } +} +""" + +manga_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: MANGA,search: $search) { + id + title { + romaji + english + native + } + description (asHtml: false) + startDate{ + year + } + type + format + status + siteUrl + averageScore + genres + bannerImage + } +} +""" + +url = "https://graphql.anilist.co" + +async def get_response(query, variables): + async with aiohttp.ClientSession() as session: + async with session.post(url, json={"query": query, "variables": variables}) as resp: + return await resp.json() + +@pbot.on_message(filters.command("airing")) +@DisableAbleCommandHandler("airing") +async def airing(client: Client, message: Message): + search_str = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not search_str and message.reply_to_message: + search_str = message.reply_to_message.text + + if not search_str: + await message.reply_text("Tell Anime Name :) ( /airing <anime name>)") + return + + data = await get_response(airing_query, {"search": search_str}) + if not data or "data" not in data or not data["data"]["Media"]: + await message.reply_text("Anime not found") + return + + response = data["data"]["Media"] + msg = f"**Name**: **{response['title']['romaji']}**(`{response['title']['native']}`)\n**ID**: `{response['id']}`" + if response["nextAiringEpisode"]: + time_val = response["nextAiringEpisode"]["timeUntilAiring"] * 1000 + time_str = t(time_val) + msg += f"\n**Episode**: `{response['nextAiringEpisode']['episode']}`\n**Airing In**: `{time_str}`" + else: + msg += f"\n**Episode**:{response['episodes']}\n**Status**: `N/A`" + await message.reply_text(msg) + +@pbot.on_message(filters.command("anime")) +@DisableAbleCommandHandler("anime") +async def anime(client: Client, message: Message): + search = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not search and message.reply_to_message: + search = message.reply_to_message.text + + if not search: + await message.reply_text("Format : /anime < anime name >") + return + + data = await get_response(anime_query, {"search": search}) + if not data or "data" not in data or not data["data"]["Media"]: + await message.reply_text("Anime not found") + return + + json_data = data["data"]["Media"] + msg = f"**{json_data['title']['romaji']}**(`{json_data['title']['native']}`)\n**Type**: {json_data['format']}\n**Status**: {json_data['status']}\n**Episodes**: {json_data.get('episodes', 'N/A')}\n**Duration**: {json_data.get('duration', 'N/A')} Per Ep.\n**Score**: {json_data['averageScore']}\n**Genres**: `" + msg += ", ".join(json_data["genres"]) + "`\n" + msg += "**Studios**: `" + msg += ", ".join([x['name'] for x in json_data["studios"]["nodes"]]) + "`\n" + + info = json_data.get("siteUrl") + trailer = json_data.get("trailer", None) + if trailer: + trailer_id = trailer.get("id", None) + site = trailer.get("site", None) + if site == "youtube": + trailer = "https://youtu.be/" + trailer_id + + description = json_data.get("description", "N/A").replace("<i>", "").replace("</i>", "").replace("<br>", "") + msg += train_shorten(description, info) + image = json_data.get("bannerImage", None) + + buttons = [ + [ + InlineKeyboardButton("ᴍᴏʀᴇ ɪɴғᴏ", url=info), + ] + ] + if trailer: + buttons[0].append(InlineKeyboardButton("ᴛʀᴀɪʟᴇʀ", url=str(trailer))) + + if image: + try: + await message.reply_photo(photo=image, caption=msg,保护_markup=InlineKeyboardMarkup(buttons)) + except Exception: + msg += f" [〽️]({image})" + await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons)) + else: + await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons)) + +def train_shorten(description, info): + if len(description) > 700: + return f"\n**Description**: _{description[:500]}...._[Read More]({info})" + return f"\n**Description**: _{description}_" + +@pbot.on_message(filters.command("character")) +@DisableAbleCommandHandler("character") +async def character(client: Client, message: Message): + search = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not search and message.reply_to_message: + search = message.reply_to_message.text + + if not search: + await message.reply_text("Format : /character < character name >") + return + + data = await get_response(character_query, {"query": search}) + if not data or "data" not in data or not data["data"]["Character"]: + await message.reply_text("Character not found") + return + + json_data = data["data"]["Character"] + msg = f"**{json_data.get('name').get('full')}**(`{json_data.get('name').get('native')}`)\n" + description = f"{json_data['description']}" + site_url = json_data.get("siteUrl") + msg += train_shorten(description, site_url) + image = json_data.get("image", None) + + if image: + await message.reply_photo(photo=image["large"], caption=msg.replace("<b>", "").replace("</b>", "")) + else: + await message.reply_text(msg.replace("<b>", "").replace("</b>", "")) + +@pbot.on_message(filters.command("manga")) +@DisableAbleCommandHandler("manga") +async def manga(client: Client, message: Message): + search = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not search and message.reply_to_message: + search = message.reply_to_message.text + + if not search: + await message.reply_text("Format : /manga < manga name >") + return + + data = await get_response(manga_query, {"search": search}) + if not data or "data" not in data or not data["data"]["Media"]: + await message.reply_text("Manga not found") + return + + json_data = data["data"]["Media"] + msg = f"**{json_data['title']['romaji']}**(`{json_data['title']['native']}`)\n" + if json_data["startDate"].get("year"): + msg += f"**Start Date**: `{json_data['startDate']['year']}`\n" + if json_data.get("status"): + msg += f"**Status**: `{json_data['status']}`\n" + if json_data.get("averageScore"): + msg += f"**Score**: `{json_data['averageScore']}`\n" + + msg += "**Genres**: " + ", ".join(json_data.get("genres", [])) + "\n" + + info = json_data["siteUrl"] + description = json_data.get('description', 'N/A') + msg += train_shorten(description, info) + + image = json_data.get("bannerImage") + buttons = [[InlineKeyboardButton("More Info", url=info)]] + + if image: + try: + await message.reply_photo(photo=image, caption=msg, reply_markup=InlineKeyboardMarkup(buttons)) + except Exception: + msg += f" [〽️]({image})" + await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons)) + else: + await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons)) + +@pbot.on_message(filters.command("user")) +@DisableAbleCommandHandler("user") +async def user_info(client: Client, message: Message): + search_query = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not search_query: + await message.reply_text("Format : /user <username>") + return + + async with AioJikan() as jikan: + try: + us = await jikan.user(search_query) + except Exception: + await message.reply_text("Username not found.") + return + + img = us.get("images", {}).get("jpg", {}).get("image_url", "https://cdn.myanimelist.net/images/questionmark_50.gif") + + birthday = us.get("birthday") + joined = us.get("joined") + + caption = f"**ᴜsᴇʀɴᴀᴍᴇ**: [{us['username']}]({us['url']})\n\n" + caption += f"**ɢᴇɴᴅᴇʀ**: `{us.get('gender', 'Unknown')}`\n" + caption += f"**ʙɪʀᴛʜᴅᴀʏ**: `{birthday[:10] if birthday else 'Unknown'}`\n" + caption += f"**ᴊᴏɪɴᴇᴅ**: `{joined[:10] if joined else 'Unknown'}`\n" + + anime_stats = us.get("statistics", {}).get("anime", {}) + manga_stats = us.get("statistics", {}).get("manga", {}) + + caption += f"**ᴅᴀʏs ᴡᴀsᴛᴇᴅ ᴡᴀᴛᴄʜɪɴɢ ᴀɴɪᴍᴇ**: `{anime_stats.get('days_watched', 0)}`\n" + caption += f"**ᴅᴀʏs ᴡᴀsᴛᴇᴅ ʀᴇᴀᴅɪɴɢ ᴍᴀɴɢᴀ**: `{manga_stats.get('days_read', 0)}`\n\n" + + about = us.get("about", "N/A") + if len(about) > 300: + about = about[:300] + "..." + caption += f"**ᴀʙᴏᴜᴛ**: {about}" + + buttons = [ + [InlineKeyboardButton(info_btn, url=us["url"])], + [InlineKeyboardButton(close_btn, callback_data=f"anime_close,{message.from_user.id}")] + ] + + await message.reply_photo(photo=img, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) + +@pbot.on_message(filters.command("upcoming")) +@DisableAbleCommandHandler("upcoming") +async def upcoming_anime(client: Client, message: Message): + async with AioJikan() as jikan: + upcomin = await jikan.top(type="anime", filter="upcoming") + + upcoming_message = "Upcoming Anime:\n" + for i, entry in enumerate(upcomin["data"][:10]): + upcoming_message += f"{i + 1}. {entry['title']}\n" + + await message.reply_text(upcoming_message) + +async def search_site(message, query, site): + search_url = f"https://{site}.com/?s={query}" + async with aiohttp.ClientSession() as session: + async with session.get(search_url) as resp: + html_text = await resp.text() + + soup = bs4.BeautifulSoup(html_text, "html.parser") + if site == "animekaizoku": + search_result = soup.find_all("h2", {"class": "post-title"}) + else: + search_result = soup.find_all("h2", {"class": "title"}) + + if not search_result: + await message.reply_text(f"**No result found for** `{query}`") + return + + result = f"**Search results for** `{query}`\n" + for entry in search_result: + if entry.text.strip() == "Nothing Found": + await message.reply_text(f"**No result found for** `{query}`") + return + + link = entry.a["href"] + if site == "animekaizoku" and not link.startswith("http"): + link = "https://animekaizoku.com/" + link + + name = html.escape(entry.text.strip()) + result += f"• [{name}]({link})\n" + + buttons = [[InlineKeyboardButton("See all results", url=search_url)]] + await message.reply_text(result, reply_markup=InlineKeyboardMarkup(buttons), disable_web_page_preview=True) + +@pbot.on_message(filters.command("kaizoku")) +@DisableAbleCommandHandler("kaizoku") +async def kaizoku_cmd(client: Client, message: Message): + query = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not query: + await message.reply_text("Give something to search") + return + await search_site(message, query, "animekaizoku") + +@pbot.on_message(filters.command("kayo")) +@DisableAbleCommandHandler("kayo") +async def kayo_cmd(client: Client, message: Message): + query = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not query: + await message.reply_text("Give something to search") + return + await search_site(message, query, "animekayo") + +__mod_name__ = "Aɴɪᴍᴇ" +__help__ = """ +ɢᴇᴛ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀɴɪᴍᴇ, ᴍᴀɴɢᴀ ᴏʀ ᴄʜᴀʀᴀᴄᴛᴇʀs ғʀᴏᴍ [ᴀɴɪʟɪsᴛ](ᴀɴɪʟɪsᴛ.ᴄᴏ). + +**ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs:** +• `/anime <anime>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴀɴɪᴍᴇ. +• `/character <ᴄʜᴀʀᴀᴄᴛᴇʀ>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴄʜᴀʀᴀᴄᴛᴇʀ. +• `/manga <ᴍᴀɴɢᴀ>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴍᴀɴɢᴀ. +• `/user <ᴜsᴇʀ>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀ ᴍʏᴀɴɪᴍᴇʟɪsᴛ ᴜsᴇʀ. +• `/upcoming`: ʀᴇᴛᴜʀɴs ᴀ ʟɪsᴛ ᴏғ ɴᴇᴡ ᴀɴɪᴍᴇ ɪɴ ᴛʜᴇ ᴜᴘᴄᴏᴍɪɴɢ sᴇᴀsᴏɴs. +• `/kaizoku <ᴀɴɪᴍᴇ>`: sᴇᴀʀᴄʜ ᴀɴ ᴀɴɪᴍᴇ ᴏɴ ᴀɴɪᴍᴇᴋᴀɪᴢᴏᴋᴜ.ᴄᴏᴍ +• `/kayo <ᴀɴɪᴍᴇ>`: sᴇᴀʀᴄʜ ᴀɴ ᴀɴɪᴍᴇ ᴏɴ ᴀɴɪᴍᴇᴋᴀʏᴏ.ᴄᴏᴍ +• `/airing <ᴀɴɪᴍᴇ>`: ʀᴇᴛᴜʀɴs ᴀɴɪᴍᴇ ᴀɪɪɴɢ ɪɴғᴏ. +""" diff --git a/QueenNoxi/modules/antiban.py b/QueenNoxi/modules/antiban.py new file mode 100644 index 0000000000000000000000000000000000000000..f7526cd4647c973703a9244c82f1acb44fdd66f2 --- /dev/null +++ b/QueenNoxi/modules/antiban.py @@ -0,0 +1,54 @@ +from pyrogram import Client +from pyrogram.raw import functions, types +from pyrogram.raw.base import Update + + +@Client.on_raw_update() +async def channel_handler(client: Client, update: Update, _, chats: dict): + while True: + try: + # Check for message that are from channel + if not isinstance(update, types.UpdateNewChannelMessage) or not isinstance( + update.message.from_id, types.PeerChannel + ): + return + # Basic data + message = update.message + chat_id = int(f"-100{message.peer_id.channel_id}") + channel_id = int(f"-100{message.from_id.channel_id}") + # Check enable or not + # Check for linked or free channel + if ( + message.fwd_from + and message.fwd_from.saved_from_peer + == message.fwd_from.from_id + == message.from_id + ) or channel_id == chat_id: + return + # Delete the message sent by channel and ban it + await client.send( + functions.channels.EditBanned( + channel=await client.resolve_peer(chat_id), + participant=await client.resolve_peer(channel_id), + banned_rights=types.ChatBannedRights( + until_date=0, + view_messages=True, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_polls=True, + ), + ) + ) + await client.delete_messages(chat_id, message.id) + await client.send_message( + int(chat_id), + f"#𝙰𝙽𝚃𝙸𝙲𝙷𝙰𝙽𝙽𝙴𝙻\n\n᛭ 𝚂𝙴𝙽𝙳𝙴𝚁 𝙸𝙳: `{channel_id}`\n᛭ 𝚃𝙰𝙺𝙴𝙽 𝙰𝙲𝚃𝙸𝙾𝙽: `DELETE BAN`", + disable_web_page_preview=True, + ) + break + except Exception as e: + print(e) + break diff --git a/QueenNoxi/modules/approve.py b/QueenNoxi/modules/approve.py new file mode 100644 index 0000000000000000000000000000000000000000..9ac6125544656b7a150287a7249984dd136823bd --- /dev/null +++ b/QueenNoxi/modules/approve.py @@ -0,0 +1,166 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + CallbackQuery +) +from pyrogram.errors import RPCError + +import QueenNoxi.modules.sql.approve_sql as sql +from QueenNoxi import DRAGONS, pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import user_admin, is_user_admin +from QueenNoxi.modules.helper_funcs.extraction import extract_user +from QueenNoxi.modules.log_channel import loggable + +@pbot.on_message(filters.command("approve") & filters.group) +@DisableAbleCommandHandler("approve") +@user_admin +@loggable +async def approve(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("I don't know who you're talking about, you're going to need to specify a user!") + return "" + + try: + member = await chat.get_member(user_id) + except RPCError: + return "" + + if member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER): + await message.reply_text("User is already admin - locks, blocklists, and antiflood already don't apply to them.") + return "" + + if sql.is_approved(chat.id, user_id): + await message.reply_text(f"{member.user.mention} is already approved in {chat.title}") + return "" + + sql.approve(chat.id, user_id) + await message.reply_text(f"{member.user.mention} has been approved in {chat.title}! They will now be ignored by automated admin actions like locks, blocklists, and antiflood.") + + return f"<b>{html.escape(chat.title)}:</b>\n#APPROVED\n<b>Admin:</b> {user.mention}\n<b>User:</b> {member.user.mention}" + +@pbot.on_message(filters.command("unapprove") & filters.group) +@DisableAbleCommandHandler("unapprove") +@user_admin +@loggable +async def disapprove(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("I don't know who you're talking about, you're going to need to specify a user!") + return "" + + try: + member = await chat.get_member(user_id) + except RPCError: + return "" + + if member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER): + await message.reply_text("This user is an admin, they can't be unapproved.") + return "" + + if not sql.is_approved(chat.id, user_id): + await message.reply_text(f"{member.user.first_name} isn't approved yet!") + return "" + + sql.disapprove(chat.id, user_id) + await message.reply_text(f"{member.user.first_name} is no longer approved in {chat.title}.") + + return f"<b>{html.escape(chat.title)}:</b>\n#UNAPPROVED\n<b>Admin:</b> {user.mention}\n<b>User:</b> {member.user.mention}" + +@pbot.on_message(filters.command("approved") & filters.group) +@DisableAbleCommandHandler("approved") +@user_admin +async def approved(client: Client, message: Message): + chat = message.chat + msg = "The following users are approved.\n" + approved_users = sql.list_approved(chat.id) + for i in approved_users: + try: + member = await chat.get_member(int(i.user_id)) + msg += f"- `{i.user_id}`: {member.user.first_name}\n" + except: + msg += f"- `{i.user_id}`\n" + + if msg.endswith("approved.\n"): + await message.reply_text(f"No users are approved in {chat.title}.") + else: + await message.reply_text(msg) + +@pbot.on_message(filters.command("approval") & filters.group) +@DisableAbleCommandHandler("approval") +@user_admin +async def approval(client: Client, message: Message): + chat = message.chat + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("I don't know who you're talking about, you're going to need to specify a user!") + return "" + + member = await chat.get_member(int(user_id)) + if sql.is_approved(chat.id, user_id): + await message.reply_text(f"{member.user.first_name} is an approved user. Locks, antiflood, and blocklists won't apply to them.") + else: + await message.reply_text(f"{member.user.first_name} is not an approved user. They are affected by normal commands.") + +@pbot.on_message(filters.command("unapproveall") & filters.group) +@DisableAbleCommandHandler("unapproveall") +async def unapproveall(client: Client, message: Message): + chat = message.chat + user = message.from_user + member = await chat.get_member(user.id) + if member.status != enums.ChatMemberStatus.OWNER and user.id not in DRAGONS: + await message.reply_text("Only the chat owner can unapprove all users at once.") + return + + buttons = InlineKeyboardMarkup( + [ + [InlineKeyboardButton(text="Unapprove all users", callback_data="unapproveall_user")], + [InlineKeyboardButton(text="Cancel", callback_data="unapproveall_cancel")], + ] + ) + await message.reply_text( + f"Are you sure you would like to unapprove ALL users in {chat.title}? This action cannot be undone.", + reply_markup=buttons, + ) + +@pbot.on_callback_query(filters.regex(r"unapproveall_.*")) +async def unapproveall_btn(client: Client, query: CallbackQuery): + chat = query.message.chat + user_id = query.from_user.id + member = await chat.get_member(user_id) + + if query.data == "unapproveall_user": + if member.status == enums.ChatMemberStatus.OWNER or user_id in DRAGONS: + approved_users = sql.list_approved(chat.id) + for i in approved_users: + sql.disapprove(chat.id, int(i.user_id)) + await query.message.edit_text("Successfully unapproved all users.") + else: + await query.answer("Only the owner of the chat can do this.", show_alert=True) + + elif query.data == "unapproveall_cancel": + if member.status == enums.ChatMemberStatus.OWNER or user_id in DRAGONS: + await query.message.edit_text("Removing of all approved users has been cancelled.") + else: + await query.answer("Only the owner of the chat can do this.", show_alert=True) + +__mod_name__ = "Approve" +__help__ = """ +Some users might be trustworthy enough to be ignored by automated admin actions. +Approval allows them to be bypassed by locks, blocklists, and antiflood. + +**Admin Commands:** +• `/approval`: Check a user's approval status +• `/approve`: Approve a user +• `/unapprove`: Unapprove a user +• `/approved`: List all approved users +• `/unapproveall`: Unapprove ALL users (Owner only) +""" diff --git a/QueenNoxi/modules/backups.py b/QueenNoxi/modules/backups.py new file mode 100644 index 0000000000000000000000000000000000000000..a026abf94cefab8a1e86e8ce92360a80eb9c6e96 --- /dev/null +++ b/QueenNoxi/modules/backups.py @@ -0,0 +1,387 @@ +import json +import os +import time +from io import BytesIO + +from telegram import ParseMode +from telegram.error import BadRequest +from telegram.ext import CommandHandler + +# from QueenNoxi.modules.sql import warns_sql as warnssql +import QueenNoxi.modules.sql.blacklist_sql as blacklistsql + +# from QueenNoxi.modules.sql import cust_filters_sql as filtersql +# import QueenNoxi.modules.sql.welcome_sql as welcsql +import QueenNoxi.modules.sql.locks_sql as locksql +import QueenNoxi.modules.sql.notes_sql as sql + +# from QueenNoxi.modules.rules import get_rules +import QueenNoxi.modules.sql.rules_sql as rulessql +from QueenNoxi import EVENT_LOGS,OWNER_ID, SUPPORT_CHAT, dispatcher +from QueenNoxi.__main__ import DATA_IMPORT +from QueenNoxi.modules.connection import connected +from QueenNoxi.modules.helper_funcs.alternate import typing_action +from QueenNoxi.modules.helper_funcs.chat_status import user_admin +from QueenNoxi.modules.sql import disable_sql as disabledsql + + +@user_admin +@typing_action +def import_data(update, context): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + # TODO: allow uploading doc with command, not just as reply + # only work with a doc + + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + update.effective_message.reply_text("This is a group only command!") + return "" + + chat = update.effective_chat + chat_name = update.effective_message.chat.title + + if msg.reply_to_message and msg.reply_to_message.document: + try: + file_info = context.bot.get_file(msg.reply_to_message.document.file_id) + except BadRequest: + msg.reply_text( + "Try downloading and uploading the file yourself again, This one seem broken to me!" + ) + return + + with BytesIO() as file: + file_info.download(out=file) + file.seek(0) + data = json.load(file) + + # only import one group + if len(data) > 1 and str(chat.id) not in data: + msg.reply_text( + "There are more than one group in this file and the chat.id is not same! How am i supposed to import it?" + ) + return + + # Check if backup is this chat + try: + if data.get(str(chat.id)) is None: + if conn: + text = "Backup comes from another chat, I can't return another chat to chat *{}*".format( + chat_name + ) + else: + text = "Backup comes from another chat, I can't return another chat to this chat" + return msg.reply_text(text, parse_mode="markdown") + except Exception: + return msg.reply_text("There was a problem while importing the data!") + # Check if backup is from self + try: + if str(context.bot.id) != str(data[str(chat.id)]["bot"]): + return msg.reply_text( + "Backup from another bot that is not suggested might cause the problem, documents, photos, videos, audios, records might not work as it should be." + ) + except Exception: + pass + # Select data source + if str(chat.id) in data: + data = data[str(chat.id)]["hashes"] + else: + data = data[list(data.keys())[0]]["hashes"] + + try: + for mod in DATA_IMPORT: + mod.__import_data__(str(chat.id), data) + except Exception: + msg.reply_text( + f"An error occurred while recovering your data. The process failed. If you experience a problem with this, please take it to @{SUPPORT_CHAT}" + ) + + EVENT_LOGS.exception( + "Imprt for the chat %s with the name %s failed.", + str(chat.id), + str(chat.title), + ) + return + + # TODO: some of that link logic + # NOTE: consider default permissions stuff? + if conn: + + text = "Backup fully restored on *{}*.".format(chat_name) + else: + text = "Backup fully restored" + msg.reply_text(text, parse_mode="markdown") + + +@user_admin +def export_data(update, context): + chat_data = context.chat_data + msg = update.effective_message # type: Optional[Message] + user = update.effective_user # type: Optional[User] + chat_id = update.effective_chat.id + chat = update.effective_chat + current_chat_id = update.effective_chat.id + conn = connected(context.bot, update, chat, user.id, need_admin=True) + if conn: + chat = dispatcher.bot.getChat(conn) + chat_id = conn + # chat_name = dispatcher.bot.getChat(conn).title + else: + if update.effective_message.chat.type == "private": + update.effective_message.reply_text("This is a group only command!") + return "" + chat = update.effective_chat + chat_id = update.effective_chat.id + # chat_name = update.effective_message.chat.title + + jam = time.time() + new_jam = jam + 10800 + checkchat = get_chat(chat_id, chat_data) + if checkchat.get("status"): + if jam <= int(checkchat.get("value")): + timeformatt = time.strftime( + "%H:%M:%S %d/%m/%Y", time.localtime(checkchat.get("value")) + ) + update.effective_message.reply_text( + "You can only backup once a day!\nYou can backup again in about `{}`".format( + timeformatt + ), + parse_mode=ParseMode.MARKDOWN, + ) + return + else: + if user.id != OWNER_ID: + put_chat(chat_id, new_jam, chat_data) + else: + if user.id != OWNER_ID: + put_chat(chat_id, new_jam, chat_data) + + note_list = sql.get_all_chat_notes(chat_id) + backup = {} + # button = "" + buttonlist = [] + namacat = "" + isicat = "" + rules = "" + count = 0 + countbtn = 0 + # Notes + for note in note_list: + count += 1 + # getnote = sql.get_note(chat_id, note.name) + namacat += "{}<###splitter###>".format(note.name) + if note.msgtype == 1: + tombol = sql.get_buttons(chat_id, note.name) + # keyb = [] + for btn in tombol: + countbtn += 1 + if btn.same_line: + buttonlist.append( + ("{}".format(btn.name), "{}".format(btn.url), True) + ) + else: + buttonlist.append( + ("{}".format(btn.name), "{}".format(btn.url), False) + ) + isicat += "###button###: {}<###button###>{}<###splitter###>".format( + note.value, str(buttonlist) + ) + buttonlist.clear() + elif note.msgtype == 2: + isicat += "###sticker###:{}<###splitter###>".format(note.file) + elif note.msgtype == 3: + isicat += "###file###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, note.value + ) + elif note.msgtype == 4: + isicat += "###photo###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, note.value + ) + elif note.msgtype == 5: + isicat += "###audio###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, note.value + ) + elif note.msgtype == 6: + isicat += "###voice###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, note.value + ) + elif note.msgtype == 7: + isicat += "###video###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, note.value + ) + elif note.msgtype == 8: + isicat += "###video_note###:{}<###TYPESPLIT###>{}<###splitter###>".format( + note.file, note.value + ) + else: + isicat += "{}<###splitter###>".format(note.value) + notes = { + "#{}".format(namacat.split("<###splitter###>")[x]): "{}".format( + isicat.split("<###splitter###>")[x] + ) + for x in range(count) + } + # Rules + rules = rulessql.get_rules(chat_id) + # Blacklist + bl = list(blacklistsql.get_chat_blacklist(chat_id)) + # Disabled command + disabledcmd = list(disabledsql.get_all_disabled(chat_id)) + # Filters (TODO) + """ + all_filters = list(filtersql.get_chat_triggers(chat_id)) + export_filters = {} + for filters in all_filters: + filt = filtersql.get_filter(chat_id, filters) + # print(vars(filt)) + if filt.is_sticker: + tipefilt = "sticker" + elif filt.is_document: + tipefilt = "doc" + elif filt.is_image: + tipefilt = "img" + elif filt.is_audio: + tipefilt = "audio" + elif filt.is_voice: + tipefilt = "voice" + elif filt.is_video: + tipefilt = "video" + elif filt.has_buttons: + tipefilt = "button" + buttons = filtersql.get_buttons(chat.id, filt.keyword) + print(vars(buttons)) + elif filt.has_markdown: + tipefilt = "text" + if tipefilt == "button": + content = "{}#=#{}|btn|{}".format(tipefilt, filt.reply, buttons) + else: + content = "{}#=#{}".format(tipefilt, filt.reply) + print(content) + export_filters[filters] = content + print(export_filters) + """ + # Welcome (TODO) + # welc = welcsql.get_welc_pref(chat_id) + # Locked + curr_locks = locksql.get_locks(chat_id) + curr_restr = locksql.get_restr(chat_id) + + if curr_locks: + locked_lock = { + "sticker": curr_locks.sticker, + "audio": curr_locks.audio, + "voice": curr_locks.voice, + "document": curr_locks.document, + "video": curr_locks.video, + "contact": curr_locks.contact, + "photo": curr_locks.photo, + "gif": curr_locks.gif, + "url": curr_locks.url, + "bots": curr_locks.bots, + "forward": curr_locks.forward, + "game": curr_locks.game, + "location": curr_locks.location, + "rtl": curr_locks.rtl, + } + else: + locked_lock = {} + + if curr_restr: + locked_restr = { + "messages": curr_restr.messages, + "media": curr_restr.media, + "other": curr_restr.other, + "previews": curr_restr.preview, + "all": all( + [ + curr_restr.messages, + curr_restr.media, + curr_restr.other, + curr_restr.preview, + ] + ), + } + else: + locked_restr = {} + + locks = {"locks": locked_lock, "restrict": locked_restr} + # Warns (TODO) + # warns = warnssql.get_warns(chat_id) + # Backing up + backup[chat_id] = { + "bot": context.bot.id, + "hashes": { + "info": {"rules": rules}, + "extra": notes, + "blacklist": bl, + "disabled": disabledcmd, + "locks": locks, + }, + } + baccinfo = json.dumps(backup, indent=4) + with open("QueenNoxi{}backup.json".format(chat_id), "w") as f: + f.write(str(baccinfo)) + context.bot.sendChatAction(current_chat_id, "upload_document") + tgl = time.strftime("%H:%M:%S - %d/%m/%Y", time.localtime(time.time())) + try: + context.bot.sendMessage( + EVENT_LOGS, + "*Successfully imported backup:*\nChat: `{}`\nChat ID: `{}`\nOn: `{}`".format( + chat.title, chat_id, tgl + ), + parse_mode=ParseMode.MARKDOWN, + ) + except BadRequest: + pass + context.bot.sendDocument( + current_chat_id, + document=open("QueenNoxi{}backup.json".format(chat_id), "rb"), + caption="📤*Successfully Exported backup:*\nChat: `{}`\nChat ID: `{}`\nOn: `{}`\n\nNote: This `QueenNoxi-Backup` was specially made for notes 📚.".format( + chat.title, chat_id, tgl + ), + timeout=360, + reply_to_message_id=msg.message_id, + parse_mode=ParseMode.MARKDOWN, + ) + os.remove("QueenNoxi{}backup.json".format(chat_id)) # Cleaning file + + +# Temporary data +def put_chat(chat_id, value, chat_data): + # print(chat_data) + status = value is not False + chat_data[chat_id] = {"backups": {"status": status, "value": value}} + + +def get_chat(chat_id, chat_data): + # print(chat_data) + try: + return chat_data[chat_id]["backups"] + except KeyError: + return {"status": False, "value": False} + + +__mod_name__ = "Bᴀᴄᴋᴜᴘ" + +__help__ = """ +*ᴏɴʟʏ ғᴏʀ ɢʀᴏᴜᴘ ᴏᴡɴᴇʀ:* + + ❍ /import : ʀᴇᴘʟʏ ᴛᴏ ᴛʜᴇ ʙᴀᴄᴋᴜᴘ ғɪʟᴇ ғᴏʀ ᴛʜᴇ ʙᴜᴛʟᴇʀ / ᴇᴍɪʟɪᴀ ɢʀᴏᴜᴘ ᴛᴏ ɪᴍᴘᴏʀᴛ ᴀs ᴍᴜᴄʜ ᴀs ᴘᴏssɪʙʟᴇ, ᴍᴀᴋɪɴɢ ᴛʀᴀɴsғᴇʀs ᴠᴇʀʏ ᴇᴀsʏ! \ + ɴᴏᴛᴇ ᴛʜᴀᴛ ғɪʟᴇs / ᴘʜᴏᴛᴏs ᴄᴀɴɴᴏᴛ ʙᴇ ɪᴍᴘᴏʀᴛᴇᴅ ᴅᴜᴇ ᴛᴏ ᴛᴇʟᴇɢʀᴀᴍ ʀᴇsᴛʀɪᴄᴛɪᴏɴs. + + ❍ /export : ᴇxᴘᴏʀᴛ ɢʀᴏᴜᴘ ᴅᴀᴛᴀ, ᴡʜɪᴄʜ ᴡɪʟʟ ʙᴇ ᴇxᴘᴏʀᴛᴇᴅ ᴀʀᴇ: ʀᴜʟᴇs, ɴᴏᴛᴇs (ᴅᴏᴄᴜᴍᴇɴᴛs, ɪᴍᴀɢᴇs, ᴍᴜsɪᴄ, ᴠɪᴅᴇᴏ, ᴀᴜᴅɪᴏ, ᴠᴏɪᴄᴇ, ᴛᴇxᴛ, ᴛᴇxᴛ ʙᴜᴛᴛᴏɴs) \ + +""" + +IMPORT_HANDLER = CommandHandler("import", import_data, run_async=True) +EXPORT_HANDLER = CommandHandler( + "export", export_data, pass_chat_data=True, run_async=True +) + +dispatcher.add_handler(IMPORT_HANDLER) +dispatcher.add_handler(EXPORT_HANDLER) diff --git a/QueenNoxi/modules/bans.py b/QueenNoxi/modules/bans.py new file mode 100644 index 0000000000000000000000000000000000000000..c1bf57be66cf6ed4411105275cdf00255c9113f2 --- /dev/null +++ b/QueenNoxi/modules/bans.py @@ -0,0 +1,325 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import Message +from pyrogram.errors import RPCError + +from QueenNoxi import ( + DEMONS, + DEV_USERS, + DRAGONS, + LOGGER, + OWNER_IDS, + TIGERS, + WOLVES, + pbot, + BOT_ID +) +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import ( + bot_admin, + can_delete, + can_restrict, + connection_status, + is_user_admin, + is_user_ban_protected, + is_user_in_chat, + user_admin, + user_can_ban, +) +from QueenNoxi.modules.helper_funcs.extraction import extract_user_and_text +from QueenNoxi.modules.helper_funcs.string_handling import extract_time +from QueenNoxi.modules.log_channel import gloggable, loggable + + +@pbot.on_message(filters.command(["ban", "sban"]) & filters.group) +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +async def ban(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + + if not user_id: + await message.reply_text("ɪ ᴅᴏᴜʙᴛ ᴛʜᴀᴛ's ᴀ ᴜsᴇʀ.") + return "" + + try: + member = await chat.get_member(user_id) + except RPCError: + await message.reply_text("ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴘᴇʀsᴏɴ.") + return "" + + if user_id == BOT_ID: + await message.reply_text("ᴏʜ ʏᴇᴀʜ, ʙᴀɴ ᴍʏsᴇʟғ, ɴᴏᴏʙ!") + return "" + + if await is_user_ban_protected(chat, user_id, member) and user.id not in DEV_USERS: + if user_id in OWNER_IDS: + await message.reply_text("ᴛʀʏɪɴɢ ᴛᴏ ᴘᴜᴛ ᴍᴇ ᴀɢᴀɪɴsᴛ ᴀ ɢᴏᴅ ʟᴇᴠᴇʟ ᴅɪsᴀsᴛᴇʀ ʜᴜʜ?") + elif user_id in DEV_USERS: + await message.reply_text("ɪ ᴄᴀɴ'ᴛ ᴀᴄᴛ ᴀɢᴀɪɴsᴛ ᴏᴜʀ ᴏᴡɴ.") + elif user_id in DRAGONS: + await message.reply_text("ғɪɢʜᴛɪɴɢ ᴛʜɪs ᴅʀᴀɢᴏɴ ʜᴇʀᴇ ᴡɪʟʟ ᴘᴜᴛ ᴄɪᴠɪʟɪᴀɴ ʟɪᴠᴇs ᴀᴛ ʀɪsᴋ.") + elif user_id in DEMONS: + await message.reply_text("ʙʀɪɴɢ ᴀɴ ᴏʀᴅᴇʀ ғʀᴏᴍ ʜᴇʀᴏᴇs ᴀssᴏᴄɪᴀᴛɪᴏɴ ᴛᴏ ғɪɢʜᴛ ᴀ ᴅᴇᴍᴏɴ ᴅɪsᴀsᴛᴇʀ.") + elif user_id in TIGERS: + await message.reply_text("ʙʀɪɴɢ ᴀɴ ᴏʀᴅᴇʀ ғʀᴏᴍ ʜᴇʀᴏᴇs ᴀssᴏᴄɪᴀᴛɪᴏɴ ᴛᴏ ғɪɢʜᴛ ᴀ ᴛɪɢᴇʀ ᴅɪsᴀsᴛᴇʀ.") + elif user_id in WOLVES: + await message.reply_text("ᴡᴏʟғ ᴀʙɪʟɪᴛɪᴇs ᴍᴀᴋᴇ ᴛʜᴇᴍ ʙᴀɴ ɪᴍᴍᴜɴᴇ!") + else: + await message.reply_text("ᴛʜɪs ᴜsᴇʀ ɪs ʙᴀɴ ᴘʀᴏᴛᴇᴄᴛᴇᴅ!") + return "" + + silent = message.command[0].lower() == "sban" + if silent and not await can_delete(chat, BOT_ID): + return "" + + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#{'S' if silent else ''}ʙᴀɴɴᴇᴅ\n" + f"<b>ʙᴀɴɴᴇᴅ ʙʏ:</b> {user.mention}\n" + f"<b>ᴜsᴇʀ:</b> {member.user.mention}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + + try: + await chat.ban_member(user_id) + if silent: + if message.reply_to_message: + await message.reply_to_message.delete() + await message.delete() + return log + + reply = ( + f"<code>❕</code><b>ʙᴀɴ ᴇᴠᴇɴᴛ</b>\n" + f"<code> </code><b>• ʙᴀɴɴᴇᴅ ʙʏ:</b> {user.mention}\n" + f"<code> </code><b>• ᴜsᴇʀ:</b> {member.user.mention}" + ) + if reason: + reply += f"\n<code> </code><b>• ʀᴇᴀsᴏɴ:</b> \n{html.escape(reason)}" + await message.reply_text(reply) + return log + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + return "" + + +@pbot.on_message(filters.command("tban") & filters.group) +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +async def temp_ban(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + + if not user_id: + await message.reply_text("ɪ ᴅᴏᴜʙᴛ ᴛʜᴀᴛ's ᴀ ᴜsᴇʀ.") + return "" + + try: + member = await chat.get_member(user_id) + except RPCError: + await message.reply_text("ɪ ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return "" + + if user_id == BOT_ID: + await message.reply_text("ɪ'ᴍ ɴᴏᴛ ɢᴏɴɴᴀ ʙᴀɴ ᴍʏsᴇʟғ.") + return "" + + if await is_user_ban_protected(chat, user_id, member): + await message.reply_text("ɪ ᴅᴏɴ'ᴛ ғᴇᴇʟ ʟɪᴋᴇ ɪᴛ.") + return "" + + if not reason: + await message.reply_text("ʏᴏᴜ ʜᴀᴠᴇɴ'ᴛ sᴘᴇᴄɪғɪᴇᴅ ᴀ ᴛɪᴍᴇ ᴛᴏ ʙᴀɴ ᴛʜɪs ᴜsᴇʀ ғᴏʀ!") + return "" + + split_reason = reason.split(None, 1) + time_val = split_reason[0].lower() + reason = split_reason[1] if len(split_reason) > 1 else "" + bantime = await extract_time(message, time_val) + + if not bantime: + return "" + + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + "ᴛᴇᴍᴩ ʙᴀɴ\n" + f"<b>ʙᴀɴɴᴇᴅ ʙʏ:</b> {user.mention}\n" + f"<b>ᴜsᴇʀ:</b> {member.user.mention}\n" + f"<b>ᴛɪᴍᴇ:</b> {time_val}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + + try: + await chat.ban_member(user_id, until_date=bantime) + await message.reply_text( + f"ʙᴀɴɴᴇᴅ! ᴜsᴇʀ {member.user.mention} ɪs ɴᴏᴡ ʙᴀɴɴᴇᴅ ғᴏʀ {time_val}." + ) + return log + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + return "" + + +@pbot.on_message(filters.command("kick") & filters.group) +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +async def kick(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + + if not user_id: + await message.reply_text("ɪ ᴅᴏᴜʙᴛ ᴛʜᴀᴛ's ᴀ ᴜsᴇʀ.") + return "" + + try: + member = await chat.get_member(user_id) + except RPCError: + await message.reply_text("ɪ ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return "" + + if user_id == BOT_ID: + await message.reply_text("ʏᴇᴀʜʜʜ ɪ'ᴍ ɴᴏᴛ ɢᴏɴɴᴀ ᴅᴏ ᴛʜᴀᴛ.") + return "" + + if await is_user_ban_protected(chat, user_id): + await message.reply_text("I really wish I could kick this user....") + return "" + + try: + await chat.unban_member(user_id) # unban = kick in telegram + await message.reply_text(f"One Kicked! {member.user.mention}.") + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"ᴋɪᴄᴋᴇᴅ\n" + f"<b>ᴋɪᴄᴋᴇᴅ ʙʏ:</b> {user.mention}\n" + f"<b>ᴜsᴇʀ:</b> {member.user.mention}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + return log + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + return "" + + +@DisableAbleCommandHandler("kickme", admin_ok=False) +async def kickme(client: Client, message: Message): + user_id = message.from_user.id + if await is_user_admin(message.chat, user_id): + await message.reply_text("ɪ ᴡɪsʜ ɪ ᴄᴏᴜʟᴅ... ʙᴜᴛ ʏᴏᴜ'ʀᴇ ᴀɴ ᴀᴅᴍɪɴ.") + return + + try: + await message.chat.unban_member(user_id) + await message.reply_text("*ᴋɪᴄᴋs ʏᴏᴜ ᴏᴜᴛ ᴏғ ᴛʜᴇ ɢʀᴏᴜᴘ*") + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + + +@pbot.on_message(filters.command("unban") & filters.group) +@connection_status +@bot_admin +@can_restrict +@user_admin +@user_can_ban +@loggable +async def unban(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + + if not user_id: + await message.reply_text("ɪ ᴅᴏᴜʙᴛ ᴛʜᴀᴛ's ᴀ ᴜsᴇʀ.") + return "" + + try: + member = await client.get_users(user_id) + except RPCError: + await message.reply_text("ɪ ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴜsᴇʀ.") + return "" + + if user_id == BOT_ID: + await message.reply_text("ʜᴏᴡ ᴡᴏᴜʟᴅ ɪ ᴜɴʙᴀɴ ᴍʏsᴇʟғ?") + return "" + + + try: + await chat.unban_member(user_id) + await message.reply_text("Yep, this user can join!") + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"ᴜɴʙᴀɴɴᴇᴅ\n" + f"<b>ᴜɴʙᴀɴɴᴇᴅ ʙʏ:</b> {user.mention}\n" + f"<b>ᴜsᴇʀ:</b> {member.mention}" + ) + if reason: + log += f"\n<b>ʀᴇᴀsᴏɴ:</b> {html.escape(reason)}" + return log + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + return "" + + +@pbot.on_message(filters.command("roar") & filters.group) +@connection_status +@bot_admin +@can_restrict +@gloggable +async def selfunban(client: Client, message: Message) -> str: + user = message.from_user + if user.id not in DRAGONS and user.id not in TIGERS: + return "" + + if len(message.command) < 2: + await message.reply_text("ɢɪᴠᴇ ᴀ ᴠᴀʟɪᴅ ᴄʜᴀᴛ ɪᴅ.") + return "" + + try: + chat_id = int(message.command[1]) + chat = await client.get_chat(chat_id) + except Exception: + await message.reply_text("ɪ ᴄᴀɴ'ᴛ sᴇᴇᴍ ᴛᴏ ғɪɴᴅ ᴛʜɪs ᴄʜᴀᴛ.") + return "" + + try: + await chat.unban_member(user.id) + await message.reply_text("ʏᴇᴘ, ɪ ʜᴀᴠᴇ ᴜɴʙᴀɴɴᴇᴅ ʏᴏᴜ.") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"ᴜɴʙᴀɴɴᴇᴅ\n" + f"<b>ᴜɴʙᴀɴɴᴇᴅ ʙʏ:</b> {user.mention}\n" + f"<b>ᴜsᴇʀ:</b> {user.mention}" + ) + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + return "" + + +__mod_name__ = "Ban" +__help__ = """ + ❍ /kickme: Kicks the user who issued the command + +*Admins only:* + ❍ /ban <userhandle>: Bans a user. (via handle, or reply) + ❍ /sban <userhandle>: Silently ban a user. + ❍ /tban <userhandle> x(m/h/d): Bans a user for `x` time. + ❍ /unban <userhandle>: Unbans a user. + ❍ /kick <userhandle>: Kicks a user out of the group. +""" diff --git a/QueenNoxi/modules/blacklist.py b/QueenNoxi/modules/blacklist.py new file mode 100644 index 0000000000000000000000000000000000000000..cce02f826d6eaf5721a8ac5f43be053ba77e6bd7 --- /dev/null +++ b/QueenNoxi/modules/blacklist.py @@ -0,0 +1,129 @@ +import html +import re +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +import QueenNoxi.modules.sql.blacklist_sql as sql +from QueenNoxi import LOGGER, pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import user_admin, connection_status +from QueenNoxi.modules.warns import warn + +BLACKLIST_GROUP = 11 + +@DisableAbleCommandHandler("blacklist", admin_ok=True) +@connection_status +async def blacklist(client: Client, message: Message): + chat_id = message.chat.id + triggers = sql.get_chat_blacklist(chat_id) + if not triggers: + return await message.reply_text("No blacklist filters active here!") + + res = "Blacklist filters active in this chat:\n" + for trigger in triggers: + res += f" • {html.escape(trigger)}\n" + await message.reply_text(res) + +@DisableAbleCommandHandler("addblacklist", admin_ok=True) +@connection_status +@user_admin +async def add_blacklist(client: Client, message: Message): + args = message.text.split(None, 1) + if len(args) < 2: + return await message.reply_text("Give me a keyword!") + + words = args[1].split("\n") + for word in words: + trigger = word.strip().lower() + if trigger: + sql.add_to_blacklist(message.chat.id, trigger) + + await message.reply_text("Added to blacklist!") + +@DisableAbleCommandHandler(["unblacklist", "rmblacklist"], admin_ok=True) +@connection_status +@user_admin +async def unblacklist(client: Client, message: Message): + args = message.text.split(None, 1) + if len(args) < 2: + return await message.reply_text("What should I remove?") + + trigger = args[1].strip().lower() + if sql.rm_from_blacklist(message.chat.id, trigger): + await message.reply_text(f"Removed '{trigger}' from blacklist.") + else: + await message.reply_text("Not found.") + +@DisableAbleCommandHandler("blacklistmode", admin_ok=True) +@connection_status +@user_admin +async def blacklist_mode(client: Client, message: Message): + args = message.command[1:] + chat_id = message.chat.id + if args: + mode = args[0].lower() + if mode in ("off", "nothing"): sql.set_blacklist_strength(chat_id, 0, "0") + elif mode == "del": sql.set_blacklist_strength(chat_id, 1, "0") + elif mode == "warn": sql.set_blacklist_strength(chat_id, 2, "0") + elif mode == "mute": sql.set_blacklist_strength(chat_id, 3, "0") + elif mode == "kick": sql.set_blacklist_strength(chat_id, 4, "0") + elif mode == "ban": sql.set_blacklist_strength(chat_id, 5, "0") + else: + return await message.reply_text("Invalid mode!") + await message.reply_text(f"Blacklist mode set to {mode}.") + else: + st, val = sql.get_blacklist_setting(chat_id) + modes = ["nothing", "del", "warn", "mute", "kick", "ban"] + await message.reply_text(f"Current blacklist mode: {modes[st]}") + +@pbot.on_message(filters.text & filters.group, group=BLACKLIST_GROUP) +async def del_blacklist(client: Client, message: Message): + chat_id = message.chat.id + if not message.from_user: return + + # Ignore admins + if await is_user_admin(message.chat, message.from_user.id): + return + + triggers = sql.get_chat_blacklist(chat_id) + if not triggers: return + + for trigger in triggers: + pattern = rf"( |^|[^\w]){re.escape(trigger)}( |$|[^\w])" + if re.search(pattern, message.text, flags=re.IGNORECASE): + st, val = sql.get_blacklist_setting(chat_id) + try: + if st == 0: + return + await message.delete() + if st == 1: + break # just delete + elif st == 2: # warn + await warn(message.from_user, chat_id, f"Blacklist trigger: {trigger}", message) + elif st == 3: # mute + await message.chat.restrict_member( + message.from_user.id, + enums.ChatMemberStatus.RESTRICTED, + enums.ChatPermissions(can_send_messages=False), + ) + elif st == 4: # kick + await message.chat.unban_member(message.from_user.id) + elif st == 5: # ban + await message.chat.ban_member(message.from_user.id) + except Exception as e: + LOGGER.error(f"Error in blacklist: {e}") + break + +async def is_user_admin(chat, user_id: int) -> bool: + from QueenNoxi.modules.helper_funcs.chat_status import is_user_admin as check_admin + return await check_admin(chat, user_id) + +__mod_name__ = "Blacklist" +__help__ = """ + • `/blacklist`: List blackened words. + • `/addblacklist <word>`: Blacklist a word. + • `/unblacklist <word>`: Un-blacklist a word. + • `/blacklistmode <mode>`: Set punishment. + +Modes: `off`, `del`, `warn`, `mute`, `kick`, `ban`. +""" diff --git a/QueenNoxi/modules/blacklist_stickers.py b/QueenNoxi/modules/blacklist_stickers.py new file mode 100644 index 0000000000000000000000000000000000000000..1ea22c623a61ff1031fa00e5b7324dbcdeebcfbc --- /dev/null +++ b/QueenNoxi/modules/blacklist_stickers.py @@ -0,0 +1,197 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + CallbackQuery, + ChatPermissions, +) +from pyrogram.errors import RPCError, BadRequest + +import QueenNoxi.modules.sql.blsticker_sql as sql +from QueenNoxi import LOGGER, pbot +from QueenNoxi.modules.connection import connected +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import user_admin, user_not_admin +from QueenNoxi.modules.helper_funcs.string_handling import extract_time +from QueenNoxi.modules.log_channel import loggable +from QueenNoxi.modules.warns import warn + +@pbot.on_message(filters.command("blsticker") & filters.group) +@user_admin +async def blackliststicker(client: Client, message: Message): + chat_id = await connected(client, message, message.from_user.id, need_admin=False) + if not chat_id: + chat_id = message.chat.id + chat_name = message.chat.title + else: + try: + chat = await client.get_chat(chat_id) + chat_name = chat.title + except: + chat_name = f"Chat {chat_id}" + + sticker_list = f"**List blacklisted stickers in {html.escape(chat_name)}:**\n" + all_stickerlist = sql.get_chat_stickers(chat_id) + + if not all_stickerlist: + await message.reply_text(f"There are no blacklisted stickers in **{html.escape(chat_name)}**!") + return + + for trigger in all_stickerlist: + sticker_list += f" - `{html.escape(trigger)}`\n" + + await message.reply_text(sticker_list) + +@pbot.on_message(filters.command("addblsticker") & filters.group) +@user_admin +async def add_blackliststicker(client: Client, message: Message): + chat_id = await connected(client, message, message.from_user.id) + if not chat_id: + chat_id = message.chat.id + chat_name = message.chat.title + else: + chat = await client.get_chat(chat_id) + chat_name = chat.title + + args = message.command[1:] + if args: + text = " ".join(args).replace("https://t.me/addstickers/", "") + to_blacklist = list({trigger.strip() for trigger in text.split("\n") if trigger.strip()}) + + added = 0 + for trigger in to_blacklist: + sql.add_to_stickers(chat_id, trigger.lower()) + added += 1 + + if added > 0: + await message.reply_text(f"Added {added} stickers to blacklist in **{html.escape(chat_name)}**!") + elif message.reply_to_message and message.reply_to_message.sticker: + trigger = message.reply_to_message.sticker.set_name + if not trigger: + await message.reply_text("Sticker is invalid!") + return + sql.add_to_stickers(chat_id, trigger.lower()) + await message.reply_text(f"Sticker `{trigger}` added to blacklist in **{html.escape(chat_name)}**!") + else: + await message.reply_text("Please provide a sticker set name or reply to a sticker.") + +@pbot.on_message(filters.command(["unblsticker", "rmblsticker"]) & filters.group) +@user_admin +async def unblackliststicker(client: Client, message: Message): + chat_id = await connected(client, message, message.from_user.id) + if not chat_id: + chat_id = message.chat.id + chat_name = message.chat.title + else: + chat = await client.get_chat(chat_id) + chat_name = chat.title + + args = message.command[1:] + if args: + text = " ".join(args).replace("https://t.me/addstickers/", "") + to_unblacklist = list({trigger.strip() for trigger in text.split("\n") if trigger.strip()}) + + successful = 0 + for trigger in to_unblacklist: + if sql.rm_from_stickers(chat_id, trigger.lower()): + successful += 1 + + if successful > 0: + await message.reply_text(f"Removed {successful} stickers from blacklist in **{html.escape(chat_name)}**!") + else: + await message.reply_text("None of these stickers were blacklisted.") + elif message.reply_to_message and message.reply_to_message.sticker: + trigger = message.reply_to_message.sticker.set_name + if not trigger: + await message.reply_text("Sticker is invalid!") + return + if sql.rm_from_stickers(chat_id, trigger.lower()): + await message.reply_text(f"Sticker `{trigger}` removed from blacklist in **{html.escape(chat_name)}**!") + else: + await message.reply_text("This sticker was not blacklisted.") + else: + await message.reply_text("Please provide a sticker set name or reply to a sticker.") + +@pbot.on_message(filters.command("blstickermode") & filters.group) +@user_admin +@loggable +async def blacklist_mode(client: Client, message: Message) -> str: + chat_id = await connected(client, message, message.from_user.id) + if not chat_id: + chat_id = message.chat.id + chat_name = message.chat.title + else: + chat = await client.get_chat(chat_id) + chat_name = chat.title + + args = message.command[1:] + if args: + mode = args[0].lower() + if mode in ("off", "nothing", "no"): + sql.set_blacklist_strength(chat_id, 0, "0") + msg = "Blacklist sticker mode disabled." + elif mode in ("del", "delete"): + sql.set_blacklist_strength(chat_id, 1, "0") + msg = "Blacklist sticker mode set to: DELETE." + elif mode == "warn": + sql.set_blacklist_strength(chat_id, 2, "0") + msg = "Blacklist sticker mode set to: WARN." + elif mode == "mute": + sql.set_blacklist_strength(chat_id, 3, "0") + msg = "Blacklist sticker mode set to: MUTE." + elif mode == "kick": + sql.set_blacklist_strength(chat_id, 4, "0") + msg = "Blacklist sticker mode set to: KICK." + elif mode == "ban": + sql.set_blacklist_strength(chat_id, 5, "0") + msg = "Blacklist sticker mode set to: BAN." + else: + await message.reply_text("Invalid mode! Use: off/del/warn/mute/kick/ban") + return "" + + await message.reply_text(f"{msg} in **{html.escape(chat_name)}**.") + return f"**{html.escape(chat_name)}:**\n#BLSTICKER_MODE\n**Admin:** {message.from_user.mention}\nMode: {mode}" + else: + mode, value = sql.get_blacklist_setting(chat_id) + modes = ["Off", "Delete", "Warn", "Mute", "Kick", "Ban"] + await message.reply_text(f"Current blacklist sticker mode: **{modes[mode]}**.") + return "" + +@pbot.on_message(filters.sticker & filters.group, group=11) +@user_not_admin +async def del_blackliststicker(client: Client, message: Message): + chat_id = message.chat.id + sticker = message.sticker + if not sticker or not sticker.set_name: + return + + mode, value = sql.get_blacklist_setting(chat_id) + if mode == 0: + return + + chat_filters = sql.get_chat_stickers(chat_id) + if sticker.set_name.lower() in [s.lower() for s in chat_filters]: + try: + await message.delete() + if mode == 2: # Warn + await warn(message.from_user, chat_id, f"Using blacklisted sticker: {sticker.set_name}", message) + elif mode == 3: # Mute + await message.chat.restrict_member(message.from_user.id, ChatPermissions(can_send_messages=False)) + elif mode == 4: # Kick + await message.chat.unban_member(message.from_user.id) + elif mode == 5: # Ban + await message.chat.ban_member(message.from_user.id) + except Exception as e: + LOGGER.warning(f"Error in del_blackliststicker: {e}") + +__mod_name__ = "Sᴛɪᴄᴋᴇʀ" +__help__ = """ +ʙʟᴀᴄᴋʟɪsᴛ sᴛɪᴄᴋᴇʀ ɪs ᴜsᴇᴅ ᴛᴏ sᴛᴏᴘ ᴄᴇʀᴛᴀɪɴ sᴛɪᴄᴋᴇʀs. ᴡʜᴇɴᴇᴠᴇʀ ᴀ sᴛɪᴄᴋᴇʀ ɪs sᴇɴᴛ, ᴛʜᴇ ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪᴍᴍᴇᴅɪᴀᴛᴇʟʏ. + + ❍ /blsticker: sᴇᴇ ᴄᴜʀʀᴇɴᴛ ʙʟᴀᴄᴋʟɪsᴛᴇᴅ sᴛɪᴄᴋᴇʀ + ❍ /addblsticker <sᴛɪᴄᴋᴇʀ ʟɪɴᴋ>: ᴀᴅᴅ sᴛɪᴄᴋᴇʀ ᴛᴏ ʙʟᴀᴄᴋʟɪsᴛ + ❍ /unblsticker <sᴛɪᴄᴋᴇʀ ʟɪɴᴋ>: ʀᴇᴍᴏᴠᴇ sᴛɪᴄᴋᴇʀ ғʀᴏᴍ ʙʟᴀᴄᴋʟɪsᴛ + ❍ /blstickermode <off/del/warn/mute/kick/ban>: sᴇᴛ ᴅᴇғᴀᴜʟᴛ ᴀᴄᴛɪᴏɴ +""" diff --git a/QueenNoxi/modules/blacklistusers.py b/QueenNoxi/modules/blacklistusers.py new file mode 100644 index 0000000000000000000000000000000000000000..553a255224b98b0ec7c9607a9fc439b7e0bf9a8f --- /dev/null +++ b/QueenNoxi/modules/blacklistusers.py @@ -0,0 +1,131 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +import QueenNoxi.modules.sql.blacklistusers_sql as sql +from QueenNoxi import DEMONS, DEV_USERS, DRAGONS, OWNER_ID, TIGERS, WOLVES, pbot +from QueenNoxi.modules.helper_funcs.chat_status import dev_plus +from QueenNoxi.modules.helper_funcs.extraction import extract_user, extract_user_and_text +from QueenNoxi.modules.log_channel import gloggable + +BLACKLISTWHITELIST = [OWNER_ID] + DEV_USERS + DRAGONS + WOLVES + DEMONS + +@pbot.on_message(filters.command("ignore")) +@dev_plus +@gloggable +async def bl_user(client: Client, message: Message) -> str: + user_id, reason = await extract_user_and_text(message, message.command[1:]) + + if not user_id: + await message.reply_text("I doubt that's a user.") + return "" + + if user_id == (await client.get_me()).id: + await message.reply_text("How am I supposed to do my work if I am ignoring myself?") + return "" + + if user_id in BLACKLISTWHITELIST: + await message.reply_text("No!\nNoticing Disasters is my job.") + return "" + + try: + target_user = await client.get_users(user_id) + except Exception: + await message.reply_text("I can't seem to find this user.") + return "" + + sql.blacklist_user(user_id, reason) + await message.reply_text("I shall ignore the existence of this user!") + + log_message = ( + f"#BLACKLIST\n" + f"**Admin:** {message.from_user.mention}\n" + f"**User:** {target_user.mention}" + ) + if reason: + log_message += f"\n**Reason:** {reason}" + + return log_message + +@pbot.on_message(filters.command("notice")) +@dev_plus +@gloggable +async def unbl_user(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + + if not user_id: + await message.reply_text("I doubt that's a user.") + return "" + + if user_id == (await client.get_me()).id: + await message.reply_text("I always notice myself.") + return "" + + try: + target_user = await client.get_users(user_id) + except Exception: + await message.reply_text("I can't seem to find this user.") + return "" + + if sql.is_user_blacklisted(user_id): + sql.unblacklist_user(user_id) + await message.reply_text("*notices user*") + log_message = ( + f"#UNBLACKLIST\n" + f"**Admin:** {message.from_user.mention}\n" + f"**User:** {target_user.mention}" + ) + return log_message + else: + await message.reply_text("I am not ignoring them at all though!") + return "" + +@pbot.on_message(filters.command("ignoredlist")) +@dev_plus +async def bl_users(client: Client, message: Message): + users = [] + for each_user in sql.get_all_blacklisted(): + try: + user = await client.get_users(each_user) + reason = sql.get_reason(each_user) + if reason: + users.append(f"• {user.mention} :- {reason}") + else: + users.append(f"• {user.mention}") + except: + users.append(f"• `{each_user}` (Not found)") + + message_text = "**Blacklisted Users**\n" + if not users: + message_text += "None is being ignored as of yet." + else: + message_text += "\n".join(users) + + await message.reply_text(message_text) + +def __user_info__(user_id): + is_blacklisted = sql.is_user_blacklisted(user_id) + text = "Blacklisted: **{}**" + if user_id in [777000, 1087968824]: + return "" + if user_id == pbot.me.id: + return "" + if int(user_id) in DRAGONS + TIGERS + WOLVES: + return "" + if is_blacklisted: + text = text.format("Yes") + reason = sql.get_reason(user_id) + if reason: + text += f"\nReason: `{reason}`" + else: + text = text.format("No") + return text + +__mod_name__ = "Ignore" +__help__ = """ +ᴛʜɪs ᴍᴏᴅᴜʟᴇ ᴀʟʟᴏᴡs ᴅᴇᴠs ᴛᴏ ɪɢɴᴏʀᴇ sᴘᴀᴍᴍᴇʀs ɢʟᴏʙᴀʟʟʏ. + + ❍ /ignore <ᴜsᴇʀ>: sᴛᴏᴘ ʀᴇsᴘᴏɴᴅɪɴɢ ᴛᴏ ᴛʜɪs ᴜsᴇʀ + ❍ /notice <ᴜsᴇʀ>: sᴛᴀʀᴛ ʀᴇsᴘᴏɴᴅɪɴɢ ᴛᴏ ᴛʜɪs ᴜsᴇʀ + ❍ /ignoredlist: ʟɪsᴛ ᴀʟʟ ɪɢɴᴏʀᴇᴅ ᴜsᴇʀs +""" diff --git a/QueenNoxi/modules/bug.py b/QueenNoxi/modules/bug.py new file mode 100644 index 0000000000000000000000000000000000000000..b7e30d2efce2b8323d60eff1e1bec7acbf1f3b9e --- /dev/null +++ b/QueenNoxi/modules/bug.py @@ -0,0 +1,119 @@ +from datetime import datetime + +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message + +from QueenNoxi import OWNER_ID as owner_id +from QueenNoxi import SUPPORT_CHAT as log,BOT_NAME,START_IMG +from QueenNoxi import pbot as Client +from QueenNoxi.utils.errors import capture_err + + +def content(msg: Message) -> [None, str]: + text_to_return = msg.text + + if msg.text is None: + return None + if " " in text_to_return: + try: + return msg.text.split(None, 1)[1] + except IndexError: + return None + else: + return None + + +@Client.on_message(filters.command("bug")) +@capture_err +async def bug(_, msg: Message): + if msg.chat.username: + chat_username = f"@{msg.chat.username}/`{msg.chat.id}`" + else: + chat_username = f"ᴩʀɪᴠᴀᴛᴇ ɢʀᴏᴜᴩ/`{msg.chat.id}`" + + bugs = content(msg) + user_id = msg.from_user.id + mention = ( + "[" + msg.from_user.first_name + "](tg://user?id=" + str(msg.from_user.id) + ")" + ) + datetimes_fmt = "%d-%m-%Y" + datetimes = datetime.utcnow().strftime(datetimes_fmt) + + + + bug_report = f""" +**#ʙᴜɢ : ** **tg://user?id={owner_id}** + +**ʀᴇᴩᴏʀᴛᴇᴅ ʙʏ : ** **{mention}** +**ᴜsᴇʀ ɪᴅ : ** **{user_id}** +**ᴄʜᴀᴛ : ** **{chat_username}** + +**ʙᴜɢ : ** **{bugs}** + +**ᴇᴠᴇɴᴛ sᴛᴀᴍᴩ : ** **{datetimes}**""" + + if msg.chat.type == "private": + await msg.reply_text("<b>» ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ɪs ᴏɴʟʏ ғᴏʀ ɢʀᴏᴜᴩs.</b>") + return + + if user_id == owner_id: + if bugs: + await msg.reply_text( + "<b>» ᴀʀᴇ ʏᴏᴜ ᴄᴏᴍᴇᴅʏ ᴍᴇ 🤣, ʏᴏᴜ'ʀᴇ ᴛʜᴇ ᴏᴡɴᴇʀ ᴏғ ᴛʜᴇ ʙᴏᴛ.</b>", + ) + return + else: + await msg.reply_text("ᴄʜᴜᴍᴛɪʏᴀ ᴏᴡɴᴇʀ!") + elif user_id != owner_id: + if bugs: + await msg.reply_text( + f"<b>ʙᴜɢ ʀᴇᴩᴏʀᴛ : {bugs}</b>\n\n" + "<b>» ʙᴜɢ sᴜᴄᴄᴇssғᴜʟʟʏ ʀᴇᴩᴏʀᴛᴇᴅ ᴀᴛ sᴜᴩᴩᴏʀᴛ ᴄʜᴀᴛ !</b>", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("• ᴄʟᴏsᴇ •", callback_data=f"close_reply")]] + ), + ) + await Client.send_photo( + log, + photo=START_IMG, + caption=f"{bug_report}", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton("• ᴠɪᴇᴡ ʙᴜɢ •", url=f"{msg.link}")], + [ + InlineKeyboardButton( + "• ᴄʟᴏsᴇ •", callback_data="close_send_photo" + ) + ], + ] + ), + ) + else: + await msg.reply_text( + f"<b>» ɴᴏ ʙᴜɢ ᴛᴏ ʀᴇᴩᴏʀᴛ !</b>", + ) + + +@Client.on_callback_query(filters.regex("close_reply")) +async def close_reply(msg, CallbackQuery): + await CallbackQuery.message.delete() + + +@Client.on_callback_query(filters.regex("close_send_photo")) +async def close_send_photo(_, CallbackQuery): + is_Admin = await Client.get_chat_member( + CallbackQuery.message.chat.id, CallbackQuery.from_user.id + ) + if not is_Admin.can_delete_messages: + return await CallbackQuery.answer( + "ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ʀɪɢʜᴛs ᴛᴏ ᴄʟᴏsᴇ ᴛʜɪs.", show_alert=True + ) + else: + await CallbackQuery.message.delete() + + +__help__ = """ +*ғᴏʀ ʀᴇᴩᴏʀᴛɪɴɢ ᴀ ʙᴜɢ * + ❍ /bug *:* ᴛᴏ ʀᴇᴩᴏʀᴛ ᴀ ʙᴜɢ ᴀᴛ sᴜᴩᴩᴏʀᴛ ᴄʜᴀᴛ. +""" +__mod_name__ = "Bᴜɢ" diff --git a/QueenNoxi/modules/carbon.py b/QueenNoxi/modules/carbon.py new file mode 100644 index 0000000000000000000000000000000000000000..d79c8db3063a143707ce488d1f455073878592c1 --- /dev/null +++ b/QueenNoxi/modules/carbon.py @@ -0,0 +1,40 @@ +from pyrogram import filters + +from QueenNoxi import pbot +from QueenNoxi.utils.errors import capture_err +from QueenNoxi.utils.functions import make_carbon + + +@pbot.on_message(filters.command("carbon")) +@capture_err +async def carbon_func(_, message): + if message.reply_to_message: + if message.reply_to_message.text: + txt = message.reply_to_message.text + else: + return await message.reply_text("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴏʀ ɢɪᴠᴇ sᴏᴍᴇ ᴛᴇxᴛ.") + else: + try: + txt = message.text.split(None, 1)[1] + except IndexError: + return await message.reply_text("ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴏʀ ɢɪᴠᴇ sᴏᴍᴇ ᴛᴇxᴛ.") + m = await message.reply_text("ɢᴇɴᴇʀᴀᴛɪɴɢ ᴄᴀʀʙᴏɴ...") + carbon = await make_carbon(txt) + await m.edit_text("ᴜᴩʟᴏᴀᴅɪɴɢ ɢᴇɴᴇʀᴀᴛᴇᴅ ᴄᴀʀʙᴏɴ...") + await pbot.send_photo( + message.chat.id, + photo=carbon, + caption=f"» ʀᴇᴏ̨ᴜᴇsᴛᴇᴅ ʙʏ : {message.from_user.mention}", + ) + await m.delete() + carbon.close() + +__mod_name__ = "Cᴀʀʙᴏɴ" + +__help__ = """ + +ᴍᴀᴋᴇs ᴀ ᴄᴀʀʙᴏɴ ᴏғ ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ ᴀɴᴅ sᴇɴᴅ ɪᴛ ᴛᴏ ʏᴏᴜ. + +❍ /carbon *:* ᴍᴀᴋᴇs ᴄᴀʀʙᴏɴ ɪғ ʀᴇᴩʟɪᴇᴅ ᴛᴏ ᴀ ᴛᴇxᴛ + + """ diff --git a/QueenNoxi/modules/chatbot.py b/QueenNoxi/modules/chatbot.py new file mode 100644 index 0000000000000000000000000000000000000000..a73f5f9dad7c1620ddf99566453d6d5f7e583d11 --- /dev/null +++ b/QueenNoxi/modules/chatbot.py @@ -0,0 +1,164 @@ +import html +import re +import aiohttp +import asyncio +from pyrogram import filters, Client, enums +from pyrogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) + +import QueenNoxi.modules.sql.chatbot_sql as sql +from QueenNoxi import BOT_ID, BOT_NAME, BOT_USERNAME, pbot +from QueenNoxi.modules.helper_funcs.chat_status import user_admin +from QueenNoxi.modules.log_channel import gloggable +from MukeshAPI import api + +import os +import subprocess + +async def get_response(text, user_id="anon", group_id=None): + try: + # Use subprocess to call the Node.js bridge + brain_path = os.path.join(os.getcwd(), "QueenNoxi", "brain", "query.js") + if not os.path.exists(brain_path): + brain_path = os.path.join(os.getcwd(), "brain", "query.js") + + cmd = ["node", brain_path, "query", text, str(user_id)] + if group_id: + cmd.append(str(group_id)) + + process = await asyncio.create_subprocess_exec( + *cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + stdout, stderr = await process.communicate() + + if process.returncode == 0: + return stdout.decode().strip() + else: + err = stderr.decode().strip() + return f"Error from Brain: {err}" + except Exception as e: + return f"Error calling Brain: {e}" + +async def learn_brain(q, a): + try: + brain_path = os.path.join(os.getcwd(), "QueenNoxi", "brain", "query.js") + if not os.path.exists(brain_path): + brain_path = os.path.join(os.getcwd(), "brain", "query.js") + + process = await asyncio.create_subprocess_exec( + "node", brain_path, "learn", q, a, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + stdout, stderr = await process.communicate() + return process.returncode == 0 + except Exception: + return False + + + +@pbot.on_callback_query(filters.regex(r"(add|rm)_chat\(")) +@gloggable +async def chatbot_callback(client: Client, query: CallbackQuery): + user = query.from_user + chat = query.message.chat + + member = await client.get_chat_member(chat.id, user.id) + if member.status not in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER): + await query.answer("You must be an admin to do this!", show_alert=True) + return + + action = query.data.split("_")[0] + if action == "add": + sql.rem_queennoxi(chat.id) + await query.message.edit_text(f"{BOT_NAME} chatbot enabled by {user.mention}.") + return f"<b>{html.escape(chat.title)}:</b>\nᴀɪ ᴇɴᴀʙʟᴇ\n<b>ᴀᴅᴍɪɴ :</b> {user.mention}\n" + else: + sql.set_queennoxi(chat.id) + await query.message.edit_text(f"{BOT_NAME} chatbot disabled by {user.mention}.") + return f"<b>{html.escape(chat.title)}:</b>\nᴀɪ ᴅɪsᴀʙʟᴇᴅ\n<b>ᴀᴅᴍɪɴ :</b> {user.mention}\n" + +@pbot.on_message(filters.command("chatbot") & filters.group) +@user_admin +async def chatbot_toggle(client: Client, message: Message): + msg = "• ᴄʜᴏᴏsᴇ ᴀɴ ᴏᴩᴛɪᴏɴ ᴛᴏ ᴇɴᴀʙʟᴇ/ᴅɪsᴀʙʟᴇ ᴄʜᴀᴛʙᴏᴛ" + keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="ᴇɴᴀʙʟᴇ", callback_data=f"add_chat({message.from_user.id})"), + InlineKeyboardButton(text="ᴅɪsᴀʙʟᴇ", callback_data=f"rm_chat({message.from_user.id})"), + ], + ] + ) + await message.reply_text(msg, reply_markup=keyboard) + +@pbot.on_message( + filters.text + & ~filters.command(["chatbot"]) + & ~filters.regex(r"^/") + & ~filters.regex(r"^#[^\s]+") + & ~filters.regex(r"^!") + & filters.group +) +async def chatbot_msg(client: Client, message: Message): + chat_id = message.chat.id + if sql.is_queennoxi(chat_id): + return + + should_reply = False + if message.text.lower() == "queennoxi": + should_reply = True + elif f"@{BOT_USERNAME}" in message.text: + should_reply = True + elif message.reply_to_message and message.reply_to_message.from_user and message.reply_to_message.from_user.id == BOT_ID: + should_reply = True + + if should_reply: + await client.send_chat_action(chat_id, enums.ChatAction.TYPING) + response = await get_response(message.text, message.from_user.id, chat_id) + if response and response.strip(): + await message.reply_text(response) + + +@pbot.on_message(filters.command("learn") & filters.reply & filters.group) +@user_admin +async def learn_handler(client: Client, message: Message): + message_b = message.reply_to_message + + # but more reliably we might need to fetch it or check message_b.reply_to_message_id + + if not message_b.reply_to_message: + # If not cached, we can try to get it + try: + reply_to = await client.get_messages(message.chat.id, message_b.reply_to_message_id) + message_a = reply_to + except Exception: + await message.reply_text("I couldn't find the original message this was replying to.") + return + else: + message_a = message_b.reply_to_message + + q = message_a.text or message_a.caption + a = message_b.text or message_b.caption + + if not q or not a: + await message.reply_text("Both messages must contain text to learn.") + return + + success = await learn_brain(q, a) + if success: + await message.reply_text(f"✅ Learned successfully!\n**Q:** `{q}`\n**A:** `{a}`") + else: + await message.reply_text("❌ Failed to learn pattern.") + + +__mod_name__ = "Chatbot" +__help__ = """ + • `/chatbot`: Toggle chatbot in the group. +""" diff --git a/QueenNoxi/modules/chatgpt.py b/QueenNoxi/modules/chatgpt.py new file mode 100644 index 0000000000000000000000000000000000000000..b9eb85476f6180a94253da1b1e26b56eddf41f28 --- /dev/null +++ b/QueenNoxi/modules/chatgpt.py @@ -0,0 +1,28 @@ +import requests +from .. import pbot as QueenNoxi,BOT_NAME,BOT_USERNAME +import time +from pyrogram.enums import ChatAction, ParseMode +from pyrogram import filters +from MukeshAPI import api +@QueenNoxi.on_message(filters.command(["chatgpt","ai","ask"], prefixes=["+", ".", "/", "-", "?", "$","#","&"])) +async def chat_gpt(bot, message): + + try: + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + await message.reply_text( + "Example:**\n\n`/chatgpt Where is TajMahal?`") + else: + a = message.text.split(' ', 1)[1] + r=api.gemini(a)["results"] + await message.reply_text(f" {r} \n\n🎉ᴘᴏᴡᴇʀᴇᴅ ʙʏ @{BOT_USERNAME} ", parse_mode=ParseMode.MARKDOWN) + except Exception as e: + await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") + +__mod_name__ = "Cʜᴀᴛɢᴘᴛ" +__help__ = """ + Cʜᴀᴛɢᴘᴛ ᴄᴀɴ ᴀɴsᴡᴇʀ ʏᴏᴜʀ ǫᴜᴇsᴛɪᴏɴ ᴀɴᴅ sʜᴏᴡs ʏᴏᴜ ᴛʜᴇ ʀᴇsᴜʟᴛ + + ❍ /chatgpt *:* ʀᴇᴘʟʏ ᴛo ᴍᴇssᴀɢᴇ ᴏʀ ɢɪᴠᴇ sᴏᴍᴇ ᴛᴇxᴛ + + """ diff --git a/QueenNoxi/modules/cleaner.py b/QueenNoxi/modules/cleaner.py new file mode 100644 index 0000000000000000000000000000000000000000..3468f814435398e7f69eb25663a269975de62e61 --- /dev/null +++ b/QueenNoxi/modules/cleaner.py @@ -0,0 +1,260 @@ +import html + +from telegram import ParseMode, Update +from telegram.ext import CallbackContext, CommandHandler, Filters, MessageHandler + +from QueenNoxi import ALLOW_EXCL, CustomCommandHandler, dispatcher +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import ( + bot_can_delete, + connection_status, + dev_plus, + user_admin, +) +from QueenNoxi.modules.sql import cleaner_sql as sql + +CMD_STARTERS = ("/", "!") if ALLOW_EXCL else "/" +BLUE_TEXT_CLEAN_GROUP = 13 +CommandHandlerList = (CommandHandler, CustomCommandHandler, DisableAbleCommandHandler) +command_list = [ + "cleanblue", + "ignoreblue", + "unignoreblue", + "listblue", + "ungignoreblue", + "gignoreblue" "start", + "help", + "settings", + "donate", + "stalk", + "aka", + "leaderboard", +] + +for handler_list in dispatcher.handlers: + for handler in dispatcher.handlers[handler_list]: + if any(isinstance(handler, cmd_handler) for cmd_handler in CommandHandlerList): + command_list += handler.command + + +def clean_blue_text_must_click(update: Update, context: CallbackContext): + bot = context.bot + chat = update.effective_chat + message = update.effective_message + if chat.get_member(bot.id).can_delete_messages and sql.is_enabled(chat.id): + fst_word = message.text.strip().split(None, 1)[0] + + if len(fst_word) > 1 and any( + fst_word.startswith(start) for start in CMD_STARTERS + ): + + command = fst_word[1:].split("@") + chat = update.effective_chat + + ignored = sql.is_command_ignored(chat.id, command[0]) + if ignored: + return + + if command[0] not in command_list: + message.delete() + + +@connection_status +@bot_can_delete +@user_admin +def set_blue_text_must_click(update: Update, context: CallbackContext): + chat = update.effective_chat + message = update.effective_message + bot, args = context.bot, context.args + if len(args) >= 1: + val = args[0].lower() + if val in ("off", "no"): + sql.set_cleanbt(chat.id, False) + reply = "Bluetext cleaning has been disabled for <b>{}</b>".format( + html.escape(chat.title) + ) + message.reply_text(reply, parse_mode=ParseMode.HTML) + + elif val in ("yes", "on"): + sql.set_cleanbt(chat.id, True) + reply = "Bluetext cleaning has been enabled for <b>{}</b>".format( + html.escape(chat.title) + ) + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "Invalid argument.Accepted values are 'yes', 'on', 'no', 'off'" + message.reply_text(reply) + else: + clean_status = sql.is_enabled(chat.id) + clean_status = "Enabled" if clean_status else "Disabled" + reply = "Bluetext cleaning for <b>{}</b> : <b>{}</b>".format( + html.escape(chat.title), clean_status + ) + message.reply_text(reply, parse_mode=ParseMode.HTML) + + +@user_admin +def add_bluetext_ignore(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) >= 1: + val = args[0].lower() + added = sql.chat_ignore_command(chat.id, val) + if added: + reply = "<b>{}</b> has been added to bluetext cleaner ignore list.".format( + args[0] + ) + else: + reply = "Command is already ignored." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "No command supplied to be ignored." + message.reply_text(reply) + + +@user_admin +def remove_bluetext_ignore(update: Update, context: CallbackContext): + message = update.effective_message + chat = update.effective_chat + args = context.args + if len(args) >= 1: + val = args[0].lower() + removed = sql.chat_unignore_command(chat.id, val) + if removed: + reply = ( + "<b>{}</b> has been removed from bluetext cleaner ignore list.".format( + args[0] + ) + ) + else: + reply = "Command isn't ignored currently." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "No command supplied to be unignored." + message.reply_text(reply) + + +@user_admin +def add_bluetext_ignore_global(update: Update, context: CallbackContext): + message = update.effective_message + args = context.args + if len(args) >= 1: + val = args[0].lower() + added = sql.global_ignore_command(val) + if added: + reply = "<b>{}</b> has been added to global bluetext cleaner ignore list.".format( + args[0] + ) + else: + reply = "Command is already ignored." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "No command supplied to be ignored." + message.reply_text(reply) + + +@dev_plus +def remove_bluetext_ignore_global(update: Update, context: CallbackContext): + message = update.effective_message + args = context.args + if len(args) >= 1: + val = args[0].lower() + removed = sql.global_unignore_command(val) + if removed: + reply = "<b>{}</b> has been removed from global bluetext cleaner ignore list.".format( + args[0] + ) + else: + reply = "Command isn't ignored currently." + message.reply_text(reply, parse_mode=ParseMode.HTML) + + else: + reply = "No command supplied to be unignored." + message.reply_text(reply) + + +@dev_plus +def bluetext_ignore_list(update: Update, context: CallbackContext): + + message = update.effective_message + chat = update.effective_chat + + global_ignored_list, local_ignore_list = sql.get_all_ignored(chat.id) + text = "" + + if global_ignored_list: + text = "The following commands are currently ignored globally from bluetext cleaning :\n" + + for x in global_ignored_list: + text += f" - <code>{x}</code>\n" + + if local_ignore_list: + text += "\nThe following commands are currently ignored locally from bluetext cleaning :\n" + + for x in local_ignore_list: + text += f" - <code>{x}</code>\n" + + if text == "": + text = "No commands are currently ignored from bluetext cleaning." + message.reply_text(text) + return + + message.reply_text(text, parse_mode=ParseMode.HTML) + return + + +__help__ = """ +*ʙʟᴜᴇ ᴛᴇxᴛ ᴄʟᴇᴀɴᴇʀ* ʀᴇᴍᴏᴠᴇᴅ ᴀɴʏ ᴍᴀᴅᴇ ᴜᴘ ᴄᴏᴍᴍᴀɴᴅs ᴛʜᴀᴛ ᴘᴇᴏᴘʟᴇ sᴇɴᴅ ɪɴ ʏᴏᴜʀ ᴄʜᴀᴛ. + ❍ /cleanblue <ᴏɴ/ᴏғғ/ʏᴇs/ɴᴏ>*:* ᴄʟᴇᴀɴ ᴄᴏᴍᴍᴀɴᴅs ᴀғᴛᴇʀ sᴇɴᴅɪɴɢ + ❍ /ignoreblue <ᴡᴏʀᴅ>*:* ᴘʀᴇᴠᴇɴᴛ ᴀᴜᴛᴏ ᴄʟᴇᴀɴɪɴɢ ᴏғ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ + ❍ /unignoreblue <ᴡᴏʀᴅ>*:* ʀᴇᴍᴏᴠᴇ ᴘʀᴇᴠᴇɴᴛ ᴀᴜᴛᴏ ᴄʟᴇᴀɴɪɴɢ ᴏғ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅ + ❍ /listblue *:* ʟɪsᴛ ᴄᴜʀʀᴇɴᴛʟʏ ᴡʜɪᴛᴇʟɪsᴛᴇᴅ ᴄᴏᴍᴍᴀɴᴅs +""" + +SET_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "cleanblue", set_blue_text_must_click, run_async=True +) +ADD_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "ignoreblue", add_bluetext_ignore, run_async=True +) +REMOVE_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "unignoreblue", remove_bluetext_ignore, run_async=True +) +ADD_CLEAN_BLUE_TEXT_GLOBAL_HANDLER = CommandHandler( + "gignoreblue", add_bluetext_ignore_global, run_async=True +) +REMOVE_CLEAN_BLUE_TEXT_GLOBAL_HANDLER = CommandHandler( + "ungignoreblue", remove_bluetext_ignore_global, run_async=True +) +LIST_CLEAN_BLUE_TEXT_HANDLER = CommandHandler( + "listblue", bluetext_ignore_list, run_async=True +) +CLEAN_BLUE_TEXT_HANDLER = MessageHandler( + Filters.command & Filters.chat_type.groups, + clean_blue_text_must_click, + run_async=True, +) + +dispatcher.add_handler(SET_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(ADD_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(REMOVE_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(ADD_CLEAN_BLUE_TEXT_GLOBAL_HANDLER) +dispatcher.add_handler(REMOVE_CLEAN_BLUE_TEXT_GLOBAL_HANDLER) +dispatcher.add_handler(LIST_CLEAN_BLUE_TEXT_HANDLER) +dispatcher.add_handler(CLEAN_BLUE_TEXT_HANDLER, BLUE_TEXT_CLEAN_GROUP) + +__mod_name__ = "Cʟᴇᴀɴ" +__handlers__ = [ + SET_CLEAN_BLUE_TEXT_HANDLER, + ADD_CLEAN_BLUE_TEXT_HANDLER, + REMOVE_CLEAN_BLUE_TEXT_HANDLER, + ADD_CLEAN_BLUE_TEXT_GLOBAL_HANDLER, + REMOVE_CLEAN_BLUE_TEXT_GLOBAL_HANDLER, + LIST_CLEAN_BLUE_TEXT_HANDLER, + (CLEAN_BLUE_TEXT_HANDLER, BLUE_TEXT_CLEAN_GROUP), +] diff --git a/QueenNoxi/modules/connection.py b/QueenNoxi/modules/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..81ca33e4816263eee4718e783cd4a55cbd087de9 --- /dev/null +++ b/QueenNoxi/modules/connection.py @@ -0,0 +1,158 @@ +import re +from pyrogram import filters, Client, enums +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message, CallbackQuery +from pyrogram.errors import BadRequest, Unauthorized + +import QueenNoxi.modules.sql.connection_sql as sql +from QueenNoxi import DEV_USERS, DRAGONS, pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import user_admin + +@pbot.on_message(filters.command("allowconnect") & filters.group) +@user_admin +async def allow_connections(client: Client, message: Message): + chat = message.chat + args = message.command[1:] + + if not args: + status = sql.allow_connect_to_chat(chat.id) + await message.reply_text(f"Connection status: {'Allowed' if status else 'Disabled'}") + return + + if args[0].lower() in ("yes", "on", "true"): + sql.set_allow_connect_to_chat(chat.id, True) + await message.reply_text("Connection allowed!") + elif args[0].lower() in ("no", "off", "false"): + sql.set_allow_connect_to_chat(chat.id, False) + await message.reply_text("Connection disabled.") + else: + await message.reply_text("Please use 'on' or 'off'.") + +@pbot.on_message(filters.command("connect") & filters.private) +async def connect_chat(client: Client, message: Message): + user = message.from_user + args = message.command[1:] + + if not args: + await message.reply_text("Please provide a chat ID to connect to.") + return + + chat_id = args[0] + try: + chat_id = int(chat_id) + except ValueError: + pass # could be username + + try: + chat = await client.get_chat(chat_id) + except Exception as e: + await message.reply_text(f"Invalid chat! Error: {e}") + return + + try: + member = await chat.get_member(user.id) + except Exception: + await message.reply_text("You are not a member of that chat!") + return + + is_admin = member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER) + is_allow = sql.allow_connect_to_chat(chat.id) + + if is_admin or (is_allow and user.id not in DRAGONS): # Simplify logic + if sql.connect(user.id, chat.id): + await message.reply_text(f"Successfully connected to **{chat.title}**.") + sql.add_history_conn(user.id, str(chat.id), chat.title) + else: + await message.reply_text("Connection failed!") + else: + await message.reply_text("Connection to this chat is not allowed or you are not an admin!") + +@pbot.on_message(filters.command("disconnect") & filters.private) +async def disconnect_chat(client: Client, message: Message): + if sql.disconnect(message.from_user.id): + await message.reply_text("Disconnected from chat!") + else: + await message.reply_text("You're not connected!") + +@pbot.on_message(filters.command("connection") & filters.private) +async def connection_chat(client: Client, message: Message): + conn = sql.get_connected_chat(message.from_user.id) + if conn: + try: + chat = await client.get_chat(conn.chat_id) + await message.reply_text(f"You are currently connected to **{chat.title}** (`{chat.id}`).") + except: + await message.reply_text("You are connected to an inaccessible chat.") + else: + await message.reply_text("You are not connected to any chat.") + +CONN_HELP = """ +*ᴀᴄᴛɪᴏɴs ᴀʀᴇ ᴀᴠᴀɪʟᴀʙʟᴇ ᴡɪᴛʜ ᴄᴏɴɴᴇᴄᴛᴇᴅ ɢʀᴏᴜᴘs:* + + • ᴠɪᴇᴡ ᴀɴᴅ ᴇᴅɪᴛ ɴᴏᴛᴇs. + • ᴠɪᴇᴡ ᴀɴᴅ ᴇᴅɪᴛ ғɪʟᴛᴇʀs. + • ɢᴇᴛ ɪɴᴠɪᴛᴇ ʟɪɴᴋ ᴏғ ᴄʜᴀᴛ. + • sᴇᴛ ᴀɴᴅ ᴄᴏɴᴛʀᴏʟ ᴀɴᴛɪғʟᴏᴏᴅ sᴇᴛᴛɪɴɢs. + • sᴇᴛ ᴀɴᴅ ᴄᴏɴᴛʀᴏʟ ʙʟᴀᴄᴋʟɪsᴛ sᴇᴛᴛɪɴɢs. + • sᴇᴛ ʟᴏᴄᴋs ᴀɴᴅ ᴜɴʟᴏᴄᴋs ɪɴ ᴄʜᴀᴛ. + • ᴇɴᴀʙʟᴇ ᴀɴᴅ ᴅɪsᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs ɪɴ ᴄʜᴀᴛ. + • ᴇxᴘᴏʀᴛ ᴀɴᴅ ɪᴍᴘᴏʀᴛ sᴇᴛᴛɪɴɢs. +""" + +@pbot.on_message(filters.command("helpconnect") & filters.private) +async def help_connect_chat(client: Client, message: Message): + await message.reply_text(CONN_HELP) + +@pbot.on_callback_query(filters.regex(r"^connect_")) +async def connect_button(client: Client, query: CallbackQuery): + data = query.data + user_id = query.from_user.id + + if data == "connect_disconnect": + if sql.disconnect(user_id): + await query.message.edit_text("Disconnected from chat!") + else: + await query.answer("You're not connected!", show_alert=True) + + elif data == "connect_clear": + sql.clear_history_conn(user_id) + await query.message.edit_text("Connection history cleared.") + + elif data == "connect_close": + await query.message.delete() + +async def connected(client: Client, message: Message, user_id: int, need_admin: bool = True): + if message.chat.type != enums.ChatType.PRIVATE: + return message.chat.id + + conn = sql.get_connected_chat(user_id) + if not conn: + return False + + chat_id = conn.chat_id + try: + member = await client.get_chat_member(chat_id, user_id) + except Exception: + sql.disconnect(user_id) + return False + + is_admin = member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER) + if need_admin and not is_admin and user_id not in DRAGONS: + await message.reply_text("You must be an admin in the connected group!") + return False + + return chat_id + +__mod_name__ = "Cᴏɴɴᴇᴄᴛ" +__help__ = """ +sᴏᴍᴇᴛɪᴍᴇs, ʏᴏᴜ ᴊᴜsᴛ ᴡᴀɴᴛ ᴛᴏ ᴀᴅᴅ sᴏᴍᴇ ɴᴏᴛᴇs ᴀɴᴅ ғɪʟᴛᴇʀs ᴛᴏ ᴀ ɢʀᴏᴜᴘ ᴄʜᴀᴛ, ʙᴜᴛ ʏᴏᴜ ᴅᴏɴ'ᴛ ᴡᴀɴᴛ ᴇᴠᴇʀʏᴏɴᴇ ᴛᴏ sᴇᴇ; ᴛʜɪs ɪs ᴡʜᴇʀᴇ ᴄᴏɴɴᴇᴄᴛɪᴏɴs ᴄᴏᴍᴇ ɪɴ... +ᴛʜɪs ᴀʟʟᴏᴡs ʏᴏᴜ ᴛᴏ ᴄᴏɴɴᴇᴄᴛ ᴛᴏ ᴀ ᴄʜᴀᴛ's ᴅᴀᴛᴀʙᴀsᴇ, ᴀɴᴅ ᴀᴅᴅ ᴛʜɪɴɢs ᴛᴏ ɪᴛ ᴡɪᴛʜᴏᴜᴛ ᴛʜᴇ ᴄᴏᴍᴍᴀɴᴅs ᴀᴘᴘᴇᴀʀɪɴɢ ɪɴ ᴄʜᴀᴛ! + + ❍ /connect <ᴄʜᴀᴛ ɪᴅ>: ᴄᴏɴɴᴇᴄᴛs ᴛᴏ ᴄʜᴀᴛ (PW only) + ❍ /connection: ᴄᴏɴɴᴇᴄᴛᴇᴅ ᴄʜᴀᴛ ɪɴғᴏ + ❍ /disconnect: ᴅɪsᴄᴏɴɴᴇᴄᴛ ғʀᴏᴍ ᴀ ᴄʜᴀᴛ + ❍ /helpconnect: ʟɪsᴛ ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs ᴛʜᴀᴛ ᴄᴀɴ ʙᴇ ᴜsᴇᴅ ʀᴇᴍᴏᴛᴇʟʏ + +*ᴀᴅᴍɪɴ ᴏɴʟʏ:* + ❍ /allowconnect <ʏᴇs/ɴᴏ>: ᴀʟʟᴏᴡ ᴀ ᴜsᴇʀ ᴛᴏ ᴄᴏɴɴᴇᴄᴛ ᴛᴏ ᴀ ᴄʜᴀᴛ +""" diff --git a/QueenNoxi/modules/country.py b/QueenNoxi/modules/country.py new file mode 100644 index 0000000000000000000000000000000000000000..c5ec64e18beddf6ce8b1d1b166cef83c0c44d7c9 --- /dev/null +++ b/QueenNoxi/modules/country.py @@ -0,0 +1,117 @@ +import flag +from countryinfo import CountryInfo + +from pyrogram import filters +from QueenNoxi import pbot as app, BOT_USERNAME + +@app.on_message(filters.command("country")) +async def msg(_, message): + if getattr(message, "forward_from", None): + return + if len(message.command) < 2: + return + input_str = message.text.split(None, 1)[1] + lol = input_str + country = CountryInfo(lol) + try: + a = country.info() + except Exception: + await message.reply("Country Not Available Currently") + return + name = a.get("name") + bb = a.get("altSpellings") + hu = "" + for p in bb: + hu += p + ", " + + area = a.get("area") + borders = "" + hell = a.get("borders") + for fk in hell: + borders += fk + ", " + + call = "" + WhAt = a.get("callingCodes") + for what in WhAt: + call += what + " " + + capital = a.get("capital") + currencies = "" + fker = a.get("currencies") + for FKer in fker: + currencies += FKer + ", " + + HmM = a.get("demonym") + geo = a.get("geoJSON") + pablo = geo.get("features") + Pablo = pablo[0] + PAblo = Pablo.get("geometry") + EsCoBaR = PAblo.get("type") + iso = "" + iSo = a.get("ISO") + for hitler in iSo: + po = iSo.get(hitler) + iso += po + ", " + fla = iSo.get("alpha2") + nox = fla.upper() + okie = flag.flag(nox) + + languages = a.get("languages") + lMAO = "" + for lmao in languages: + lMAO += lmao + ", " + + nonive = a.get("nativeName") + waste = a.get("population") + reg = a.get("region") + sub = a.get("subregion") + tik = a.get("timezones") + tom = "" + for jerry in tik: + tom += jerry + ", " + + GOT = a.get("tld") + lanester = "" + for targaryen in GOT: + lanester += targaryen + ", " + + wiki = a.get("wiki") + + caption = f"""<b><u>ɪɴғᴏʀᴍᴀᴛɪᴏɴ ɢᴀᴛʜᴇʀᴇᴅ sᴜᴄᴇssғᴜʟʟʏ </b></u> + +<b>ᴄᴏᴜɴᴛʀʏ ɴᴀᴍᴇ :</b> {name} +<b>ᴀʟᴛᴇʀɴᴀᴛɪᴠᴇ sᴘᴇʟʟɪɴɢs :</b> {hu} +<b>ᴄᴏᴜɴᴛʀʏ ᴀʀᴇᴀ :</b> {area} square kilometers +<b>ʙᴏʀᴅᴇʀs :</b> {borders} +<b>ᴄᴀʟʟɪɴɢ ᴄᴏᴅᴇs :</b> {call} +<b>ᴄᴏᴜɴᴛʀʏ's ᴄᴀᴘɪᴛᴀʟ :</b> {capital} +<b>ᴄᴏᴜɴᴛʀʏ's ᴄᴜʀʀᴇɴᴄʏ :</b> {currencies} +<b>ᴄᴏᴜɴᴛʀʏ's ғʟᴀɢ :</b> {okie} +<b>ᴅᴇᴍᴏʏᴍ:</b> {HmM} +<b>ᴄᴏᴜɴᴛʀʏ ᴛʏᴘᴇ :</b> {EsCoBaR} +<b>ɪsᴏ ɴᴀᴍᴇs :</b> {iso} +<b>ʟᴀɴɢᴜᴀɢᴇs :</b> {lMAO} +<b>ɴᴀᴛɪᴠᴇ ɴᴀᴍᴇs :</b> {nonive} +<b>ᴘᴏᴘᴜʟᴀᴛɪᴏɴs :</b> {waste} +<b>ʀᴇɢɪᴏɴ :</b> {reg} +<b>sᴜʙ ʀᴇɢɪᴏɴ :</b> {sub} +<b>ᴛɪᴍᴇ ᴢᴏɴᴇs :</b> {tom} +<b>ᴛᴏᴛᴀʟ ʟᴇᴠᴇʟ ᴅᴏᴍᴀɪɴ :</b> {lanester} +<b>ᴡɪᴋɪᴘᴇᴅɪᴀ:</b> {wiki} + +<u>ɪɴғᴏʀᴍᴀᴛɪᴏɴ ɢᴀᴛʜᴇʀᴇᴅ ʙʏ @{BOT_USERNAME}</u> +""" + + await message.reply( + caption, + disable_web_page_preview=True, + ) + + +__help__ = """ +ɪ ᴡɪʟʟ ɢɪᴠᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀ ᴄᴏᴜɴᴛʀʏ + + ❍ /country <ᴄᴏᴜɴᴛʀʏ ɴᴀᴍᴇ>*:* ɢᴀᴛʜᴇʀɪɴɢ ɪɴғᴏ ᴀʙᴏᴜᴛ ɢɪᴠᴇɴ ᴄᴏᴜɴᴛʀʏ +""" + +__mod_name__ = "Cᴏᴜɴᴛʀʏ" diff --git a/QueenNoxi/modules/couples.py b/QueenNoxi/modules/couples.py new file mode 100644 index 0000000000000000000000000000000000000000..c83677c287f48b34d435579360d73ec7db64f7dc --- /dev/null +++ b/QueenNoxi/modules/couples.py @@ -0,0 +1,84 @@ +import random +from datetime import datetime + +from pyrogram import filters +from pyrogram.enums import ChatType + +from QueenNoxi import pbot +from QueenNoxi.utils.mongo import get_couple, save_couple + + +# Date and time +def dt(): + now = datetime.now() + dt_string = now.strftime("%d/%m/%Y %H:%M") + dt_list = dt_string.split(" ") + return dt_list + + +def dt_tom(): + a = ( + str(int(dt()[0].split("/")[0]) + 1) + + "/" + + dt()[0].split("/")[1] + + "/" + + dt()[0].split("/")[2] + ) + return a + + +today = str(dt()[0]) +tomorrow = str(dt_tom()) + + +@pbot.on_message(filters.command(["couple", "couples"])) +async def couple(_, message): + if message.chat.type == ChatType.PRIVATE: + return await message.reply_text("ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴏɴʟʏ ᴡᴏʀᴋs ɪɴ ɢʀᴏᴜᴘ.") + try: + chat_id = message.chat.id + is_selected = await get_couple(chat_id, today) + if not is_selected: + list_of_users = [] + async for i in pbot.get_chat_members(message.chat.id, limit=50): + if not i.user.is_bot: + list_of_users.append(i.user.id) + if len(list_of_users) < 2: + return await message.reply_text("ɴᴏᴛ ᴇɴᴏᴜɢʜ ᴜsᴇʀ") + c1_id = random.choice(list_of_users) + c2_id = random.choice(list_of_users) + while c1_id == c2_id: + c1_id = random.choice(list_of_users) + c1_mention = (await pbot.get_users(c1_id)).mention + c2_mention = (await pbot.get_users(c2_id)).mention + + couple_selection_message = f"""**ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ :** + +{c1_mention} + {c2_mention} = 💗 +ɴᴇᴡ ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ ᴄᴀɴ ʙᴇ ᴄʜᴏsᴇɴ ᴀᴛ 12 ᴀᴍ {tomorrow}""" + await pbot.send_message(message.chat.id, text=couple_selection_message) + couple = {"c1_id": c1_id, "c2_id": c2_id} + await save_couple(chat_id, today, couple) + + elif is_selected: + c1_id = int(is_selected["c1_id"]) + c2_id = int(is_selected["c2_id"]) + c1_name = (await pbot.get_users(c1_id)).mention + c2_name = (await pbot.get_users(c2_id)).mention + couple_selection_message = f"""ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ : + +{c1_name} + {c2_name} = 💗 +ɴᴇᴡ ᴄᴏᴜᴘʟᴇ ᴏғ ᴛʜᴇ ᴅᴀʏ ᴄᴀɴ ʙᴇ ᴄʜᴏsᴇɴ ᴀᴛ 12 ᴀᴍ {tomorrow}""" + await pbot.send_message(message.chat.id, text=couple_selection_message) + except Exception as e: + print(e) + await message.reply_text(e) + + +__help__ = """ +ᴄʜᴏᴏsᴇ ᴄᴏᴜᴘʟᴇs ɪɴ ʏᴏᴜʀ ᴄʜᴀᴛ + + ❍ /couples *:* ᴄʜᴏᴏsᴇ 2 ᴜsᴇʀs ᴀɴᴅ sᴇɴᴅ ᴛʜᴇɪʀ ɴᴀᴍᴇ ᴀs ᴄᴏᴜᴘʟᴇs ɪɴ ʏᴏᴜʀ ᴄʜᴀᴛ. +""" + +__mod_name__ = "Cᴏᴜᴘʟᴇ" diff --git a/QueenNoxi/modules/currency_converter.py b/QueenNoxi/modules/currency_converter.py new file mode 100644 index 0000000000000000000000000000000000000000..a773874f2ec2f6229ac083e7e3ca6eca78f48d12 --- /dev/null +++ b/QueenNoxi/modules/currency_converter.py @@ -0,0 +1,66 @@ +import requests +from telegram import ParseMode, Update +from telegram.ext import CallbackContext, CommandHandler + +from QueenNoxi import CASH_API_KEY, dispatcher + + +def convert(update: Update, context: CallbackContext): + args = update.effective_message.text.split(" ") + + if len(args) == 4: + try: + orig_cur_amount = float(args[1]) + + except ValueError: + update.effective_message.reply_text("Invalid Amount Of Currency") + return + + orig_cur = args[2].upper() + + new_cur = args[3].upper() + + request_url = ( + f"https://www.alphavantage.co/query" + f"?function=CURRENCY_EXCHANGE_RATE" + f"&from_currency={orig_cur}" + f"&to_currency={new_cur}" + f"&apikey={CASH_API_KEY}" + ) + response = requests.get(request_url).json() + try: + current_rate = float( + response["Realtime Currency Exchange Rate"]["5. Exchange Rate"] + ) + except KeyError: + update.effective_message.reply_text("Currency Not Supported.") + return + new_cur_amount = round(orig_cur_amount * current_rate, 5) + update.effective_message.reply_text( + f"{orig_cur_amount} {orig_cur} = {new_cur_amount} {new_cur}" + ) + + elif len(args) == 1: + update.effective_message.reply_text(__help__, parse_mode=ParseMode.MARKDOWN) + + else: + update.effective_message.reply_text( + f"*Invalid Args!!:* Required 3 But Passed {len(args) -1}", + parse_mode=ParseMode.MARKDOWN, + ) + + +__help__ = """ +Converts money from one exchange to another + +Usage: /cash amount from to +Example: /cash 20 USD INR +""" + +CONVERTER_HANDLER = CommandHandler("cash", convert, run_async=True) +dispatcher.add_handler(CONVERTER_HANDLER) +__command_list__ = ["cash"] + +__handlers__ = [CONVERTER_HANDLER] + +__mod_name__ = "Cᴀsʜ" diff --git a/QueenNoxi/modules/cust_filters.py b/QueenNoxi/modules/cust_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..59ccb4513f76106dfb38b7b8106df7a72d293cca --- /dev/null +++ b/QueenNoxi/modules/cust_filters.py @@ -0,0 +1,125 @@ +import re +import html +import random +from pyrogram import filters, Client, enums +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + CallbackQuery, +) +from pyrogram.errors import RPCError + +import QueenNoxi +from QueenNoxi import pbot, LOGGER, DRAGONS +from QueenNoxi.modules.helper_funcs.chat_status import user_admin, connection_status +from QueenNoxi.modules.helper_funcs.misc import build_keyboard, revert_buttons +from QueenNoxi.modules.helper_funcs.msg_types import get_filter_type, Types +from QueenNoxi.modules.helper_funcs.string_handling import button_markdown_parser, split_quotes +from QueenNoxi.modules.sql import cust_filters_sql as sql + +# Handler group for filters +HANDLER_GROUP = 10 + +@pbot.on_message(filters.command("filter") & filters.group) +@user_admin +@connection_status +async def add_filter(client: Client, message: Message): + chat_id = message.chat.id + args = message.text.split(None, 1) + + if len(args) < 2 and not message.reply_to_message: + await message.reply_text("Please provide a keyword and reply text for the filter!") + return + + if message.reply_to_message: + if len(args) < 2: + await message.reply_text("Please provide a keyword for this filter!") + return + keyword = args[1].lower() + else: + extracted = split_quotes(args[1]) + if len(extracted) < 2: + await message.reply_text("Please provide both a keyword and a reply text!") + return + keyword = extracted[0].lower() + + text, file_type, file_id = await get_filter_type(message) + + # Extract buttons from text if any + _, buttons = button_markdown_parser(text) if text else (None, []) + + sql.new_add_filter(chat_id, keyword, text, file_type, file_id, buttons) + await message.reply_text(f"Saved filter '{keyword}'!") + +@pbot.on_message(filters.command("stop") & filters.group) +@user_admin +@connection_status +async def stop_filter(client: Client, message: Message): + chat_id = message.chat.id + args = message.command[1:] + + if not args: + await message.reply_text("What filter should I stop?") + return + + keyword = args[0].lower() + if sql.remove_filter(chat_id, keyword): + await message.reply_text(f"Stopped replying to '{keyword}'.") + else: + await message.reply_text("That's not an active filter!") + +@pbot.on_message(filters.command("filters") & filters.group) +@connection_status +async def list_handlers(client: Client, message: Message): + chat_id = message.chat.id + all_handlers = sql.get_chat_triggers(chat_id) + + if not all_handlers: + await message.reply_text("No filters saved in this chat!") + return + + filter_list = f"**Filters in {html.escape(message.chat.title)}:**\n" + for keyword in all_handlers: + filter_list += f" • `{keyword}`\n" + + await message.reply_text(filter_list) + +@pbot.on_message(filters.text & filters.group, group=HANDLER_GROUP) +async def reply_filter(client: Client, message: Message): + if not message.text: + return + + chat_id = message.chat.id + chat_filters = sql.get_chat_triggers(chat_id) + + for keyword in chat_filters: + pattern = rf"( |^|[^\w]){re.escape(keyword)}( |$|[^\w])" + if re.search(pattern, message.text, flags=re.IGNORECASE): + filt = sql.get_filter(chat_id, keyword) + buttons = sql.get_buttons(chat_id, keyword) + keyboard = InlineKeyboardMarkup(build_keyboard(buttons)) if buttons else None + + if filt.file_type in (Types.TEXT, Types.BUTTON_TEXT): + await message.reply_text( + filt.reply_text, + reply_markup=keyboard, + disable_web_page_preview=True + ) + else: + await client.send_cached_media( + chat_id, + filt.file_id, + caption=filt.reply_text, + reply_markup=keyboard + ) + break + +__mod_name__ = "Filters" +__help__ = """ +**Admin Commands:** +• `/filter <keyword> <reply>`: Add a filter +• `/stop <keyword>`: Stop a filter +• `/filters`: List all filters +• `/removeallfilters`: Stop ALL filters (Owner only) +""" diff --git a/QueenNoxi/modules/dbcleanup.py b/QueenNoxi/modules/dbcleanup.py new file mode 100644 index 0000000000000000000000000000000000000000..1290e1f4fd4e8bb02fedf87df004b664557182d2 --- /dev/null +++ b/QueenNoxi/modules/dbcleanup.py @@ -0,0 +1,144 @@ +from time import sleep + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.error import BadRequest, Unauthorized +from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler + +import QueenNoxi.modules.sql.global_bans_sql as gban_sql +import QueenNoxi.modules.sql.users_sql as user_sql +from QueenNoxi import DEV_USERS, OWNER_ID, dispatcher +from QueenNoxi.modules.helper_funcs.chat_status import dev_plus + + +def get_invalid_chats(update: Update, context: CallbackContext, remove: bool = False): + bot = context.bot + chat_id = update.effective_chat.id + chats = user_sql.get_all_chats() + kicked_chats, progress = 0, 0 + chat_list = [] + progress_message = None + + for chat in chats: + + if ((100 * chats.index(chat)) / len(chats)) > progress: + progress_bar = f"{progress}% completed in getting invalid chats." + if progress_message: + try: + bot.editMessageText( + progress_bar, chat_id, progress_message.message_id + ) + except: + pass + else: + progress_message = bot.sendMessage(chat_id, progress_bar) + progress += 5 + + cid = chat.chat_id + sleep(0.1) + try: + bot.get_chat(cid, timeout=60) + except (BadRequest, Unauthorized): + kicked_chats += 1 + chat_list.append(cid) + except: + pass + + try: + progress_message.delete() + except: + pass + + if not remove: + return kicked_chats + else: + for muted_chat in chat_list: + sleep(0.1) + user_sql.rem_chat(muted_chat) + return kicked_chats + + +def get_invalid_gban(update: Update, context: CallbackContext, remove: bool = False): + bot = context.bot + banned = gban_sql.get_gban_list() + ungbanned_users = 0 + ungban_list = [] + + for user in banned: + user_id = user["user_id"] + sleep(0.1) + try: + bot.get_chat(user_id) + except BadRequest: + ungbanned_users += 1 + ungban_list.append(user_id) + except: + pass + + if not remove: + return ungbanned_users + else: + for user_id in ungban_list: + sleep(0.1) + gban_sql.ungban_user(user_id) + return ungbanned_users + + +@dev_plus +def dbcleanup(update: Update, context: CallbackContext): + msg = update.effective_message + + msg.reply_text("Getting invalid chat count ...") + invalid_chat_count = get_invalid_chats(update, context) + + msg.reply_text("Getting invalid gbanned count ...") + invalid_gban_count = get_invalid_gban(update, context) + + reply = f"Total invalid chats - {invalid_chat_count}\n" + reply += f"Total invalid gbanned users - {invalid_gban_count}" + + buttons = [[InlineKeyboardButton("Cleanup DB", callback_data="db_cleanup")]] + + update.effective_message.reply_text( + reply, reply_markup=InlineKeyboardMarkup(buttons) + ) + + +def callback_button(update: Update, context: CallbackContext): + bot = context.bot + query = update.callback_query + message = query.message + chat_id = update.effective_chat.id + query_type = query.data + + admin_list = [OWNER_ID] + DEV_USERS + + bot.answer_callback_query(query.id) + + if query_type == "db_leave_chat": + if query.from_user.id in admin_list: + bot.editMessageText("Leaving chats ...", chat_id, message.message_id) + chat_count = get_muted_chats(update, context, True) + bot.sendMessage(chat_id, f"Left {chat_count} chats.") + else: + query.answer("You are not allowed to use this.") + elif query_type == "db_cleanup": + if query.from_user.id in admin_list: + bot.editMessageText("Cleaning up DB ...", chat_id, message.message_id) + invalid_chat_count = get_invalid_chats(update, context, True) + invalid_gban_count = get_invalid_gban(update, context, True) + reply = "Cleaned up {} chats and {} gbanned users from db.".format( + invalid_chat_count, invalid_gban_count + ) + bot.sendMessage(chat_id, reply) + else: + query.answer("You are not allowed to use this.") + + +DB_CLEANUP_HANDLER = CommandHandler("dbcleanup", dbcleanup, run_async=True) +BUTTON_HANDLER = CallbackQueryHandler(callback_button, pattern="db_.*", run_async=True) + +dispatcher.add_handler(DB_CLEANUP_HANDLER) +dispatcher.add_handler(BUTTON_HANDLER) + +__mod_name__ ="Dᴀᴛᴀʙᴀsᴇ" +__handlers__ = [DB_CLEANUP_HANDLER, BUTTON_HANDLER] diff --git a/QueenNoxi/modules/debug.py b/QueenNoxi/modules/debug.py new file mode 100644 index 0000000000000000000000000000000000000000..1332a98f9ff2905c97be726d48823598bbc7512d --- /dev/null +++ b/QueenNoxi/modules/debug.py @@ -0,0 +1,72 @@ +import datetime +import os + +from telegram import Update +from telegram.ext import CallbackContext, CommandHandler +from telethon import events + +from QueenNoxi import dispatcher, telethn +from QueenNoxi.modules.helper_funcs.chat_status import dev_plus + +DEBUG_MODE = False + + +@dev_plus +def debug(update: Update, context: CallbackContext): + global DEBUG_MODE + args = update.effective_message.text.split(None, 1) + message = update.effective_message + print(DEBUG_MODE) + if len(args) > 1: + if args[1] in ("yes", "on"): + DEBUG_MODE = True + message.reply_text("Debug mode is now on.") + elif args[1] in ("no", "off"): + DEBUG_MODE = False + message.reply_text("Debug mode is now off.") + else: + if DEBUG_MODE: + message.reply_text("Debug mode is currently on.") + else: + message.reply_text("Debug mode is currently off.") + + +@telethn.on(events.NewMessage(pattern="[/!].*")) +async def i_do_nothing_yes(event): + global DEBUG_MODE + if DEBUG_MODE: + print(f"-{event.from_id} ({event.chat_id}) : {event.text}") + if os.path.exists("updates.txt"): + with open("updates.txt", "r") as f: + text = f.read() + with open("updates.txt", "w+") as f: + f.write(text + f"\n-{event.from_id} ({event.chat_id}) : {event.text}") + else: + with open("updates.txt", "w+") as f: + f.write( + f"- {event.from_id} ({event.chat_id}) : {event.text} | {datetime.datetime.now()}" + ) + + +support_chat = os.getenv("SUPPORT_CHAT") + + +@dev_plus +def logs(update: Update, context: CallbackContext): + user = update.effective_user + with open("log.txt", "rb") as f: + context.bot.send_document(document=f, filename=f.name, chat_id=user.id) + + +LOG_HANDLER = CommandHandler("logs", logs, run_async=True) +dispatcher.add_handler(LOG_HANDLER) + +DEBUG_HANDLER = CommandHandler("debug", debug, run_async=True) +dispatcher.add_handler(DEBUG_HANDLER) + +__mod_name__ = "Dᴇʙᴜɢ" +__help__ = """ +/logs ᴛᴏ ɢᴇᴛ ʜᴇʀᴜᴋᴏ ʟᴏɢs +/debug ᴛᴏ ᴏɴ / ᴏғғ ᴅᴇʙᴜɢ """ +__command_list__ = ["debug"] +__handlers__ = [DEBUG_HANDLER] diff --git a/QueenNoxi/modules/dev.py b/QueenNoxi/modules/dev.py new file mode 100644 index 0000000000000000000000000000000000000000..ffbce91106a2344986b49d307091474631601e66 --- /dev/null +++ b/QueenNoxi/modules/dev.py @@ -0,0 +1,71 @@ +import os +import subprocess +import sys +import asyncio +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +import QueenNoxi +from QueenNoxi import pbot, OWNER_ID +from QueenNoxi.modules.helper_funcs.chat_status import dev_plus + +@pbot.on_message(filters.command("lockdown") & filters.user(OWNER_ID)) +@dev_plus +async def allow_groups(client: Client, message: Message): + args = message.command[1:] + if not args: + await message.reply_text(f"Current state: {QueenNoxi.ALLOW_CHATS}") + return + + if args[0].lower() in ("off", "no"): + QueenNoxi.ALLOW_CHATS = True + elif args[0].lower() in ("yes", "on"): + QueenNoxi.ALLOW_CHATS = False + else: + await message.reply_text("Format: /lockdown Yes/No or Off/On") + return + await message.reply_text("Done! Lockdown value toggled.") + +@pbot.on_message(filters.command("leave") & filters.user(OWNER_ID)) +@dev_plus +async def leave(client: Client, message: Message): + args = message.command[1:] + if args: + chat_id = args[0] + try: + await client.leave_chat(int(chat_id)) + await message.reply_text("Successfully left the chat.") + except Exception as e: + await message.reply_text(f"Could not leave chat: {e}") + else: + await message.reply_text("Send a valid chat ID.") + +@pbot.on_message(filters.command("reboot") & filters.user(OWNER_ID)) +@dev_plus +async def restart(client: Client, message: Message): + await message.reply_text("Starting a new instance and shutting down this one...") + # These might need adjustment depending on how the bot is actually run + # For now, keeping the original logic of calling scripts + if os.name == 'nt': # Windows + os.system("restart.bat") + else: # Linux/HuggingFace (scripts might not exist there, but keeping for compatibility) + os.execv(sys.executable, [sys.executable] + sys.argv) + +@pbot.on_message(filters.command("gitpull") & filters.user(OWNER_ID)) +@dev_plus +async def gitpull(client: Client, message: Message): + sent_msg = await message.reply_text("Pulling changes from git...") + subprocess.Popen("git pull", stdout=subprocess.PIPE, shell=True).wait() + + await sent_msg.edit_text("Changes pulled. Restarting in 5 seconds...") + for i in reversed(range(5)): + await asyncio.sleep(1) + await sent_msg.edit_text(f"Restarting in {i+1}...") + + await sent_msg.edit_text("Restarting now.") + if os.name == 'nt': + os.system("restart.bat") + else: + os.execv(sys.executable, [sys.executable] + sys.argv) + +__mod_name__ = "Dᴇᴠ" diff --git a/QueenNoxi/modules/dicegame.py b/QueenNoxi/modules/dicegame.py new file mode 100644 index 0000000000000000000000000000000000000000..d74870a0bbca76f9e15a9e2637006603d9755187 --- /dev/null +++ b/QueenNoxi/modules/dicegame.py @@ -0,0 +1,51 @@ +from pyrogram import Client, enums, filters +#from config import * +import asyncio +from QueenNoxi import pbot as queennoxi + +from pyrogram.handlers import MessageHandler + + +@queennoxi.on_message(filters.command("dice")) +async def dice(bot, message): + x=await bot.send_dice(message.chat.id) + m=x.dice.value + await message.reply_text(f"Hey {message.from_user.mention} your Score is : {m}",quote=True) + +@queennoxi.on_message(filters.command("dart")) +async def dart(bot, message): + x=await bot.send_dice(message.chat.id, "🎯") + m=x.dice.value + await message.reply_text(f"Hey {message.from_user.mention} your Score is : {m}",quote=True) + +@queennoxi.on_message(filters.command("basket")) +async def basket(bot, message): + x=await bot.send_dice(message.chat.id, "🏀") + m=x.dice.value + await message.reply_text(f"Hey {message.from_user.mention} your Score is : {m}",quote=True) +@queennoxi.on_message(filters.command("jackpot")) +async def basket(bot, message): + x=await bot.send_dice(message.chat.id, "🎰") + m=x.dice.value + await message.reply_text(f"Hey {message.from_user.mention} your Score is : {m}",quote=True) +@queennoxi.on_message(filters.command("ball")) +async def basket(bot, message): + x=await bot.send_dice(message.chat.id, "🎳") + m=x.dice.value + await message.reply_text(f"Hey {message.from_user.mention} your Score is : {m}",quote=True) +@queennoxi.on_message(filters.command("football")) +async def basket(bot, message): + x=await bot.send_dice(message.chat.id, "⚽") + m=x.dice.value + await message.reply_text(f"Hey {message.from_user.mention} your Score is : {m}",quote=True) +__help__ = """ + Play Game With Emojis: +/dice - Dice 🎲 +/dart - Dart 🎯 +/basket - Basket Ball 🏀 +/ball - Bowling Ball 🎳 +/football - Football ⚽ +/jackpot - Spin slot machine 🎰 + """ + +__mod_name__ = "Dɪᴄᴇ" diff --git a/QueenNoxi/modules/disable.py b/QueenNoxi/modules/disable.py new file mode 100644 index 0000000000000000000000000000000000000000..69a21f8a0b91247ff7d4dd4cd9e5a73461a3a657 --- /dev/null +++ b/QueenNoxi/modules/disable.py @@ -0,0 +1,129 @@ +import importlib +from typing import Union, List +from functools import wraps + +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import pbot, LOGGER +from QueenNoxi.modules.helper_funcs.chat_status import connection_status, user_admin, is_user_admin +from QueenNoxi.modules.sql import disable_sql as sql +from QueenNoxi.modules.helper_funcs.handlers import SpamChecker, is_user_blacklisted + +DISABLE_CMDS = [] +DISABLE_OTHER = [] +ADMIN_CMDS = [] + +def DisableAbleCommandHandler(command, callback=None, admin_ok=False, **kwargs): + def decorator(callback): + if isinstance(command, str): + cmds = [command] + else: + cmds = command + + for cmd in cmds: + DISABLE_CMDS.append(cmd) + if admin_ok: + ADMIN_CMDS.append(cmd) + + @wraps(callback) + async def wrapped(client: Client, message: Message, *args, **kwargs): + if message.from_user: + user_id = message.from_user.id + if is_user_blacklisted(user_id): + return + if SpamChecker.check_user(user_id): + return + + chat_id = message.chat.id + cmd_name = message.command[0].lower() if message.command else "" + + if sql.is_command_disabled(chat_id, cmd_name): + if admin_ok and await is_user_admin(message.chat, message.from_user.id): + return await callback(client, message, *args, **kwargs) + return + + return await callback(client, message, *args, **kwargs) + + pbot.on_message(filters.command(cmds) & ~filters.forwarded)(wrapped) + return wrapped + + if callback is not None: + return decorator(callback) + return decorator + + +@pbot.on_message(filters.command("disable") & filters.group) +@connection_status +@user_admin +async def disable(client: Client, message: Message): + if len(message.command) < 2: + await message.reply_text("What should I disable?") + return + + disable_cmd = message.command[1].lower() + if disable_cmd in set(DISABLE_CMDS + DISABLE_OTHER): + sql.disable_command(message.chat.id, disable_cmd) + await message.reply_text(f"Disabled the use of `{disable_cmd}`") + else: + await message.reply_text("That command can't be disabled") + + +@pbot.on_message(filters.command("enable") & filters.group) +@connection_status +@user_admin +async def enable(client: Client, message: Message): + if len(message.command) < 2: + await message.reply_text("What should I enable?") + return + + enable_cmd = message.command[1].lower() + if sql.enable_command(message.chat.id, enable_cmd): + await message.reply_text(f"Enabled the use of `{enable_cmd}`") + else: + await message.reply_text("Is that even disabled?") + + +@pbot.on_message(filters.command(["cmds", "disabled"]) & filters.group) +@connection_status +async def commands(client: Client, message: Message): + chat_id = message.chat.id + disabled = sql.get_all_disabled(chat_id) + if not disabled: + await message.reply_text("No commands are disabled!") + return + + result = "The following commands are currently restricted:\n" + for cmd in disabled: + result += f" - `{cmd}`\n" + await message.reply_text(result) + + +@pbot.on_message(filters.command("listcmds") & filters.group) +@connection_status +@user_admin +async def list_cmds(client: Client, message: Message): + if DISABLE_CMDS + DISABLE_OTHER: + result = "The following commands are toggleable:\n" + for cmd in set(DISABLE_CMDS + DISABLE_OTHER): + result += f" - `{cmd}`\n" + await message.reply_text(result) + else: + await message.reply_text("No commands can be disabled.") + + +def __stats__(): + return f"• {sql.num_disabled()} ᴅɪsᴀʙʟᴇᴅ ɪᴛᴇᴍs, ᴀᴄʀᴏss {sql.num_chats()} ᴄʜᴀᴛs." + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + +__mod_name__ = "Disable" +__help__ = """ +» /cmds: Check the current status of disabled commands + +*Admins only:* +» /enable <cmd name>: Enable that command +» /disable <cmd name>: Disable that command +» /listcmds: List all possible toggleable commands +""" diff --git a/QueenNoxi/modules/disasters.py b/QueenNoxi/modules/disasters.py new file mode 100644 index 0000000000000000000000000000000000000000..b69e2b97053bb9a19ef540e9798b085ec7a28f39 --- /dev/null +++ b/QueenNoxi/modules/disasters.py @@ -0,0 +1,213 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import ( + DEMONS, + DEV_USERS, + DRAGONS, + OWNER_ID, + TIGERS, + WOLVES, + pbot, +) +from QueenNoxi.modules.helper_funcs.chat_status import ( + dev_plus, + sudo_plus, + whitelist_plus, +) +from QueenNoxi.modules.helper_funcs.extraction import extract_user +from QueenNoxi.modules.log_channel import gloggable + +async def check_user_id(user_id: int, client: Client) -> str: + if not user_id: + return "That...is a chat! baka ka omae?" + if user_id == (await client.get_me()).id: + return "This does not work that way." + return None + +@pbot.on_message(filters.command("addsudo") & filters.user(OWNER_ID)) +@dev_plus +@gloggable +async def addsudo(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + reply = await check_user_id(user_id, client) + if reply: + await message.reply_text(reply) + return "" + + if user_id in DRAGONS: + await message.reply_text("This member is already a Dragon Disaster") + return "" + + if user_id in DEMONS: DEMONS.remove(user_id) + if user_id in WOLVES: WOLVES.remove(user_id) + if user_id in TIGERS: TIGERS.remove(user_id) + + DRAGONS.append(user_id) + user_member = await client.get_users(user_id) + await message.reply_text(f"Successfully set Disaster level of {user_member.first_name} to Dragon!") + + return f"#SUDO\n**Admin:** {message.from_user.mention}\n**User:** {user_member.mention}" + +@pbot.on_message(filters.command(["addsupport", "adddemon"]) & filters.user(DRAGONS)) +@sudo_plus +@gloggable +async def addsupport(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + reply = await check_user_id(user_id, client) + if reply: + await message.reply_text(reply) + return "" + + if user_id in DEMONS: + await message.reply_text("This user is already a Demon Disaster.") + return "" + + if user_id in DRAGONS: DRAGONS.remove(user_id) + if user_id in WOLVES: WOLVES.remove(user_id) + if user_id in TIGERS: TIGERS.remove(user_id) + + DEMONS.append(user_id) + user_member = await client.get_users(user_id) + await message.reply_text(f"{user_member.first_name} was added as a Demon Disaster!") + + return f"#SUPPORT\n**Admin:** {message.from_user.mention}\n**User:** {user_member.mention}" + +@pbot.on_message(filters.command(["addwhitelist", "addwolf"]) & filters.user(DRAGONS)) +@sudo_plus +@gloggable +async def addwhitelist(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + reply = await check_user_id(user_id, client) + if reply: + await message.reply_text(reply) + return "" + + if user_id in WOLVES: + await message.reply_text("This user is already a Wolf Disaster.") + return "" + + for l in [DRAGONS, DEMONS, TIGERS]: + if user_id in l: l.remove(user_id) + + WOLVES.append(user_id) + user_member = await client.get_users(user_id) + await message.reply_text(f"Successfully promoted {user_member.first_name} to a Wolf Disaster!") + + return f"#WHITELIST\n**Admin:** {message.from_user.mention}\n**User:** {user_member.mention}" + +@pbot.on_message(filters.command("addtiger") & filters.user(DRAGONS)) +@sudo_plus +@gloggable +async def addtiger(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + reply = await check_user_id(user_id, client) + if reply: + await message.reply_text(reply) + return "" + + if user_id in TIGERS: + await message.reply_text("This user is already a Tiger.") + return "" + + for l in [DRAGONS, DEMONS, WOLVES]: + if user_id in l: l.remove(user_id) + + TIGERS.append(user_id) + user_member = await client.get_users(user_id) + await message.reply_text(f"Successfully promoted {user_member.first_name} to a Tiger Disaster!") + + return f"#TIGER\n**Admin:** {message.from_user.mention}\n**User:** {user_member.mention}" + +@pbot.on_message(filters.command(["removesudo", "rmsudo"]) & filters.user(OWNER_ID)) +@dev_plus +@gloggable +async def removesudo(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + if user_id in DRAGONS: + DRAGONS.remove(user_id) + user_member = await client.get_users(user_id) + await message.reply_text("Demoted user to Civilian.") + return f"#UNSUDO\n**Admin:** {message.from_user.mention}\n**User:** {user_member.mention}" + await message.reply_text("This user is not a Dragon Disaster!") + return "" + +@pbot.on_message(filters.command(["removesupport", "removedemon"]) & filters.user(DRAGONS)) +@sudo_plus +@gloggable +async def removesupport(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + if user_id in DEMONS: + DEMONS.remove(user_id) + user_member = await client.get_users(user_id) + await message.reply_text("Demoted user to Civilian.") + return f"#UNSUPPORT\n**Admin:** {message.from_user.mention}\n**User:** {user_member.mention}" + await message.reply_text("This user is not a Demon level Disaster!") + return "" + +@pbot.on_message(filters.command(["sudolist", "dragons"])) +@whitelist_plus +async def sudolist(client: Client, message: Message): + true_sudo = list(set(DRAGONS) - set(DEV_USERS)) + reply = "**Known Dragon Disasters 🐉:**\n" + for each_user in true_sudo: + try: + user = await client.get_users(each_user) + reply += f"• {user.mention}\n" + except: pass + await message.reply_text(reply) + +@pbot.on_message(filters.command(["supportlist", "demons"])) +@whitelist_plus +async def supportlist(client: Client, message: Message): + reply = "**Known Demon Disasters 👹:**\n" + for each_user in DEMONS: + try: + user = await client.get_users(each_user) + reply += f"• {user.mention}\n" + except: pass + await message.reply_text(reply) + +@pbot.on_message(filters.command(["wolves", "whitelistlist"])) +@whitelist_plus +async def wolveslist(client: Client, message: Message): + reply = "**Known Wolf Disasters 🐺:**\n" + for each_user in WOLVES: + try: + user = await client.get_users(each_user) + reply += f"• {user.mention}\n" + except: pass + await message.reply_text(reply) + +@pbot.on_message(filters.command("tigers")) +@whitelist_plus +async def tigerslist(client: Client, message: Message): + reply = "**Known Tiger Disasters 🐯:**\n" + for each_user in TIGERS: + try: + user = await client.get_users(each_user) + reply += f"• {user.mention}\n" + except: pass + await message.reply_text(reply) + +@pbot.on_message(filters.command("devlist")) +@whitelist_plus +async def devlist(client: Client, message: Message): + true_dev = list(set(DEV_USERS) - {OWNER_ID}) + reply = "✨ **Devs User List:**\n" + for each_user in true_dev: + try: + user = await client.get_users(each_user) + reply += f"• {user.mention}\n" + except: pass + await message.reply_text(reply) + +__mod_name__ = "Dᴇᴠꜱ" +__help__ = """ +★ /sudolist: lists all dragons +★ /supportlist: lists all demons +★ /wolves: lists all wolves +★ /tigers: lists all tigers +★ /devlist: lists hero association members +""" diff --git a/QueenNoxi/modules/echo.py b/QueenNoxi/modules/echo.py new file mode 100644 index 0000000000000000000000000000000000000000..6870f277022820c2343f6dc954353b0f02f6fbf4 --- /dev/null +++ b/QueenNoxi/modules/echo.py @@ -0,0 +1,58 @@ +from pyrogram import filters +from pyrogram.types import Message +from QueenNoxi import pbot +from QueenNoxi.modules.helper_funcs.chat_status import sudo_plus + +@pbot.on_message(filters.command("go")) +async def go_cmd(client, message: Message): + if len(message.command) < 2: + await message.reply_text("Usage: /go <text>") + return + + text = message.text.split(None, 1)[1] + try: + await message.delete() + except Exception: + pass + if message.reply_to_message: + await message.reply_to_message.reply_text(text) + else: + await client.send_message(message.chat.id, text) + +@pbot.on_message(filters.command("more")) +@sudo_plus +async def more_cmd(client, message: Message): + if len(message.command) < 3: + await message.reply_text("Usage: /more <count> <text>") + return + + args = message.text.split(None, 2) + try: + count = int(args[1]) + except ValueError: + await message.reply_text("Count must be an integer.") + return + + # Cap count to prevent spam/floodwaits + count = min(count, 50) + text = args[2] + + try: + await message.delete() + except Exception: + pass + + for _ in range(count): + if message.reply_to_message: + await message.reply_to_message.reply_text(text) + else: + await client.send_message(message.chat.id, text) + +__mod_name__ = "Echo" +__help__ = """ +**Commands:** +• `/go <text>`: Delete your message and send text as the bot. (Available to all) + +**Sudo/Owner restrict:** +• `/more <count> <text>`: Repeat text count times as the bot. +""" diff --git a/QueenNoxi/modules/encode_decode.py b/QueenNoxi/modules/encode_decode.py new file mode 100644 index 0000000000000000000000000000000000000000..95f77537896e32aaf3c85526aa7f12698de6f1c2 --- /dev/null +++ b/QueenNoxi/modules/encode_decode.py @@ -0,0 +1,90 @@ +import requests +from .. import pbot as QueenNoxi,BOT_NAME,BOT_USERNAME +import time +from pyrogram.enums import ChatAction, ParseMode +from pyrogram import filters +@QueenNoxi.on_message(filters.command(["password"])) +async def passwordgen(bot, message): + + try: + + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "Example:**\n\n`/password <length>`") + else: + a = message.text.split(' ', 1)[1] + response = requests.get(f'https://queennoxi-api.vercel.app/password?num={a}') + x=response.json()["results"] + + await message.reply_text(f"Here is your Password:- ` {x}`", parse_mode=ParseMode.MARKDOWN) + except Exception as e: + await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") +@QueenNoxi.on_message(filters.command(["morseencode"])) +async def morse_en(bot, message): + + try: + + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "Example:**\n\n`/morseencode <query>`") + else: + a = message.text.split(' ', 1)[1] + response = requests.get(f'https://queennoxi-api.vercel.app/morse/encode?query={a}') + x=response.json()["results"] + + await message.reply_text(f"`{x}`", parse_mode=ParseMode.MARKDOWN) + except Exception as e: + await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") +@QueenNoxi.on_message(filters.command("morsedecode")) +async def morse_de(bot, message): + + try: + + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "Example:**\n\n`/morsedecode <query>`") + else: + a = message.text.split(' ', 1)[1] + response = requests.get(f'https://queennoxi-api.vercel.app/morse/decode?query={a}') + x=response.json()["results"] + + await message.reply_text(f"`{x}`", parse_mode=ParseMode.MARKDOWN) + except Exception as e: + await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") +@QueenNoxi.on_message(filters.command(["encode"])) +async def base_en(bot, message): + + try: + + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "Example:**\n\n`/encode <query>`") + else: + a = message.text.split(' ', 1)[1] + response = requests.get(f'https://queennoxi-api.vercel.app/base/encode?query={a}') + x=response.json()["results"] + + await message.reply_text(f"` {x}`", parse_mode=ParseMode.MARKDOWN) + except Exception as e: + await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") +@QueenNoxi.on_message(filters.command(["decode"])) +async def base_de(bot, message): + + try: + + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "Example:**\n\n`/decode <query>`") + else: + a = message.text.split(' ', 1)[1] + response = requests.get(f'https://queennoxi-api.vercel.app/base/decode?query={a}') + x=response.json()["results"] + + await message.reply_text(f" `{x}`", parse_mode=ParseMode.MARKDOWN) + except Exception as e: + await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") diff --git a/QueenNoxi/modules/encrypt.py b/QueenNoxi/modules/encrypt.py new file mode 100644 index 0000000000000000000000000000000000000000..cce560d20e336e3ee2affb35de4c28f544828552 --- /dev/null +++ b/QueenNoxi/modules/encrypt.py @@ -0,0 +1,47 @@ +import secureme +from pyrogram import filters +from QueenNoxi import pbot as queennoxi + + +@queennoxi.on_message(filters.command("encrypt")) +async def encyrpt(bot, message): + if len(message.command) < 2: + return await message.reply_text("**Example:**\n\n`/encyrpt India`") + m = message.text.split(' ',1)[1] + try: + Secure = secureme.encrypt(m) + + await message.reply_text(f"`{Secure}`") + + + except Exception as e: + await message.reply_text(f"Error {e}") + +@queennoxi.on_message(filters.command("decrypt")) +async def decrypt(bot, message): + if len(message.command) < 2: + return await message.reply_text("**Example:**\n\n`/decrypt Nsinf`") + m = message.text.split(' ',1)[1] + try: + Decrypt = secureme.decrypt(m) + + await message.reply_text(f"`{Decrypt}`") + + + except Exception as e: + await message.reply_text(f"{e}") + + +__mod_name__ = "ᴘᴀssᴡᴏʀᴅ" + +__help__ = """ +*ᴄᴏɴᴠᴇʀᴛs* + ❍ /encrypt*:* ᴇɴᴄʀʏᴘᴛs ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ + ❍ /decrypt*:* ᴅᴇᴄʀʏᴘᴛs ᴘʀᴇᴠɪᴏᴜsʟʏ ᴇᴄʀʏᴘᴛᴇᴅ ᴛᴇxᴛ + ❍ /encode*:* ᴇɴᴄᴏᴅᴇ ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ + ❍ /decode*:* ᴅᴇᴄᴏᴅᴇ ᴘʀᴇᴠɪᴏᴜsʟʏ ᴇᴄʀʏᴘᴛᴇᴅ ᴛᴇxᴛ + ❍ /morseencode*:* Mᴏʀsᴇ Eɴᴄᴏᴅᴇ ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ + ❍ /morsedecode*:* ᴅᴇᴄʀʏᴘᴛs ᴘʀᴇᴠɪᴏᴜsʟʏ ᴇᴄʀʏᴘᴛᴇᴅ ᴛᴇxᴛ + ❍ /password *:* ɢɪᴠᴇ ʟᴇɴɢᴛʜ ᴏғ ᴘᴀssᴡᴏʀᴅ ᴛᴏ ɢᴇɴᴇʀᴀᴛᴇ + ❍ /uselessfact *:* Gᴇɴᴇʀᴀᴛᴇ Rᴀᴍᴅᴏᴍ Usᴇʟᴇss Fᴀᴄᴛ +""" diff --git a/QueenNoxi/modules/english.py b/QueenNoxi/modules/english.py new file mode 100644 index 0000000000000000000000000000000000000000..5ce870cef5eb80a33f7ac6b23dd91586ce75093e --- /dev/null +++ b/QueenNoxi/modules/english.py @@ -0,0 +1,83 @@ +import json +import requests +from PyDictionary import PyDictionary +from pyrogram import filters +from QueenNoxi import pbot as app + +API_KEY = "6ae0c3a0-afdc-4532-a810-82ded0054236" +URL = "http://services.gingersoftware.com/Ginger/correct/json/GingerTheText" + +@app.on_message(filters.command("spell")) +async def _(_, message): + if not message.reply_to_message: + return + msg = message.reply_to_message.text + if not msg: + return + params = dict(lang="US", clientVersion="2.0", apiKey=API_KEY, text=msg) + res = requests.get(URL, params=params) + changes = json.loads(res.text).get("LightGingerTheTextResult") + curr_string = "" + prev_end = 0 + for change in changes: + start = change.get("From") + end = change.get("To") + 1 + suggestions = change.get("Suggestions") + if suggestions: + sugg_str = suggestions[0].get("Text") + curr_string += msg[prev_end:start] + sugg_str + prev_end = end + curr_string += msg[prev_end:] + await message.reply(curr_string) + + +dictionary = PyDictionary() + +@app.on_message(filters.command("define")) +async def _(_, message): + if len(message.command) < 2: + return + word = message.text.split(None, 1)[1] + let = dictionary.meaning(word) + set_word = str(let) + jet = set_word.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + await message.reply(got) + + +@app.on_message(filters.command("synonyms")) +async def _(_, message): + if len(message.command) < 2: + return + word = message.text.split(None, 1)[1] + let = dictionary.synonym(word) + set_word = str(let) + jet = set_word.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + await message.reply(got) + + +@app.on_message(filters.command("antonyms")) +async def _(_, message): + if len(message.command) < 2: + return + word = message.text.split(None, 1)[1] + let = dictionary.antonym(word) + set_word = str(let) + jet = set_word.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + await message.reply(got) + + +__help__ = """ + ❍ /define <ᴛᴇxᴛ>*:* ᴛʏᴘᴇ ᴛʜᴇ ᴡᴏʀᴅ ᴏʀ ᴇxᴘʀᴇssɪᴏɴ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ sᴇᴀʀᴄʜ +ғᴏʀ ᴇxᴀᴍᴘʟᴇ /ᴅᴇғɪɴᴇ ᴋɪʟʟ + ❍ /spell *:* ᴡʜɪʟᴇ ʀᴇᴘʟʏɪɴɢ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ, ᴡɪʟʟ ʀᴇᴘʟʏ ᴡɪᴛʜ ᴀ ɢʀᴀᴍᴍᴀʀ ᴄᴏʀʀᴇᴄᴛᴇᴅ ᴠᴇʀsɪᴏɴ + ❍ /synonyms <ᴡᴏʀᴅ>*:* ғɪɴᴅ ᴛʜᴇ sʏɴᴏɴʏᴍs ᴏғ ᴀ ᴡᴏʀᴅ + ❍ /antonyms <ᴡᴏʀᴅ>*:* ғɪɴᴅ ᴛʜᴇ ᴀɴᴛᴏɴʏᴍs ᴏғ ᴀ ᴡᴏʀᴅ +""" + +__mod_name__ = "Eɴɢʟɪsʜ" diff --git a/QueenNoxi/modules/error_handler.py b/QueenNoxi/modules/error_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..352ecb2008e1c45f1dbdd4d5a9975af3aebaf694 --- /dev/null +++ b/QueenNoxi/modules/error_handler.py @@ -0,0 +1,18 @@ +import html +import traceback +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import pbot, DEV_USERS + +@pbot.on_message(filters.command("errors")) +async def list_errors(client: Client, message: Message): + if message.from_user.id not in DEV_USERS: + return + await message.reply_text("Error logging is being refactored for Pyrogram. Check logs for details.") + +# Global error handling is typically done by wrapping the handler execution loop +# or using custom middleware in newer Pyrogram versions, but for this migration +# we'll rely on the standard logging for now. + +__mod_name__ = "Errors" diff --git a/QueenNoxi/modules/eval.py b/QueenNoxi/modules/eval.py new file mode 100644 index 0000000000000000000000000000000000000000..66f41ff8e906991cced0e2c0e5949aaf093284f5 --- /dev/null +++ b/QueenNoxi/modules/eval.py @@ -0,0 +1,112 @@ +import io +import os +import textwrap +import traceback +from contextlib import redirect_stdout + +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import LOGGER, pbot, OWNER_ID +from QueenNoxi.modules.helper_funcs.chat_status import dev_plus + +namespaces = {} + + +def namespace_of(chat, message, client): + if chat not in namespaces: + namespaces[chat] = { + "__builtins__": globals()["__builtins__"], + "client": client, + "pbot": pbot, + "message": message, + "user": message.from_user, + "chat": message.chat, + } + + return namespaces[chat] + + +async def send(msg, message: Message): + if len(str(msg)) > 4000: + with io.BytesIO(str.encode(msg)) as out_file: + out_file.name = "output.txt" + await message.reply_document(document=out_file, caption="Output too long, sent as file.") + else: + await message.reply_text(f"```python\n{msg}```", parse_mode=enums.ParseMode.MARKDOWN) + + +@pbot.on_message(filters.command(["e", "eval"]) & filters.user(OWNER_ID)) +@dev_plus +async def evaluate(client: Client, message: Message): + await send(await do(eval, client, message), message) + + +@pbot.on_message(filters.command(["x", "ex", "exec", "py"]) & filters.user(OWNER_ID)) +@dev_plus +async def execute(client: Client, message: Message): + await send(await do(exec, client, message), message) + + +def cleanup_code(code): + if code.startswith("```") and code.endswith("```"): + return "\n".join(code.split("\n")[1:-1]) + return code.strip("` \n") + + +async def do(func, client, message): + content = message.text.split(None, 1)[-1] + body = cleanup_code(content) + env = namespace_of(message.chat.id, message, client) + + stdout = io.StringIO() + + to_compile = f'async def func():\n{textwrap.indent(body, " ")}' + + try: + exec(to_compile, env) + except Exception as e: + return f"{e.__class__.__name__}: {e}" + + func = env["func"] + + try: + with redirect_stdout(stdout): + func_return = await func() + except Exception: + value = stdout.getvalue() + return f"{value}{traceback.format_exc()}" + else: + value = stdout.getvalue() + result = None + if func_return is None: + if value: + result = f"{value}" + else: + try: + result = f"{repr(eval(body, env))}" + except: + pass + else: + result = f"{value}{func_return}" + if result: + return result + return "Clean execution (No output)" + + +@pbot.on_message(filters.command("clearlocals") & filters.user(OWNER_ID)) +@dev_plus +async def clear_locals(client: Client, message: Message): + global namespaces + if message.chat.id in namespaces: + del namespaces[message.chat.id] + await message.reply_text("Locals cleared for this chat.") + + +__mod_name__ = "Eᴠᴀʟ" +__help__ = """ +★ᴏᴡɴᴇʀ ᴄᴍᴅ ★ + ❍ /eval: ᴇᴠᴀʟᴜᴀᴛᴇ ᴘʏᴛʜᴏɴ ᴄᴏᴅᴇ + ❍ /exec: ᴇxᴇᴄᴜᴛᴇ ᴘʏᴛʜᴏɴ ᴄᴏᴅᴇ + ❍ /clearlocals: ᴄʟᴇᴀʀ ʟᴏᴄᴀʟ variables +""" diff --git a/QueenNoxi/modules/extra_mod.py b/QueenNoxi/modules/extra_mod.py new file mode 100644 index 0000000000000000000000000000000000000000..c5490949616e9e4cf1a2636f6c927fd079246c04 --- /dev/null +++ b/QueenNoxi/modules/extra_mod.py @@ -0,0 +1,27 @@ +import requests +from .. import pbot as QueenNoxi,BOT_NAME,BOT_USERNAME +import time +from pyrogram.enums import ChatAction, ParseMode +from pyrogram import filters +# @QueenNoxi.on_message(filters.command("mahadev")) +# async def Mahadev(bot, message): +# try: + +# await bot.send_chat_action(message.chat.id, ChatAction.UPLOAD_PHOTO) +# response = requests.get(f'https://queennoxi-api.vercel.app/mahadev') +# x=response.json()["results"] + +# await message.reply_photo(photo=x,caption=f" \n🎉ᴘᴏᴡᴇʀᴇᴅ ʙʏ @{BOT_USERNAME} ", parse_mode=ParseMode.MARKDOWN) +# except Exception as e: +# await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") +# @QueenNoxi.on_message(filters.command("uselessfact")) +# async def uselessa_fact(bot, message): +# try: + +# await bot.send_chat_action(message.chat.id, ChatAction.TYPING) +# response = requests.get(f'https://queennoxi-api.vercel.app/uselessfact') +# x=response.json()["results"] + +# await message.reply_text(x, parse_mode=ParseMode.MARKDOWN) +# except Exception as e: +# await message.reply_text(f"**ᴇʀʀᴏʀ: {e} ") diff --git a/QueenNoxi/modules/feds.py b/QueenNoxi/modules/feds.py new file mode 100644 index 0000000000000000000000000000000000000000..b1bf07944886f965b83e8b49dde80789753ae5b0 --- /dev/null +++ b/QueenNoxi/modules/feds.py @@ -0,0 +1,772 @@ +import ast +import csv +import json +import os +import re +import time +import uuid +from io import BytesIO + +from pyrogram import filters, Client, enums +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + CallbackQuery, + MessageEntity, +) +from pyrogram.errors import RPCError, Forbidden, BadRequest + +import QueenNoxi.modules.sql.feds_sql as sql +from QueenNoxi import ( + DRAGONS, + EVENT_LOGS, + LOGGER, + OWNER_ID, + SUPPORT_CHAT, + TIGERS, + WOLVES, + pbot, +) +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import user_admin, connection_status +from QueenNoxi.modules.helper_funcs.extraction import ( + extract_unt_fedban, + extract_user, + extract_user_fban, +) +from QueenNoxi.modules.helper_funcs.string_handling import markdown_parser + +FBAN_ERRORS = { + "USER_ADMIN_INVALID", + "CHAT_NOT_FOUND", + "NOT_ENOUGH_RIGHTS_TO_RESTRICT", + "USER_NOT_PARTICIPANT", + "PEER_ID_INVALID", + "GROUP_DEACTIVATED", + "CHAT_ADMIN_REQUIRED", + "CHANNEL_PRIVATE", + "NOT_IN_CHAT", +} + +UNFBAN_ERRORS = { + "USER_ADMIN_INVALID", + "CHAT_NOT_FOUND", + "NOT_ENOUGH_RIGHTS_TO_RESTRICT", + "USER_NOT_PARTICIPANT", + "CHANNEL_PRIVATE", + "CHAT_ADMIN_REQUIRED", +} + +@pbot.on_message(filters.command("newfed")) +async def new_fed(client: Client, message: Message): + chat = message.chat + user = message.from_user + if chat.type != enums.ChatType.PRIVATE: + await message.reply_text( + "Federations can only be created by privately messaging me.", + ) + return + + args = message.command[1:] + if not args: + await message.reply_text("Please write the name of the federation!") + return + + fed_name = " ".join(args) + fed_id = str(uuid.uuid4()) + + x = sql.new_fed(user.id, fed_name, fed_id) + if not x: + await message.reply_text( + f"Can't federate! Please contact @{SUPPORT_CHAT} if the problem persist.", + ) + return + + await message.reply_text( + "**You have succeeded in creating a new federation!**\n" + f"Name: `{fed_name}`\n" + f"ID: `{fed_id}`\n\n" + "Use the command below to join the federation:\n" + f"`/joinfed {fed_id}`" + ) + try: + await client.send_message( + EVENT_LOGS, + f"New Federation: <b>{fed_name}</b>\nID: <pre>{fed_id}</pre>", + parse_mode=enums.ParseMode.HTML, + ) + except Exception as e: + LOGGER.warning(f"Cannot send a message to EVENT_LOGS: {e}") + +@pbot.on_message(filters.command("delfed")) +async def del_fed(client: Client, message: Message): + chat = message.chat + user = message.from_user + if chat.type != enums.ChatType.PRIVATE: + await message.reply_text( + "Federations can only be deleted in personal chat.", + ) + return + + args = message.command[1:] + if not args: + await message.reply_text("Please provide your federation ID.") + return + + fed_id = args[0] + owner_id = sql.get_fed_info(fed_id).get("owner") + + if str(owner_id) != str(user.id): + await message.reply_text("Only the federation owner can delete this!") + return + + await message.reply_text( + "Are you sure you want to delete your federation? This action cannot be undone, you will lose your entire ban list, and all your sub-feds will be disconnected.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Delete Federation", + callback_data=f"rm_fed_{fed_id}", + ) + ], + [ + InlineKeyboardButton( + text="Cancel", + callback_data="rm_fed_cancel", + ) + ], + ] + ), + ) + +@pbot.on_message(filters.command("fedname")) +async def rename_fed(client: Client, message: Message): + user = message.from_user + args = message.command[1:] + if not args: + await message.reply_text("Please provide a name for the federation.") + return + + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can rename this!") + return + + new_name = " ".join(args) + sql.rename_fed(fed_id, new_name) + await message.reply_text(f"Successfully renamed your federation to {new_name}!") + +@pbot.on_message(filters.command("joinfed")) +async def join_fed(client: Client, message: Message): + chat = message.chat + user = message.from_user + + if chat.type == enums.ChatType.PRIVATE: + await message.reply_text("This command can only be used in groups.") + return + + member = await chat.get_member(user.id) + if member.status != enums.ChatMemberStatus.OWNER and user.id not in DRAGONS: + await message.reply_text("Only group owners can join a federation!") + return + + args = message.command[1:] + if not args: + await message.reply_text("Please provide a federation ID.") + return + + fed_id = args[0] + getfed = sql.get_fed_info(fed_id) + if not getfed: + await message.reply_text("Invalid federation ID!") + return + + res = sql.chat_join_fed(fed_id, chat.title, chat.id) + if res: + await message.reply_text(f"Successfully joined the federation {getfed['fname']}!") + else: + await message.reply_text("This group is already part of a federation!") + +@pbot.on_message(filters.command("leavefed")) +async def leave_fed(client: Client, message: Message): + chat = message.chat + user = message.from_user + + if chat.type == enums.ChatType.PRIVATE: + await message.reply_text("This command can only be used in groups.") + return + + member = await chat.get_member(user.id) + if member.status != enums.ChatMemberStatus.OWNER and user.id not in DRAGONS: + await message.reply_text("Only group owners can leave a federation!") + return + + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + res = sql.chat_leave_fed(chat.id) + if res: + await message.reply_text("Successfully left the federation!") + else: + await message.reply_text("Failed to leave the federation.") + +@pbot.on_message(filters.command("fprom")) +async def user_join_fed(client: Client, message: Message): + chat = message.chat + user = message.from_user + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can promote people!") + return + + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("You need to specify a user to promote.") + return + + res = sql.user_join_fed(fed_id, user_id) + if res: + await message.reply_text("Successfully promoted user to federation admin!") + else: + await message.reply_text("User is already a federation admin!") + +@pbot.on_message(filters.command("fdem")) +async def user_demote_fed(client: Client, message: Message): + chat = message.chat + user = message.from_user + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can demote people!") + return + + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("You need to specify a user to demote.") + return + + if str(info["owner"]) == str(user_id): + await message.reply_text("You cannot demote the owner!") + return + + res = sql.user_demote_fed(fed_id, user_id) + if res: + await message.reply_text("Successfully demoted user from federation admin!") + else: + await message.reply_text("User is not a federation admin!") + +@pbot.on_message(filters.command("fedinfo")) +async def fed_info(client: Client, message: Message): + args = message.command[1:] + if not args: + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("Please provide a federation ID or use this in a federated group.") + return + else: + fed_id = args[0] + + info = sql.get_fed_info(fed_id) + if not info: + await message.reply_text("Invalid federation ID!") + return + + numban = sql.get_all_fban_users(fed_id) + numchat = sql.get_all_fed_chats(fed_id) + + text = ( + f"**Federation Information:**\n" + f"**Name:** {info['fname']}\n" + f"**ID:** `{fed_id}`\n" + f"**Owner:** {info['owner']}\n" + f"**Admins:** {len(sql.all_fed_admins(fed_id))}\n" + f"**Banned Users:** {len(numban)}\n" + f"**Connected Chats:** {len(numchat)}" + ) + await message.reply_text(text) + +@pbot.on_message(filters.command("fedadmins")) +async def fed_admin(client: Client, message: Message): + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + admins = sql.all_fed_admins(fed_id) + + text = f"**Admins in {info['fname']}:**\n" + text += f"- [{info['owner']}](tg://user?id={info['owner']}) (Owner)\n" + for admin in admins: + text += f"- [{admin['user_id']}](tg://user?id={admin['user_id']})\n" + + await message.reply_text(text) + +@pbot.on_message(filters.command("fban") & filters.group) +@DisableAbleCommandHandler("fban") +async def fed_ban(client: Client, message: Message): + chat = message.chat + user = message.from_user + args = message.command[1:] + + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + await message.reply_text("This group is not a part of any federation!") + return + + info = sql.get_fed_info(fed_id) + getfednotif = sql.user_feds_report(info["owner"]) + + # Check if user is fed admin + if not sql.is_user_fed_admin(fed_id, user.id): + await message.reply_text("Only federation admins can do this!") + return + + user_id, reason = await extract_unt_fedban(message, args) + if not user_id: + await message.reply_text("You don't seem to be referring to a user") + return + + me = await client.get_me() + if user_id == me.id: + await message.reply_text("I cannot fban myself!") + return + + if sql.is_user_fed_owner(fed_id, user_id): + await message.reply_text("He is the federation owner!") + return + + if sql.is_user_fed_admin(fed_id, user_id): + await message.reply_text("He is a federation admin!") + return + + if user_id == OWNER_ID or user_id in DRAGONS or user_id in TIGERS or user_id in WOLVES: + await message.reply_text("This user cannot be fed banned!") + return + + try: + user_chat = await client.get_users(user_id) + fban_user_id = user_chat.id + fban_user_name = user_chat.first_name + fban_user_lname = user_chat.last_name + fban_user_uname = user_chat.username + except Exception: + fban_user_id = int(user_id) + fban_user_name = f"user({user_id})" + fban_user_lname = None + fban_user_uname = None + + user_target = f"[{fban_user_name}](tg://user?id={fban_user_id})" + + fban, fbanreason, fbantime = sql.get_fban_user(fed_id, fban_user_id) + if fban: + sql.un_fban_user(fed_id, fban_user_id) + + x = sql.fban_user( + fed_id, + fban_user_id, + fban_user_name, + fban_user_lname, + fban_user_uname, + reason or "No reason provided", + int(time.time()), + ) + if not x: + await message.reply_text("Failed to ban from the federation!") + return + + reply_text = ( + "**New FedBan**\n" + f"**Federation:** {info['fname']}\n" + f"**Federation Admin:** {user.mention}\n" + f"**User:** {user_target}\n" + f"**User ID:** `{fban_user_id}`\n" + f"**Reason:** {reason or 'No reason provided'}" + ) + await message.reply_text(reply_text) + + get_fedlog = sql.get_fed_log(fed_id) + if get_fedlog: + try: + await client.send_message(get_fedlog, reply_text) + except Exception: + pass + + fed_chats = sql.all_fed_chats(fed_id) + for fchat in fed_chats: + try: + await client.ban_chat_member(fchat, fban_user_id) + except Exception: + pass + +@pbot.on_message(filters.command("unfban") & filters.group) +@DisableAbleCommandHandler("unfban") +async def unfban(client: Client, message: Message): + chat = message.chat + user = message.from_user + args = message.command[1:] + + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + await message.reply_text("This group is not a part of any federation!") + return + + if not sql.is_user_fed_admin(fed_id, user.id): + await message.reply_text("Only federation admins can do this!") + return + + user_id = await extract_user(message, args) + if not user_id: + await message.reply_text("You don't seem to be referring to a user") + return + + fban, fbanreason, fbantime = sql.get_fban_user(fed_id, user_id) + if not fban: + await message.reply_text("This user is not fedanned!") + return + + sql.un_fban_user(fed_id, user_id) + await message.reply_text(f"Successfully un-fedbanned user.") + + fed_chats = sql.all_fed_chats(fed_id) + for fchat in fed_chats: + try: + await client.unban_chat_member(fchat, user_id) + except Exception: + pass + +@pbot.on_message(filters.command("setfrules")) +async def set_frules(client: Client, message: Message): + chat = message.chat + user = message.from_user + fed_id = sql.get_fed_id(chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can set rules!") + return + + rules = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + sql.set_frules(fed_id, rules) + await message.reply_text("Successfully set federation rules!") + +@pbot.on_message(filters.command("frules")) +async def get_frules(client: Client, message: Message): + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + rules = sql.get_frules(fed_id) + if not rules: + await message.reply_text("This federation has no rules.") + return + + await message.reply_text(f"**Federation Rules:**\n\n{rules}") + +@pbot.on_message(filters.command("fbroadcast")) +async def fed_broadcast(client: Client, message: Message): + user = message.from_user + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can broadcast!") + return + + text = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + if not text: + await message.reply_text("What do you want to broadcast?") + return + + chats = sql.all_fed_chats(fed_id) + for chat_id in chats: + try: + await client.send_message(chat_id, text) + except Exception: + pass + await message.reply_text("Broadcast sent!") + +@pbot.on_message(filters.command("fbanlist")) +async def fed_ban_list(client: Client, message: Message): + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + bans = sql.get_all_fban_users(fed_id) + if not bans: + await message.reply_text("No users are federation-banned.") + return + + text = "**Banned users in this federation:**\n" + for ban in bans: + text += f"- `{ban['user_id']}`: {ban['reason']}\n" + + # In a real bot, you'd want to handle long list splitting here. + await message.reply_text(text[:4000]) + +@pbot.on_message(filters.command("fednotif")) +async def fed_notif(client: Client, message: Message): + user = message.from_user + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can toggle notifications!") + return + + args = message.command[1:] + if not args: + await message.reply_text("Use `on` or `off`.") + return + + if args[0].lower() in ("on", "yes", "true"): + sql.set_user_feds_report(user.id, True) + await message.reply_text("Notifications turned on!") + elif args[0].lower() in ("off", "no", "false"): + sql.set_user_feds_report(user.id, False) + await message.reply_text("Notifications turned off!") + else: + await message.reply_text("Invalid option!") + +@pbot.on_message(filters.command("fedchats")) +async def fed_chats(client: Client, message: Message): + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + chats = sql.all_fed_chats(fed_id) + text = f"**Chats in this federation:**\n" + for chat_id in chats: + try: + c = await client.get_chat(chat_id) + text += f"- {c.title} (`{chat_id}`)\n" + except: + text += f"- `{chat_id}`\n" + + await message.reply_text(text[:4000]) + +@pbot.on_callback_query(filters.regex(r"^rm_fed_")) +async def del_fed_button(client: Client, query: CallbackQuery): + fed_id = query.data.split("_")[-1] + user = query.from_user + info = sql.get_fed_info(fed_id) + + if str(info["owner"]) != str(user.id): + await query.answer("You are not the owner of this federation!", show_alert=True) + return + + sql.del_fed(fed_id) + await query.message.edit_text(f"Federation {info['fname']} has been deleted.") + +@pbot.on_message(filters.command("fstat")) +async def fed_stat_user(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) + if not user_id: + user_id = message.from_user.id + + feds = sql.get_user_fban_list(user_id) + if not feds: + await message.reply_text("This user has no federation bans.") + return + + text = f"**Federation bans for this user:**\n" + for fed in feds: + info = sql.get_fed_info(fed['fed_id']) + text += f"- {info['fname']}: {fed['reason']}\n" + await message.reply_text(text) + +@pbot.on_message(filters.command("setfedlog")) +async def set_fed_log(client: Client, message: Message): + user = message.from_user + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can set log channel!") + return + + sql.set_fed_log(fed_id, message.chat.id) + await message.reply_text("Successfully set this chat as federation log channel!") + +@pbot.on_message(filters.command("unsetfedlog")) +async def unset_fed_log(client: Client, message: Message): + user = message.from_user + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can unset log channel!") + return + + sql.unset_fed_log(fed_id) + await message.reply_text("Successfully unset federation log channel!") + +@pbot.on_message(filters.command("subfed")) +async def subs_feds(client: Client, message: Message): + user = message.from_user + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can subscribe!") + return + + args = message.command[1:] + if not args: + await message.reply_text("Please provide a federation ID to subscribe to.") + return + + target_fed_id = args[0] + if target_fed_id == fed_id: + await message.reply_text("You cannot subscribe to your own federation!") + return + + res = sql.subs_fed(fed_id, target_fed_id) + if res: + await message.reply_text("Successfully subscribed to federation!") + else: + await message.reply_text("Already subscribed or invalid ID.") + +@pbot.on_message(filters.command("unsubfed")) +async def unsubs_feds(client: Client, message: Message): + user = message.from_user + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + info = sql.get_fed_info(fed_id) + if str(info["owner"]) != str(user.id): + await message.reply_text("Only the federation owner can unsubscribe!") + return + + args = message.command[1:] + if not args: + await message.reply_text("Please provide a federation ID to unsubscribe from.") + return + + target_fed_id = args[0] + res = sql.unsubs_fed(fed_id, target_fed_id) + if res: + await message.reply_text("Successfully unsubscribed from federation!") + else: + await message.reply_text("Not subscribed to this federation.") + +@pbot.on_message(filters.command("fedsubs")) +async def get_myfedsubs(client: Client, message: Message): + fed_id = sql.get_fed_id(message.chat.id) + if not fed_id: + await message.reply_text("This group is not part of any federation!") + return + + subs = sql.get_subscriber(fed_id) + if not subs: + await message.reply_text("You have no subscribed federations.") + return + + text = "**Subscribed federations:**\n" + for sub in subs: + text += f"- `{sub}`\n" + await message.reply_text(text) + +@pbot.on_message(filters.command("myfeds")) +async def get_myfeds_list(client: Client, message: Message): + user = message.from_user + feds = sql.get_user_owner_fed_full(user.id) + if not feds: + await message.reply_text("You do not own any federations.") + return + + text = "**Your federations:**\n" + for fed in feds: + text += f"- {fed['fname']} (`{fed['fed_id']}`)\n" + await message.reply_text(text) + +@pbot.on_message(filters.command("fedownerhelp")) +async def fed_owner_help(client: Client, message: Message): + await message.reply_text( + "**Federation Owner Help:**\n" + "• `/newfed <name>`: Create a new federation\n" + "• `/delfed <id>`: Delete a federation\n" + "• `/fedname <new name>`: Rename a federation\n" + "• `/fprom <user>`: Promote a user to fed admin\n" + "• `/fdem <user>`: Demote a user from fed admin\n" + "• `/setfrules <rules>`: Set federation rules\n" + "• `/fbroadcast <message>`: Broadcast a message to all fed chats\n" + "• `/setfedlog`: Set current chat as fed log\n" + "• `/unsetfedlog`: Unset fed log\n" + "• `/subfed <id>`: Subscribe to another federation\n" + "• `/unsubfed <id>`: Unsubscribe from another federation\n" + "• `/fednotif <on/off>`: Toggle fed notifications" + ) + +@pbot.on_message(filters.command("fedadminhelp")) +async def fed_admin_help(client: Client, message: Message): + await message.reply_text( + "**Federation Admin Help:**\n" + "• `/fban <user> [reason]`: Ban a user from the federation\n" + "• `/unfban <user>`: Unban a user from the federation\n" + "• `/fstat <user>`: Check a user's fed ban status\n" + "• `/fedinfo <id>`: Get info about a federation\n" + "• `/fedadmins`: List all fed admins\n" + "• `/fedchats`: List all chats in the federation\n" + "• `/fbanlist`: List all banned users" + ) + +@pbot.on_message(filters.command("feduserhelp")) +async def fed_user_help(client: Client, message: Message): + await message.reply_text( + "**Federation User Help:**\n" + "• `/fstat`: Check your own fed ban status\n" + "• `/frules`: View the rules of the current federation" + ) + +__mod_name__ = "Fed" +__help__ = """ +Federations allow you to sync bans across multiple groups. +One ban in the federation affects all chats connected to it. + +**Commands:** +• `/fedownerhelp`: Help for Fed owners +• `/fedadminhelp`: Help for Fed admins +• `/feduserhelp`: Help for all users +""" diff --git a/QueenNoxi/modules/figlet.py b/QueenNoxi/modules/figlet.py new file mode 100644 index 0000000000000000000000000000000000000000..db88f0586e0b3a2d31bf98b36014240ec8ac621a --- /dev/null +++ b/QueenNoxi/modules/figlet.py @@ -0,0 +1,35 @@ +from pyrogram import filters +import asyncio +import pyfiglet +from random import choice +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message, CallbackQuery +from pyrogram.handlers import MessageHandler +from .. import pbot as Client +def figle(text): + x = pyfiglet.FigletFont.getFonts() + font = choice(x) + figled = str(pyfiglet.figlet_format(text,font=font)) + keyboard = InlineKeyboardMarkup([[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ", callback_data="figlet"),InlineKeyboardButton(text="ᴄʟᴏsᴇ", callback_data="close_reply")]]) + return figled, keyboard + +@Client.on_message(filters.command("figlet")) +async def echo(bot, message): + global text + try: + text = message.text.split(' ',1)[1] + except IndexError: + return await message.reply_text("Example:\n\n`/figlet QueenNoxi`") + kul_text, keyboard = figle(text) + await message.reply_text(f"ʜᴇʀᴇ ɪs ʏᴏᴜʀ ғɪɢʟᴇᴛ :\n<pre>{kul_text}</pre>", quote=True, reply_markup=keyboard) + +@Client.on_callback_query(filters.regex("figlet")) +async def figlet_handler(Client, query: CallbackQuery): + try: + kul_text, keyboard = figle(text) + await query.message.edit_text(f"ʜᴇʀᴇ ɪs ʏᴏᴜʀ ғɪɢʟᴇᴛ :\n<pre>{kul_text}</pre>", reply_markup=keyboard) + except Exception as e : + await message.reply(e) +__mod_name__ = "Fɪɢʟᴇᴛ" +__help__=""" +❍ /figlet*:* ᴍᴀᴋᴇs ғɪɢʟᴇᴛ ᴏғ ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ +Example:\n\n`/figlet QueenNoxi`""" diff --git a/QueenNoxi/modules/flood.py b/QueenNoxi/modules/flood.py new file mode 100644 index 0000000000000000000000000000000000000000..ea9eb835d1eea542cf0ed182df70e8d51fab92f2 --- /dev/null +++ b/QueenNoxi/modules/flood.py @@ -0,0 +1,131 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import Message, ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery + +import QueenNoxi.modules.sql.antiflood_sql as sql +from QueenNoxi.modules.sql.approve_sql import is_approved +from QueenNoxi import TIGERS, WOLVES, pbot, LOGGER +from QueenNoxi.modules.helper_funcs.chat_status import bot_admin, is_user_admin, user_admin, user_admin_no_reply +from QueenNoxi.modules.helper_funcs.string_handling import extract_time +from QueenNoxi.modules.connection import connected +from QueenNoxi.modules.log_channel import loggable + +FLOOD_GROUP = 3 + +@pbot.on_message(filters.group & ~filters.service, group=FLOOD_GROUP) +async def check_flood(client: Client, message: Message): + user = message.from_user + chat = message.chat + if not user: return + + # Admin/Whitelist/Approved ignore + if await is_user_admin(chat, user.id) or user.id in WOLVES or user.id in TIGERS: + sql.update_flood(chat.id, None) + return + + if is_approved(chat.id, user.id): + sql.update_flood(chat.id, None) + return + + should_ban = sql.update_flood(chat.id, user.id) + if not should_ban: return + + try: + getmode, getvalue = sql.get_flood_setting(chat.id) + msg_text = "" + + if getmode == 1: # Ban + await chat.ban_member(user.id) + msg_text = f"Banned {user.mention} for flooding!" + elif getmode == 2: # Kick + await chat.unban_member(user.id) + msg_text = f"Kicked {user.mention} for flooding!" + elif getmode == 3: # Mute + await chat.restrict_member(user.id, ChatPermissions(can_send_messages=False)) + msg_text = f"Muted {user.mention} for flooding!" + elif getmode == 4: # Tban + bantime = await extract_time(message, getvalue) + await chat.ban_member(user.id, until_date=bantime) + msg_text = f"Banned {user.mention} for {getvalue} (flooding)!" + elif getmode == 5: # Tmute + mutetime = await extract_time(message, getvalue) + await chat.restrict_member(user.id, ChatPermissions(can_send_messages=False), until_date=mutetime) + msg_text = f"Muted {user.mention} for {getvalue} (flooding)!" + + if msg_text: + await message.reply_text(msg_text) + + except Exception as e: + LOGGER.warning(f"Error in antiflood: {e}") + +@pbot.on_message(filters.command("setflood") & filters.group) +@user_admin +async def set_flood(client: Client, message: Message): + chat = message.chat + args = message.command[1:] + + if not args: + limit = sql.get_flood_limit(chat.id) + await message.reply_text(f"Current flood limit: {limit if limit > 0 else 'Disabled'}") + return + + val = args[0].lower() + if val in ("off", "no", "0"): + sql.set_flood(chat.id, 0) + await message.reply_text("Antiflood disabled.") + elif val.isdigit(): + limit = int(val) + if limit < 3 and limit != 0: + await message.reply_text("Flood limit must be 0 or greater than 3.") + return + sql.set_flood(chat.id, limit) + await message.reply_text(f"Flood limit updated to {limit}.") + else: + await message.reply_text("Use `/setflood <number>` or `/setflood off`.") + +@pbot.on_message(filters.command("flood") & filters.group) +async def flood(client: Client, message: Message): + limit = sql.get_flood_limit(message.chat.id) + if limit == 0: + await message.reply_text("Antiflood is not enabled here.") + else: + await message.reply_text(f"Antiflood is currently set to {limit} consecutive messages.") + +@pbot.on_message(filters.command("setfloodmode") & filters.group) +@user_admin +async def set_flood_mode(client: Client, message: Message): + chat = message.chat + args = message.command[1:] + + if not args: + mode, val = sql.get_flood_setting(chat.id) + modes = ["Off", "Ban", "Kick", "Mute", "Tban", "Tmute"] + await message.reply_text(f"Current flood mode: {modes[mode]} {f'({val})' if mode > 3 else ''}") + return + + mode = args[0].lower() + if mode == "ban": + sql.set_flood_strength(chat.id, 1, "0") + await message.reply_text("Flood mode: BAN.") + elif mode == "kick": + sql.set_flood_strength(chat.id, 2, "0") + await message.reply_text("Flood mode: KICK.") + elif mode == "mute": + sql.set_flood_strength(chat.id, 3, "0") + await message.reply_text("Flood mode: MUTE.") + elif mode == "tban": + if len(args) < 2: + await message.reply_text("Specify time (e.g., 5m, 1h).") + return + sql.set_flood_strength(chat.id, 4, args[1]) + await message.reply_text(f"Flood mode: TBAN for {args[1]}.") + elif mode == "tmute": + if len(args) < 2: + await message.reply_text("Specify time (e.g., 5m, 1h).") + return + sql.set_flood_strength(chat.id, 5, args[1]) + await message.reply_text(f"Flood mode: TMUTE for {args[1]}.") + else: + await message.reply_text("Use: ban/kick/mute/tban/tmute") + +__mod_name__ = "Fʟᴏᴏᴅ" diff --git a/QueenNoxi/modules/fonts.py b/QueenNoxi/modules/fonts.py new file mode 100644 index 0000000000000000000000000000000000000000..e81ef7536042efeb46ae8c227cec880207082c4e --- /dev/null +++ b/QueenNoxi/modules/fonts.py @@ -0,0 +1,202 @@ +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup + +from QueenNoxi.utils.fonts import Fonts +from QueenNoxi import pbot + +@pbot.on_message(filters.command(["font", "fonts"])) +async def style_buttons(c, m, cb=False): + text = m.text.split(' ',1)[1] + buttons = [ + [ + InlineKeyboardButton("𝚃𝚢𝚙𝚎𝚠𝚛𝚒𝚝𝚎𝚛", callback_data="style+typewriter"), + InlineKeyboardButton("𝕆𝕦𝕥𝕝𝕚𝕟𝕖", callback_data="style+outline"), + InlineKeyboardButton("𝐒𝐞𝐫𝐢𝐟", callback_data="style+serif"), + ], + [ + InlineKeyboardButton("𝑺𝒆𝒓𝒊𝒇", callback_data="style+bold_cool"), + InlineKeyboardButton("𝑆𝑒𝑟𝑖𝑓", callback_data="style+cool"), + InlineKeyboardButton("Sᴍᴀʟʟ Cᴀᴘs", callback_data="style+small_cap"), + ], + [ + InlineKeyboardButton("𝓈𝒸𝓇𝒾𝓅𝓉", callback_data="style+script"), + InlineKeyboardButton("𝓼𝓬𝓻𝓲𝓹𝓽", callback_data="style+script_bolt"), + InlineKeyboardButton("ᵗⁱⁿʸ", callback_data="style+tiny"), + ], + [ + InlineKeyboardButton("ᑕOᗰIᑕ", callback_data="style+comic"), + InlineKeyboardButton("𝗦𝗮𝗻𝘀", callback_data="style+sans"), + InlineKeyboardButton("𝙎𝙖𝙣𝙨", callback_data="style+slant_sans"), + ], + [ + InlineKeyboardButton("𝘚𝘢𝘯𝘴", callback_data="style+slant"), + InlineKeyboardButton("𝖲𝖺𝗇𝗌", callback_data="style+sim"), + InlineKeyboardButton("Ⓒ︎Ⓘ︎Ⓡ︎Ⓒ︎Ⓛ︎Ⓔ︎Ⓢ︎", callback_data="style+circles"), + ], + [ + InlineKeyboardButton("🅒︎🅘︎🅡︎🅒︎🅛︎🅔︎🅢︎", callback_data="style+circle_dark"), + InlineKeyboardButton("𝔊𝔬𝔱𝔥𝔦𝔠", callback_data="style+gothic"), + InlineKeyboardButton("𝕲𝖔𝖙𝖍𝖎𝖈", callback_data="style+gothic_bolt"), + ], + [ + InlineKeyboardButton("C͜͡l͜͡o͜͡u͜͡d͜͡s͜͡", callback_data="style+cloud"), + InlineKeyboardButton("H̆̈ă̈p̆̈p̆̈y̆̈", callback_data="style+happy"), + InlineKeyboardButton("S̑̈ȃ̈d̑̈", callback_data="style+sad"), + ], + [InlineKeyboardButton ("ᴄʟᴏsᴇ",callback_data="close_reply"),InlineKeyboardButton ("ɴᴇxᴛ ➻", callback_data="nxt")], + ] + if not cb: + await m.reply_text( + f"`{text}`", reply_markup=InlineKeyboardMarkup(buttons), quote=True + ) + else: + await m.answer() + await m.message.edit_reply_markup(InlineKeyboardMarkup(buttons)) + + +@pbot.on_callback_query(filters.regex("^nxt")) +async def nxt(c, m): + if m.data == "nxt": + buttons = [ + [ + InlineKeyboardButton("🇸 🇵 🇪 🇨 🇮 🇦 🇱 ", callback_data="style+special"), + InlineKeyboardButton("🅂🅀🅄🄰🅁🄴🅂", callback_data="style+squares"), + InlineKeyboardButton( + "🆂︎🆀︎🆄︎🅰︎🆁︎🅴︎🆂︎", callback_data="style+squares_bold" + ), + ], + [ + InlineKeyboardButton("ꪖꪀᦔꪖꪶꪊᥴ𝓲ꪖ", callback_data="style+andalucia"), + InlineKeyboardButton("爪卂几ᘜ卂", callback_data="style+manga"), + InlineKeyboardButton("S̾t̾i̾n̾k̾y̾", callback_data="style+stinky"), + ], + [ + InlineKeyboardButton( + "B̥ͦu̥ͦb̥ͦb̥ͦl̥ͦe̥ͦs̥ͦ", callback_data="style+bubbles" + ), + InlineKeyboardButton( + "U͟n͟d͟e͟r͟l͟i͟n͟e͟", callback_data="style+underline" + ), + InlineKeyboardButton("꒒ꍏꀷꌩꌃꀎꁅ", callback_data="style+ladybug"), + ], + [ + InlineKeyboardButton("R҉a҉y҉s҉", callback_data="style+rays"), + InlineKeyboardButton("B҈i҈r҈d҈s҈", callback_data="style+birds"), + InlineKeyboardButton("S̸l̸a̸s̸h̸", callback_data="style+slash"), + ], + [ + InlineKeyboardButton("s⃠t⃠o⃠p⃠", callback_data="style+stop"), + InlineKeyboardButton( + "S̺͆k̺͆y̺͆l̺͆i̺͆n̺͆e̺͆", callback_data="style+skyline" + ), + InlineKeyboardButton("A͎r͎r͎o͎w͎s͎", callback_data="style+arrows"), + ], + [ + InlineKeyboardButton("ዪሀክቿነ", callback_data="style+qvnes"), + InlineKeyboardButton("S̶t̶r̶i̶k̶e̶", callback_data="style+strike"), + InlineKeyboardButton("F༙r༙o༙z༙e༙n༙", callback_data="style+frozen"), + ], + [InlineKeyboardButton ("ᴄʟᴏsᴇ",callback_data="close_reply"),InlineKeyboardButton ("ʙᴀᴄᴋ", callback_data="nxt+0")], + ] + await m.answer() + await m.message.edit_reply_markup(InlineKeyboardMarkup(buttons)) + else: + await style_buttons(c, m, cb=True) + + +@pbot.on_callback_query(filters.regex("^style")) +async def style(c, m): + await m.answer() + cmd,style = m.data.split('+') + if style == "typewriter": + cls = Fonts.typewriter + if style == "outline": + cls = Fonts.outline + if style == "serif": + cls = Fonts.serief + if style == "bold_cool": + cls = Fonts.bold_cool + if style == "cool": + cls = Fonts.cool + if style == "small_cap": + cls = Fonts.smallcap + if style == "script": + cls = Fonts.script + if style == "script_bolt": + cls = Fonts.bold_script + if style == "tiny": + cls = Fonts.tiny + if style == "comic": + cls = Fonts.comic + if style == "sans": + cls = Fonts.san + if style == "slant_sans": + cls = Fonts.slant_san + if style == "slant": + cls = Fonts.slant + if style == "sim": + cls = Fonts.sim + if style == "circles": + cls = Fonts.circles + if style == "circle_dark": + cls = Fonts.dark_circle + if style == "gothic": + cls = Fonts.gothic + if style == "gothic_bolt": + cls = Fonts.bold_gothic + if style == "cloud": + cls = Fonts.cloud + if style == "happy": + cls = Fonts.happy + if style == "sad": + cls = Fonts.sad + if style == "special": + cls = Fonts.special + if style == "squares": + cls = Fonts.square + if style == "squares_bold": + cls = Fonts.dark_square + if style == "andalucia": + cls = Fonts.andalucia + if style == "manga": + cls = Fonts.manga + if style == "stinky": + cls = Fonts.stinky + if style == "bubbles": + cls = Fonts.bubbles + if style == "underline": + cls = Fonts.underline + if style == "ladybug": + cls = Fonts.ladybug + if style == "rays": + cls = Fonts.rays + if style == "birds": + cls = Fonts.birds + if style == "slash": + cls = Fonts.slash + if style == "stop": + cls = Fonts.stop + if style == "skyline": + cls = Fonts.skyline + if style == "arrows": + cls = Fonts.arrows + if style == "qvnes": + cls = Fonts.rvnes + if style == "strike": + cls = Fonts.strike + if style == "frozen": + cls = Fonts.frozen + #text = m.text.split(' ',1)[1] + new_text = cls(m.message.reply_to_message.text.split(" ",1)[1]) + try: + await m.message.edit_text(new_text, reply_markup=m.message.reply_markup) + except: + pass + + +__help__ = """ + + ❍ /fonts <text> *:* ᴄᴏɴᴠᴇʀᴛs sɪᴍᴩʟᴇ ᴛᴇxᴛ ᴛᴏ ʙᴇᴀᴜᴛɪғᴜʟ ᴛᴇxᴛ ʙʏ ᴄʜᴀɴɢɪɴɢ ɪᴛ's ғᴏɴᴛ. + """ + +__mod_name__ = "Fᴏɴᴛ" diff --git a/QueenNoxi/modules/fsub.py b/QueenNoxi/modules/fsub.py new file mode 100644 index 0000000000000000000000000000000000000000..1445f6145dc9e2a41eed09fd49fa18a77d4fc666 --- /dev/null +++ b/QueenNoxi/modules/fsub.py @@ -0,0 +1,144 @@ +from pyrogram import filters +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ChatPermissions +from pyrogram.errors import UserNotParticipant, ChatAdminRequired +from QueenNoxi import pbot as app, BOT_ID, DRAGONS as DEVS, OWNER_ID +from QueenNoxi.modules.no_sql import fsub_db as db + +async def check_admin(chat_id, user_id): + try: + member = await app.get_chat_member(chat_id, user_id) + if member.privileges: + return True + except Exception: + pass + return False + +@app.on_message(filters.command(["fsub", "forcesubscribe", "Forcesubscribe", "Forcesub"])) +async def fsub_cmd(_, message): + if message.chat.type.name == "PRIVATE": + return + try: + member = await app.get_chat_member(message.chat.id, message.from_user.id) + except Exception: + return + + if not member.privileges: + return await message.reply("ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ʙᴇ ᴀɴ ᴀᴅᴍɪɴ ᴛᴏ ᴅᴏ ᴛʜɪs.") + if member.status.name != "OWNER": + return await message.reply("❗ <b>ɢʀᴏᴜᴘ ᴄʀᴇᴀᴛᴏʀ ʀᴇǫᴜɪʀᴇᴅ</b> \n<i>ʏᴏᴜ ʜᴀᴠᴇ ᴛᴏ ʙᴇ ᴛʜᴇ ɢʀᴏᴜᴘ ᴄʀᴇᴀᴛᴏʀ ᴛᴏ ᴅᴏ ᴛʜᴀᴛ.</i>", parse_mode="html") + + channel = message.text.split(None, 1)[1] if len(message.command) > 1 else None + if not channel: + chat_db = db.fs_settings(message.chat.id) + if not chat_db: + await message.reply("<b>❌ ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴅɪsᴀʙʟᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ.</b>") + else: + await message.reply(f"ғᴏʀᴄᴇsᴜʙsᴄʀɪʙᴇ ɪs ᴄᴜʀʀᴇɴᴛʟʏ <b>ᴇɴᴀʙʟᴇᴅ</b>. ᴜsᴇʀs ᴀʀᴇ ғᴏʀᴄᴇᴅ ᴛᴏ ᴊᴏɪɴ <b>@{chat_db.channel}</b> ᴛᴏ sᴘᴇᴀᴋ ʜᴇʀᴇ.") + elif channel in ["on", "yes", "y"]: + await message.reply("❗ᴘʟᴇᴀsᴇ sᴘᴇᴄɪғʏ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ ᴜsᴇʀɴᴀᴍᴇ.") + elif channel in ["off", "no", "n"]: + await message.reply("**❌ ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴅɪsᴀʙʟᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ.**") + db.disapprove(message.chat.id) + else: + try: + channel_entity = await app.get_chat(channel) + if channel_entity.type.name != "CHANNEL": + return await message.reply("ᴛʜᴀᴛ's ɴᴏᴛ ᴀ ᴠᴀʟɪᴅ ᴄʜᴀɴɴᴇʟ.") + except Exception: + return await message.reply("❗<b>ɪɴᴠᴀʟɪᴅ ᴄʜᴀɴɴᴇʟ ᴜsᴇʀɴᴀᴍᴇ ᴘʀᴏᴠɪᴅᴇᴅ.</b>") + + channel_username = channel_entity.username + try: + member = await app.get_chat_member(channel_entity.id, app.me.id) + if not member.privileges: + return await message.reply(f"❗**ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ**\nI ᴀᴍ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ɪɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ. ᴀᴅᴅ ᴍᴇ ᴀs ᴀ ᴀᴅᴍɪɴ ɪɴ ᴏʀᴅᴇʀ ᴛᴏ ᴇɴᴀʙʟᴇ ғᴏʀᴄᴇsᴜʙsᴄʀɪʙᴇ.") + except Exception: + return await message.reply("I am not in that channel.") + + db.add_channel(message.chat.id, str(channel_username)) + await message.reply(f"✅ **ғᴏʀᴄᴇ sᴜʙsᴄʀɪʙᴇ ɪs ᴇɴᴀʙʟᴇᴅ** to @{channel_username}.") + +@app.on_message(~filters.private, group=10) +async def fsub_n(_, message): + if not db.fs_settings(message.chat.id): + return + if getattr(message, "forward_from", None) or getattr(message, "sender_chat", None): + return + if not message.from_user: + return + + try: + bot_member = await app.get_chat_member(message.chat.id, app.me.id) + if not bot_member.privileges or not bot_member.privileges.can_restrict_members: + return + except Exception: + return + + if await check_admin(message.chat.id, message.from_user.id) or message.from_user.id in DEVS or message.from_user.id == OWNER_ID: + return + + channel = (db.fs_settings(message.chat.id)).get("channel") + try: + await app.get_chat_member(channel, message.from_user.id) + check = True + except UserNotParticipant: + check = False + except Exception: + check = True + + if not check: + buttons = InlineKeyboardMarkup([ + [InlineKeyboardButton("ᴊᴏɪɴ ᴄʜᴀɴɴᴇʟ", url=f"https://t.me/{channel}")], + [InlineKeyboardButton("ᴜɴᴍᴜᴛᴇ ᴍᴇ", callback_data=f"fs_{message.from_user.id}")] + ]) + txt = f'<b><a href="tg://user?id={message.from_user.id}">{message.from_user.first_name}</a></b>, ʏᴏᴜ ʜᴀᴠᴇ <b>ɴᴏᴛ sᴜʙsᴄʀɪʙᴇᴅ</b> ᴛᴏ ᴏᴜʀ <b><a href="t.me/{channel}">ᴄʜᴀɴɴᴇʟ</a></b> ʏᴇᴛ❗.ᴘʟᴇᴀsᴇ <b><a href="t.me/{channel}">ᴊᴏɪɴ</a></b> ᴀɴᴅ <b>ᴘʀᴇss ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ</b> ᴛᴏ ᴜɴᴍᴜᴛᴇ ʏᴏᴜʀsᴇʟғ.' + await message.reply(txt, reply_markup=buttons, disable_web_page_preview=True) + try: + await app.restrict_chat_member(message.chat.id, message.from_user.id, ChatPermissions(can_send_messages=False)) + except Exception: + pass + +@app.on_callback_query(filters.regex(r"^fs\_(\d+)")) +async def unmute_fsub(_, query): + user_id = int(query.matches[0].group(1)) + if query.from_user.id != user_id: + return await query.answer("ᴛʜɪs ɪs ɴᴏᴛ ᴍᴇᴀɴᴛ ғᴏʀ ʏᴏᴜ.", show_alert=True) + + channel = (db.fs_settings(query.message.chat.id)).get("channel") + try: + await app.get_chat_member(channel, user_id) + check = True + except UserNotParticipant: + check = False + except Exception: + check = True + + if not check: + return await query.answer("ʏᴏᴜ ʜᴀᴠᴇ ᴛᴏ ᴊᴏɪɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ ғɪʀsᴛ, ᴛᴏ ɢᴇᴛ ᴜɴᴍᴜᴛᴇᴅ!", show_alert=True) + + try: + await app.restrict_chat_member(query.message.chat.id, user_id, ChatPermissions( + can_send_messages=True, can_send_media_messages=True, can_send_other_messages=True, can_add_web_page_previews=True + )) + except Exception: + pass + await query.message.delete() + +__mod_name__ = "F-sᴜʙ" + +__help__=""" +*ғᴏʀᴄᴇ ꜱᴜʙꜱᴄʀɪʙᴇ:* + + •➥ *ᴍᴜᴋᴇsʜʀᴏʙᴏᴛ ᴄᴀɴ ᴍᴜᴛᴇ ᴍᴇᴍʙᴇʀꜱ ᴡʜᴏ ᴀʀᴇ ɴᴏᴛ ꜱᴜʙꜱᴄʀɪʙᴇᴅ ʏᴏᴜʀ ᴄʜᴀɴɴᴇʟ ᴜɴᴛɪʟ ᴛʜᴇʏ ꜱᴜʙꜱᴄʀɪʙᴇ* + •➥ ᴡʜᴇɴ ᴇɴᴀʙʟᴇᴅ ɪ ᴡɪʟʟ ᴍᴜᴛᴇ ᴜɴꜱᴜʙꜱᴄʀɪʙᴇᴅ ᴍᴇᴍʙᴇʀꜱ ᴀɴᴅ ꜱʜᴏᴡ ᴛʜᴇᴍ ᴀ ᴜɴᴍᴜᴛᴇ ʙᴜᴛᴛᴏɴ. ᴡʜᴇɴ ᴛʜᴇʏ ᴘʀᴇꜱꜱᴇᴅ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ɪ ᴡɪʟʟ ᴜɴᴍᴜᴛᴇ ᴛʜᴇᴍ + + •➥ *ꜱᴇᴛᴜᴘ* + •➥ [ᴀᴅᴅ ᴍᴇ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴀꜱ ᴀᴅᴍɪɴ](https://t.me/groupcontrollertgbot?startgroup=new) + •➥ [ᴀᴅᴅ ᴍᴇ ɪɴ your ᴄʜᴀɴɴᴇʟ ᴀꜱ ᴀᴅᴍɪɴ](https://t.me/groupcontrollertgbot?startgroup=new) + + *ᴄᴏᴍᴍᴍᴀɴᴅꜱ* + •➥ /fsub channel username - ᴛᴏ ᴛᴜʀɴ ᴏɴ ᴀɴᴅ sᴇᴛᴜᴘ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ. + •➥ /fsub off - ᴛᴏ ᴛᴜʀɴ ᴏғ ғᴏʀᴄᴇꜱᴜʙꜱᴄʀɪʙᴇ.. + 💡 ɪғ ʏᴏᴜ ᴅɪꜱᴀʙʟᴇ ғꜱᴜʙ, ʏᴏᴜ ɴᴇᴇᴅ ᴛᴏ ꜱᴇᴛ ᴀɢᴀɪɴ ғᴏʀ ᴡᴏʀᴋɪɴɢ /fsub channel username + +""" diff --git a/QueenNoxi/modules/fun.py b/QueenNoxi/modules/fun.py new file mode 100644 index 0000000000000000000000000000000000000000..a816656a908bf94e3d7b30d95933b5bb0d61f11b --- /dev/null +++ b/QueenNoxi/modules/fun.py @@ -0,0 +1,116 @@ +import html +import random +import requests +import asyncio +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +import QueenNoxi.modules.fun_strings as fun_strings +from QueenNoxi import pbot, OWNER_ID +from QueenNoxi.modules.helper_funcs.extraction import extract_user + +@pbot.on_message(filters.command("runs")) +async def runs(client: Client, message: Message): + await message.reply_text(random.choice(fun_strings.RUN_STRINGS)) + +@pbot.on_message(filters.command("slap")) +async def slap(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) + curr_user = message.from_user.first_name + + if not user_id: + user1 = (await client.get_me()).first_name + user2 = curr_user + else: + user = await client.get_users(user_id) + user1 = curr_user + user2 = user.first_name + + temp = random.choice(fun_strings.SLAP_TEMPLATES) + item = random.choice(fun_strings.ITEMS) + hit = random.choice(fun_strings.HIT) + throw = random.choice(fun_strings.THROW) + + reply = temp.format(user1=html.escape(user1), user2=html.escape(user2), item=item, hits=hit, throws=throw) + await message.reply_text(reply) + +@pbot.on_message(filters.command("pat")) +async def pat(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) + curr_user = message.from_user.first_name + + if not user_id: + user1 = (await client.get_me()).first_name + user2 = curr_user + else: + user = await client.get_users(user_id) + user1 = curr_user + user2 = user.first_name + + temp = random.choice(fun_strings.PAT_TEMPLATES) + reply = temp.format(user1=html.escape(user1), user2=html.escape(user2)) + await message.reply_text(reply) + +@pbot.on_message(filters.command("roll")) +async def roll(client: Client, message: Message): + await message.reply_text(str(random.randint(1, 6))) + +@pbot.on_message(filters.command("toss")) +async def toss(client: Client, message: Message): + await message.reply_text(random.choice(fun_strings.TOSS)) + +@pbot.on_message(filters.command("shrug")) +async def shrug(client: Client, message: Message): + await message.reply_text(r"¯\_(ツ)_/¯") + +@pbot.on_message(filters.command("decide")) +async def decide(client: Client, message: Message): + await message.reply_text(random.choice(fun_strings.DECIDE)) + +@pbot.on_message(filters.command("8ball")) +async def eightball(client: Client, message: Message): + await message.reply_text(random.choice(fun_strings.EIGHTBALL)) + +@pbot.on_message(filters.command("kiss")) +async def kiss(client: Client, message: Message): + if not message.reply_to_message: + await message.reply_text("You need to reply to a user's message to kiss them!") + return + + sender = message.from_user + replied_user = message.reply_to_message.from_user + + if not replied_user: + await message.reply_text("You need to reply to a valid user to kiss them!") + return + + if sender.id == replied_user.id: + await message.reply_text("You can't kiss yourself, that's sad!") + return + + try: + req = requests.get("https://nekos.life/api/v2/img/kiss", timeout=5).json() + gif_url = req.get("url") + except Exception: + gif_url = "https://cdn.nekos.life/kiss/kiss_001.gif" + + caption = f"[{sender.first_name}](tg://user?id={sender.id}) gives a sweet kiss to [{replied_user.first_name}](tg://user?id={replied_user.id}) 💋" + + await message.reply_animation( + animation=gif_url, + caption=caption, + reply_to_message_id=message.id + ) + +__mod_name__ = "Fun" +__help__ = """ +• `/runs`: Random strings! +• `/slap`: Slap someone! +• `/pat`: Pat someone! +• `/roll`: Roll a dice! +• `/toss`: Toss a coin! +• `/shrug`: Shrug! +• `/decide`: Yes/No/Maybe? +• `/8ball`: Ask the 8-ball! +• `/kiss`: Give someone a sweet kiss! +""" diff --git a/QueenNoxi/modules/fun_strings.py b/QueenNoxi/modules/fun_strings.py new file mode 100644 index 0000000000000000000000000000000000000000..f8d9c87fcc117db09df80bb3ccfbffd134ad4d3e --- /dev/null +++ b/QueenNoxi/modules/fun_strings.py @@ -0,0 +1,442 @@ +RUN_STRINGS = ( + "Now you see me, now you don't." "ε=ε=ε=ε=┌(; ̄▽ ̄)┘", + "Get back here!", + "REEEEEEEEEEEEEEEEEE!!!!!!!", + "Look out for the wall!", + "Don't leave me alone with them!!", + "You've got company!", + "Chotto matte!", + "Yare yare daze", + "*Naruto run activated*", + "*Nezuko run activated*", + "Hey take responsibilty for what you just did!", + "May the odds be ever in your favour.", + "Run everyone, they just dropped a bomb 💣💣", + "And they disappeared forever, never to be seen again.", + "Legend has it, they're still running.", + "Hasta la vista, baby.", + "Ah, what a waste. I liked that one.", + "As The Doctor would say... RUN!", +) + +GIFS = [ + "CgACAgQAAx0CSVUvGgAC7KpfWxMrgGyQs-GUUJgt-TSO8cOIDgACaAgAAlZD0VHT3Zynpr5nGxsE", + "CgACAgUAAx0CU_rCTAABAjdgX1s4NVaeCls6YaH3p43vgdCRwQIAAqsAA4P_MFUYQhyoR-kgpRsE", + "CgACAgUAAx0CU_rCTAABAjdSX1s3fq5iEJ64YeQLKI8cD7CSuSEAAlUBAAJu09hW5iqWB0hTPD4bBA", +] + +SLAP_MUKESH_TEMPLATES = ( + "Slap me one more time and I'll mute you.", + "Stop slapping me. REEEEEEEEEEEEEE.", + [ + "I am muting you for a minute.", # normal reply + "Stop slapping me just because I can't mute you. REEEEEEEEEE.", # reply to admin + "tmute", # command + "Shut up!", + "Silence!", + ], +) + +SLAP_TEMPLATES = ( + "{user2} was killed by magic.", + "{user2} starved without pats.", + "{user2} was knocked into the void by {user1}.", + "{user2} fainted.", + "{user2} is out of usable Pokemon! {user2} whited out!.", + "{user2} is out of usable Pokemon! {user2} blacked out!.", + "{user2} got rekt.", + "{user2}'s melon was split by {user1}.", + "{user2} was sliced and diced by {user1}.", + "{user2} played hot-potato with a grenade.", + "{user2} was knifed by {user1}.", + "{user2} ate a grenade.", + "{user2} is what's for dinner!", + "{user2} was terminated by {user1}.", + "{user1} spammed {user2}'s email.", + "{user1} RSA-encrypted {user2} and deleted the private key.", + "{user1} put {user2} in the friendzone.", + "{user1} slaps {user2} with a DMCA takedown request!", + "{user2} got a house call from Doctor {user1}.", + "{user1} beheaded {user2}.", + "{user2} got stoned...by an angry mob.", + "{user1} sued the pants off {user2}.", + "{user2} was one-hit KO'd by {user1}.", + "{user1} sent {user2} down the memory hole.", + "{user2} was a mistake. - '{user1}' ", + "{user2} was made redundant.", + "{user1} {hits} {user2} with a bat!.", + "{user1} {hits} {user2} with a Taijutsu Kick!.", + "{user1} {hits} {user2} with X-Gloves!.", + "{user1} {hits} {user2} with a Jet Punch!.", + "{user1} {hits} {user2} with a Jet Pistol!.", + "{user1} {hits} {user2} with a United States of Smash!.", + "{user1} {hits} {user2} with a Detroit Smash!.", + "{user1} {hits} {user2} with a Texas Smash!.", + "{user1} {hits} {user2} with a California Smash!.", + "{user1} {hits} {user2} with a New Hampshire Smash!.", + "{user1} {hits} {user2} with a Missouri Smash!.", + "{user1} {hits} {user2} with a Carolina Smash!.", + "{user1} {hits} {user2} with a King Kong Gun!.", + "{user1} {hits} {user2} with a baseball bat - metal one.!.", + "*Serious punches {user2}*.", + "*Normal punches {user2}*.", + "*Consecutive Normal punches {user2}*.", + "*Two Handed Consecutive Normal Punches {user2}*.", + "*Ignores {user2} to let them die of embarassment*.", + "*points at {user2}* What's with this sassy... lost child?.", + "*Hits {user2} with a Fire Tornado*.", + "{user1} pokes {user2} in the eye !", + "{user1} pokes {user2} on the sides!", + "{user1} pokes {user2}!", + "{user1} pokes {user2} with a needle!", + "{user1} pokes {user2} with a pen!", + "{user1} pokes {user2} with a stun gun!", + "{user2} is secretly a Furry!", + "Hey Everybody! {user1} is asking me to be mean!", + "( ・_・)ノ⌒●~* (・.・;) <-{user2}", + "Take this {user2}\n(ノ゚Д゚)ノ ))))●~* ", + "Here {user2} hold this\n(`・ω・)つ ●~*", + "( ・_・)ノΞ●~* {user2}\nDieeeee!!.", + "( ・∀・)r鹵~<≪巛;゚Д゚)ノ\n*Bug sprays {user2}*.", + "( ゚Д゚)ノ占~<巛巛巛.\n-{user2} You pest!", + "( う-´)づ︻╦̵̵̿╤── \(˚☐˚”)/ {user2}.", + "{user1} {hits} {user2} with a {item}.", + "{user1} {hits} {user2} in the face with a {item}.", + "{user1} {hits} {user2} around a bit with a {item}.", + "{user1} {throws} a {item} at {user2}.", + "{user1} grabs a {item} and {throws} it at {user2}'s face.", + "{user1} launches a {item} in {user2}'s general direction.", + "{user1} starts slapping {user2} silly with a {item}.", + "{user1} pins {user2} down and repeatedly {hits} them with a {item}.", + "{user1} grabs up a {item} and {hits} {user2} with it.", + "{user1} ties {user2} to a chair and {throws} a {item} at them.", + "{user1} gave a friendly push to help {user2} learn to swim in lava.", + "{user1} bullied {user2}.", + "Nyaan ate {user2}'s leg. *nomnomnom*", + "{user1} {throws} a master ball at {user2}, resistance is futile.", + "{user1} hits {user2} with an action beam...bbbbbb (ง・ω・)ง ====*", + "{user1} ara ara's {user2}.", + "{user1} ora ora's {user2}.", + "{user1} muda muda's {user2}.", + "{user2} was turned into a Jojo reference!", + "{user1} hits {user2} with {item}.", + "Round 2!..Ready? .. FIGHT!!", + "WhoPixel will oof {user2} to infinity and beyond.", + "{user2} ate a bat and discovered a new disease.", + "{user1} folded {user2} into a paper plane", + "{user1} served {user2} some bat soup.", + "{user2} was sent to his home, the planet of the apes.", + "{user1} kicked {user2} out of a moving train.", + "{user2} just killed John Wick’s dog.", + "{user1} performed an Avada Kedavra spell on {user2}.", + "{user1} subjected {user2} to a fiery furnace.", + "Sakura Haruno just got more useful than {user2}", + "{user1} unplugged {user2}'s life support.", + "{user1} subscribed {user2}' to 5 years of bad internet.", + "You know what’s worse than Dad jokes? {user2}!", + "{user1} took all of {user2}'s cookies.", + "{user2} wa mou.......Shindeiru! - {user1}.", + "{user2} lost his race piece!", # No game no life reference + "Shut up {user2}, you are just {user2}.", # No game no life reference + "{user1} hits {user2} with Aka si anse!", # No game no life reference + "@NeoTheKitty scratches {user2}", # Pixels pet cat - @NeoTheKitty + "Majin buu ate {user2}", # Dbz + "Goblin slayer slays {user2}", # Goblin Slayer +) + +PAT_TEMPLATES = ( + "{user1} pats {user2} on the head.", + "*gently rubs {user2}'s head*.", + "*{user1} mofumofus {user2}'s head*", + "*{user1} messes up {user2}'s head*", + "*{user1} intensly rubs {user2}'s head*", + "*{user2}'s waifu pats their head*", + "*{user2}'s got free headpats*", + "No pats for {user2}!", + "Oh no! We are all out of pats.", + "This is a designated no pat zone!", + "No pats for {user2}!", + "{user1} spoils {user2} with headpats!", + "{user2} received one free headpat!", + "{user1} headpats {user2} whilst giving a lap pillow", + "{user1} aggressively pats {user2}", + "{user1} gently strokes {user2}'s head", + "Pat, pat, pat, pat", + "{user2} could not escape {user1}'s headpats", + "{user2}.exe has stopped working", + "{user1} rubs {user2} on the neck", + "Must... extort... HEADPATS", + "{user1} headpats {user2}'s head with a pat", + "{user1} pats {user2} unexpectedly", + "{user1} pats {user2} with consent, maybe?", + "Pat pat, {user2} honto kawaii ne!", + "{user1} headpats {user2} at 420apm", + "{user1} bellyrubs {user2}", + "{user1} pats {user2} friendlily", + "{user2} uses HEADPATS? O KAWAII KOTO", + "*headpats.gif intensifies for {user2}*", + "(*´ω´(*`ω`)", + "(o・_・)ノ”(ᴗ_ ᴗ。)", + "(* ̄▽ ̄)ノ”(- -*)", + "(っ´ω`)ノ(╥ω╥)", + "( ´Д`)ノ(´・ω・`) ナデナデ", +) + +PAT_GIFS = ( + "CgACAgQAAxkBAALRX19Xs7tBdOH1gQwS_rglVRkTbgVYAAKEAgACmQn9UWlyGa_xy9_aGwQ", + "CgACAgEAAxkBAALRYF9Xs8EnhsDfDpld3ILoqTbzDmwxAAJFAAOJxjlHECanwn69E5QbBA", +) + +PAT_STICKERS = ( + "CAACAgQAAxkBAALRWV9Xs4HH0XaXfhZe-jWaZoXfs-IsAAJYAwACdDgSEHYOt4KvL02oGwQ", + "CAACAgQAAxkBAALRXF9Xs6XmIeDbnoL1wiDky0TdX-CvAAKKAQAC1TMzC9A3CtiT2rqVGwQ", +) + +PING_STRING = ("ɪ ᴀᴍ ᴀʟɪᴠᴇ ʙᴀʙʏ! 🖤",) + +ITEMS = ( + "cast iron skillet", + "angry meow", + "cricket bat", + "wooden cane", + "shovel", + "toaster", + "book", + "laptop", + "rubber chicken", + "spiked bat", + "heavy rock", + "chunk of dirt", + "ton of bricks", + "rasengan", + "spirit bomb", + "100-Type Guanyin Bodhisattva", + "rasenshuriken", + "Murasame", + "ban", + "chunchunmaru", + "Kubikiribōchō", + "rasengan", + "spherical flying kat", +) + +THROW = ( + "throws", + "flings", + "chucks", + "hurls", +) + +HIT = ( + "hits", + "whacks", + "slaps", + "smacks", + "bashes", + "pats", +) + +EYES = [ + ["⌐■", "■"], + [" ͠°", " °"], + ["⇀", "↼"], + ["´• ", " •`"], + ["´", "`"], + ["`", "´"], + ["ó", "ò"], + ["ò", "ó"], + ["⸌", "⸍"], + [">", "<"], + ["Ƹ̵̡", "Ʒ"], + ["ᗒ", "ᗕ"], + ["⟃", "⟄"], + ["⪧", "⪦"], + ["⪦", "⪧"], + ["⪩", "⪨"], + ["⪨", "⪩"], + ["⪰", "⪯"], + ["⫑", "⫒"], + ["⨴", "⨵"], + ["⩿", "⪀"], + ["⩾", "⩽"], + ["⩺", "⩹"], + ["⩹", "⩺"], + ["◥▶", "◀◤"], + ["◍", "◎"], + ["/͠-", "┐͡-\\"], + ["⌣", "⌣”"], + [" ͡⎚", " ͡⎚"], + ["≋"], + ["૦ઁ"], + [" ͯ"], + [" ͌"], + ["ළ"], + ["◉"], + ["☉"], + ["・"], + ["▰"], + ["ᵔ"], + [" ゚"], + ["□"], + ["☼"], + ["*"], + ["`"], + ["⚆"], + ["⊜"], + [">"], + ["❍"], + [" ̄"], + ["─"], + ["✿"], + ["•"], + ["T"], + ["^"], + ["ⱺ"], + ["@"], + ["ȍ"], + ["  "], + ["  "], + ["x"], + ["-"], + ["$"], + ["Ȍ"], + ["ʘ"], + ["Ꝋ"], + [""], + ["⸟"], + ["๏"], + ["ⴲ"], + ["◕"], + ["◔"], + ["✧"], + ["■"], + ["♥"], + [" ͡°"], + ["¬"], + [" º "], + ["⨶"], + ["⨱"], + ["⏓"], + ["⏒"], + ["⍜"], + ["⍤"], + ["ᚖ"], + ["ᴗ"], + ["ಠ"], + ["σ"], + ["☯"], +] + +MOUTHS = [ + ["v"], + ["ᴥ"], + ["ᗝ"], + ["Ѡ"], + ["ᗜ"], + ["Ꮂ"], + ["ᨓ"], + ["ᨎ"], + ["ヮ"], + ["╭͜ʖ╮"], + [" ͟ل͜"], + [" ͜ʖ"], + [" ͟ʖ"], + [" ʖ̯"], + ["ω"], + [" ³"], + [" ε "], + ["﹏"], + ["□"], + ["ل͜"], + ["‿"], + ["╭╮"], + ["‿‿"], + ["▾"], + ["‸"], + ["Д"], + ["∀"], + ["!"], + ["人"], + ["."], + ["ロ"], + ["_"], + ["෴"], + ["ѽ"], + ["ഌ"], + ["⏠"], + ["⏏"], + ["⍊"], + ["⍘"], + ["ツ"], + ["益"], + ["╭∩╮"], + ["Ĺ̯"], + ["◡"], + [" ͜つ"], +] + +EARS = [ + ["q", "p"], + ["ʢ", "ʡ"], + ["⸮", "?"], + ["ʕ", "ʔ"], + ["ᖗ", "ᖘ"], + ["ᕦ", "ᕥ"], + ["ᕦ(", ")ᕥ"], + ["ᕙ(", ")ᕗ"], + ["ᘳ", "ᘰ"], + ["ᕮ", "ᕭ"], + ["ᕳ", "ᕲ"], + ["(", ")"], + ["[", "]"], + ["¯\\_", "_/¯"], + ["୧", "୨"], + ["୨", "୧"], + ["⤜(", ")⤏"], + ["☞", "☞"], + ["ᑫ", "ᑷ"], + ["ᑴ", "ᑷ"], + ["ヽ(", ")ノ"], + ["\\(", ")/"], + ["乁(", ")ㄏ"], + ["└[", "]┘"], + ["(づ", ")づ"], + ["(ง", ")ง"], + ["⎝", "⎠"], + ["ლ(", "ლ)"], + ["ᕕ(", ")ᕗ"], + ["(∩", ")⊃━☆゚.*"], +] + +TOSS = ("Heads", "Tails") + +EIGHTBALL = [ + "🟢 As I see it, yes.", + "🟡 Ask again later.", + "🟡 Better not tell you now.", + "🟡 Cannot predict now.", + "🟡 Concentrate and ask again.", + "🟡 Don’t count on it.", + "🟢 It is certain.", + "🟢 It is decidedly so.", + "🟢 Most likely.", + "🔴 My reply is no.", + "🔴 My sources say no.", + "🔴 Outlook not so good.", + "🟢 Outlook good.", + "🟡 Reply hazy, try again.", + "🟢 Signs point to yes.", + "🔴 Very doubtful.", + "🟢 Without a doubt.", + "🟢 Yes.", + "🟢 Yes – definitely.", + "🟢 You may rely on it.", +] + +DECIDE = ("Yes.", "No.", "Maybe.") + +TABLE = ( + "(╯°□°)╯彡 ┻━┻", + "I ran out of tables, will order more.", + "Go do some work instead of flippin tables.", +) diff --git a/QueenNoxi/modules/gban_.py b/QueenNoxi/modules/gban_.py new file mode 100644 index 0000000000000000000000000000000000000000..d3b5beaf757f2340d1f5a421365c5ff568af24e5 --- /dev/null +++ b/QueenNoxi/modules/gban_.py @@ -0,0 +1,86 @@ +# -----------CREDITS ----------- +# telegram : @legend_coder +# github : Alexainc +from pyrogram import filters +from .. import pbot as QueenNoxi,OWNER_ID +from pyrogram.types import ChatPrivileges,ChatPermissions +from pyrogram.types import * +from QueenNoxi.modules.no_sql import add_gban,remove_gban,is_gban,get_served_chats,get_gban_list,is_user_ingbanned +@QueenNoxi.on_message(filters.command("gban") & filters.user(OWNER_ID) & ( filters.group | filters.channel) ) +async def banuser(b,message): + reason="" + if message.reply_to_message: + user = message.reply_to_message.from_user.id + r=message.text.split()[1:] if message.reply_to_message else None + reason+=r + u=is_user_ingbanned(user) + if u: + return await message.reply_text("User is already gbanned") + elif not message.reply_to_message and len(message.command) != 1: + user = message.text.split(None, 1)[1].split()[0] + r= " ".join(message.command[2:]) if len(message.command) > 2 else None + print(r) + reason+=r + success=0 + hm = await QueenNoxi.get_users(user) + try: + all_chats =get_served_chats() + for chat in all_chats: + + try: + sts=await b.ban_chat_member(chat["chat_id"],user) + add_gban(user_id=user,gban_reason=reason) + success += 1 + except Exception as e: + print(e) + await message.reply_text(f"gbanned from {success} chats \n user :> {hm.mention()} reason {reason}") + except Exception as e: + await message.reply_text(f"failed due to {e}") +@QueenNoxi.on_message(filters.command("ungban") & filters.user(OWNER_ID) & ( filters.group | filters.channel) ) +async def unbanuser(b,message): + if message.reply_to_message: + user = message.reply_to_message.from_user.id + u=is_user_ingbanned(user) + if not u: + return await message.reply_text("User is not gbanned yet") + elif not message.reply_to_message and len(message.command) != 1: + user = message.text.split(None, 1)[1] + + success=0 + hm = await QueenNoxi.get_users(user) + try: + all_chats =get_served_chats() + for chat in all_chats: + + try: + sts=await b.unban_chat_member(chat["chat_id"],user) + remove_gban(user_id=user) + success += 1 + except Exception as e: + print(e) + await message.reply_text(f"ungbanned from {success} chats \n user :> {hm.mention()}") + except Exception as e: + await message.reply_text(f"failed due to {e}") +@QueenNoxi.on_message(filters.command("gbanlist") & filters.user(OWNER_ID)) +async def get_gban_listss(_,m) : + banned=get_gban_list() + if len(get_gban_list())==0: + return await m.reply_text("No users are gbanned") + text="Gbanned Users:\n" + for user in banned : + text+=f"• {user['user_id']} - {user['gban_reason']}\n" + if len(text) > 4096: + filename = "gbanlist.txt" + with open(filename, "w+", encoding="utf8") as out_file: + out_file.write(str(text)) + + return await m.reply_document( + document=filename) + else: + await m.reply_text(text) + + + +def __stats__(): + return f"• {len(get_gban_list())} ɢʙᴀɴɴᴇᴅ ᴜsᴇʀs." +__mod_name__ = "Gʙᴀɴ​" \ No newline at end of file diff --git a/QueenNoxi/modules/get_common_chats.py b/QueenNoxi/modules/get_common_chats.py new file mode 100644 index 0000000000000000000000000000000000000000..64ac7e6686faf733aeb0a69106d287e8e852cf78 --- /dev/null +++ b/QueenNoxi/modules/get_common_chats.py @@ -0,0 +1,52 @@ +import os +from time import sleep + +from telegram import Update +from telegram.error import BadRequest, RetryAfter, Unauthorized +from telegram.ext import CallbackContext, CommandHandler, Filters + +from QueenNoxi import OWNER_ID, dispatcher +from QueenNoxi.modules.helper_funcs.extraction import extract_user +from QueenNoxi.modules.sql.users_sql import get_user_com_chats + + +def get_user_common_chats(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + msg = update.effective_message + user = extract_user(msg, args) + if not user: + msg.reply_text("I share no common chats with the void.") + return + common_list = get_user_com_chats(user) + if not common_list: + msg.reply_text("No common chats with this user!") + return + name = bot.get_chat(user).first_name + text = f"<b>Common chats with {name}</b>\n" + for chat in common_list: + try: + chat_name = bot.get_chat(chat).title + sleep(0.3) + text += f"• <code>{chat_name}</code>\n" + except BadRequest: + pass + except Unauthorized: + pass + except RetryAfter as e: + sleep(e.retry_after) + + if len(text) < 4096: + msg.reply_text(text, parse_mode="HTML") + else: + with open("common_chats.txt", "w") as f: + f.write(text) + with open("common_chats.txt", "rb") as f: + msg.reply_document(f) + os.remove("common_chats.txt") + + +COMMON_CHATS_HANDLER = CommandHandler( + "getchats", get_user_common_chats, filters=Filters.user(OWNER_ID), run_async=True +) + +dispatcher.add_handler(COMMON_CHATS_HANDLER) diff --git a/QueenNoxi/modules/gettime.py b/QueenNoxi/modules/gettime.py new file mode 100644 index 0000000000000000000000000000000000000000..a33eb60270137506cbd076f9a31eb84e35a18f67 --- /dev/null +++ b/QueenNoxi/modules/gettime.py @@ -0,0 +1,108 @@ +import datetime +from typing import List + +import requests +from telegram import ParseMode, Update +from telegram.ext import CallbackContext +from QueenNoxi import TIME_API_KEY, dispatcher +from QueenNoxi.modules.disable import DisableAbleCommandHandler + + +def generate_time(to_find: str, findtype: List[str]) -> str: + data = requests.get( + f"https://api.timezonedb.com/v2.1/list-time-zone" + f"?key={TIME_API_KEY}" + f"&format=json" + f"&fields=countryCode,countryName,zoneName,gmtOffset,timestamp,dst" + ).json() + + for zone in data["zones"]: + for eachtype in findtype: + if to_find in zone[eachtype].lower(): + country_name = zone["countryName"] + country_zone = zone["zoneName"] + country_code = zone["countryCode"] + + if zone["dst"] == 1: + daylight_saving = "Yes" + else: + daylight_saving = "No" + + date_fmt = r"%d-%m-%Y" + time_fmt = r"%H:%M:%S" + day_fmt = r"%A" + gmt_offset = zone["gmtOffset"] + timestamp = datetime.datetime.now( + datetime.timezone.utc + ) + datetime.timedelta(seconds=gmt_offset) + current_date = timestamp.strftime(date_fmt) + current_time = timestamp.strftime(time_fmt) + current_day = timestamp.strftime(day_fmt) + + break + + try: + result = ( + f"<b>Country:</b> <code>{country_name}</code>\n" + f"<b>Zone Name:</b> <code>{country_zone}</code>\n" + f"<b>Country Code:</b> <code>{country_code}</code>\n" + f"<b>Daylight saving:</b> <code>{daylight_saving}</code>\n" + f"<b>Day:</b> <code>{current_day}</code>\n" + f"<b>Current Time:</b> <code>{current_time}</code>\n" + f"<b>Current Date:</b> <code>{current_date}</code>\n" + '<b>Timezones:</b> <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">List here</a>' + ) + except: + result = None + + return result + + +def gettime(update: Update, context: CallbackContext): + message = update.effective_message + + try: + query = message.text.strip().split(" ", 1)[1] + except: + message.reply_text("Provide a country name/abbreviation/timezone to find.") + return + send_message = message.reply_text( + f"Finding timezone info for <b>{query}</b>", parse_mode=ParseMode.HTML + ) + + query_timezone = query.lower() + if len(query_timezone) == 2: + result = generate_time(query_timezone, ["countryCode"]) + else: + result = generate_time(query_timezone, ["zoneName", "countryName"]) + + if not result: + send_message.edit_text( + f"Timezone info not available for <b>{query}</b>\n" + '<b>All Timezones:</b> <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">List here</a>', + parse_mode=ParseMode.HTML, + disable_web_page_preview=True, + ) + return + + send_message.edit_text( + result, parse_mode=ParseMode.HTML, disable_web_page_preview=True + ) + + +__help__ = """ + ❍ /time <ǫᴜᴇʀʏ>*:* ɢɪᴠᴇs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀ ᴛɪᴍᴇᴢᴏɴᴇ. +*ᴀᴠᴀɪʟᴀʙʟᴇ ǫᴜᴇʀɪᴇs:* ᴄᴏᴜɴᴛʀʏ ᴄᴏᴅᴇ/ᴄᴏᴜɴᴛʀʏ ɴᴀᴍᴇ/ᴛɪᴍᴇᴢᴏɴᴇ ɴᴀᴍᴇ + + ❍ ⏰ [ᴛɪᴍᴇᴢᴏɴᴇs ʟɪsᴛ](ʜᴛᴛᴘs://ᴇɴ.ᴡɪᴋɪᴘᴇᴅɪᴀ.ᴏʀɢ/ᴡɪᴋɪ/ʟɪsᴛ_ᴏғ_ᴛᴢ_ᴅᴀᴛᴀʙᴀsᴇ_ᴛɪᴍᴇ_ᴢᴏɴᴇs) + +💡 ᴇx:- /time ɪɴ *:* ɪᴛ ᴡɪʟʟ sʜᴏᴡs ɪɴᴅɪᴀɴ ᴄᴜʀʀᴇɴᴛ ᴛɪᴍᴇ ᴀɴᴅ ᴅᴀᴛᴇ.. +""" + +TIME_HANDLER = DisableAbleCommandHandler("time", gettime, run_async=True) + +dispatcher.add_handler(TIME_HANDLER) + +__mod_name__ = "Tɪᴍᴇ" +__command_list__ = ["time"] +__handlers__ = [TIME_HANDLER] diff --git a/QueenNoxi/modules/gitinfo.py b/QueenNoxi/modules/gitinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..85a81ad735bcf5d1a652fea287d69ba74d0e6159 --- /dev/null +++ b/QueenNoxi/modules/gitinfo.py @@ -0,0 +1,59 @@ +from aiohttp import ClientSession +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from QueenNoxi import pbot +from QueenNoxi.utils.errors import capture_err + + +@pbot.on_message(filters.command(["github","git"])) +@capture_err +async def github(_, message): + if len(message.command) != 2: + return await message.reply_text("/github {username} \n`/github Noob-QueenNoxi`") + username = message.text.split(None, 1)[1] + URL = f"https://api.github.com/users/{username}" + async with ClientSession() as session: + async with session.get(URL) as request: + if request.status == 404: + return await message.reply_text("404") + result = await request.json() + try: + url = result["html_url"] + name = result["name"] + company = result["company"] + bio = result["bio"] + created_at = result["created_at"] + avatar_url = result["avatar_url"] + blog = result["blog"] + location = result["location"] + repositories = result["public_repos"] + followers = result["followers"] + following = result["following"] + global QueenNoxi + QueenNoxi = [[ + InlineKeyboardButton(text="ᴘʀᴏғɪʟᴇ ʟɪɴᴋ", url=url), + InlineKeyboardButton("Cʟᴏsᴇ",callback_data="close_reply") + ]] + caption = f"""**Iɴғᴏ Oғ {name}** +**ᴜsᴇʀɴᴀᴍᴇ :** `{username}` +**ʙɪᴏ :** `{bio}` +**ᴄᴏᴍᴘᴀɴʏ :** `{company}` +**ᴄʀᴇᴀᴛᴇᴅ ᴏɴ:** `{created_at}` +**ʀᴇᴘᴏsɪᴛᴏʀɪᴇs :** `{repositories}` +**ʙʟᴏɢ :** `{blog}` +**ʟᴏᴄᴀᴛɪᴏɴ :** `{location}` +**ғᴏʟʟᴏᴡᴇʀs :** `{followers}` +**ғᴏʟʟᴏᴡɪɴɢ :** `{following}`""" + except Exception as e: + await message.reply(f"#ERROR {e}") + + await message.reply_photo(photo=avatar_url, caption=caption,reply_markup=InlineKeyboardMarkup(QueenNoxi)) + + +__mod_name__ = "Gɪᴛʜᴜʙ" + +__help__ = """ +ᴘʀᴏᴠɪᴅᴇs ʏᴏᴜ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀ ɢɪᴛʜᴜʙ ᴘʀᴏғɪʟᴇ. + + ❍ /github <ᴜsᴇʀɴᴀᴍᴇ> *:* ɢᴇᴛ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀ ɢɪᴛʜᴜʙ ᴜsᴇʀ. +""" diff --git a/QueenNoxi/modules/google.py b/QueenNoxi/modules/google.py new file mode 100644 index 0000000000000000000000000000000000000000..74c669a8f157853abeda8eeb2a1da4cdcfdbf3d3 --- /dev/null +++ b/QueenNoxi/modules/google.py @@ -0,0 +1,226 @@ +import glob +import io +import os +import re +import urllib +import urllib.request +import bs4 +import requests +from bing_image_downloader import downloader +from bs4 import BeautifulSoup +from PIL import Image +from search_engine_parser import GoogleSearch + +from pyrogram import filters +from pyrogram.types import InputMediaPhoto +from QueenNoxi import pbot as app + +opener = urllib.request.build_opener() +useragent = "Mozilla/5.0 (Linux; Android 11; SM-M017F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36" +opener.addheaders = [("User-agent", useragent)] + +@app.on_message(filters.command("google")) +async def _(_, message): + if getattr(message, "forward_from", None): + return + + webevent = await message.reply("Searching...") + match = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + if not match: + return await webevent.edit("Please provide a query.") + + page = re.findall(r"page=\d+", match) + try: + page_num = int(page[0].replace("page=", "")) + match = match.replace("page=" + page[0], "") + except (IndexError, ValueError): + page_num = 1 + + search_args = (str(match), page_num) + gsearch = GoogleSearch() + gresults = await gsearch.async_search(*search_args) + msg = "" + for i in range(len(gresults["links"])): + try: + title = gresults["titles"][i] + link = gresults["links"][i] + desc = gresults["descriptions"][i] + msg += f"❍[{title}]({link})\n**{desc}**\n\n" + except IndexError: + break + + await webevent.edit("**Search Query:**\n`" + match + "`\n\n**Results:**\n" + msg, disable_web_page_preview=True) + +@app.on_message(filters.command("img")) +async def img_sampler(_, message): + if getattr(message, "forward_from", None): + return + + query = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + if not query: + return + + jit = f'"{query}"' + downloader.download( + jit, + limit=4, + output_dir="store", + adult_filter_off=False, + force_replace=False, + timeout=60, + ) + + query_dir = f'./store/{query}' + + if os.path.exists(query_dir): + types = ("*.png", "*.jpeg", "*.jpg") + files_grabbed = [] + for tk in types: + files_grabbed.extend(glob.glob(os.path.join(query_dir, tk))) + + if files_grabbed: + media_group = [InputMediaPhoto(f) for f in files_grabbed] + try: + await app.send_media_group(message.chat.id, media=media_group, reply_to_message_id=message.id) + except Exception: + pass + os.system("rm -rf store") + +@app.on_message(filters.command(["reverse", "pp", "grs"])) +async def okgoogle(_, message): + if os.path.isfile("okgoogle.png"): + os.remove("okgoogle.png") + + r_message = message.reply_to_message + if r_message and r_message.media: + dev = await message.reply("`Processing...`") + photo = await r_message.download("okgoogle.png") + else: + return await message.reply("`Reply to photo or sticker fu*ker`") + + if photo: + try: + image = Image.open(photo) + image.save("okgoogle.png", "PNG") + image.close() + except OSError: + await dev.edit("`Unsupported sexuality, most likely.`") + return + + searchUrl = "https://www.google.com/searchbyimage/upload" + multipart = {"encoded_image": ("okgoogle.png", open("okgoogle.png", "rb")), "image_content": ""} + response = requests.post(searchUrl, files=multipart, allow_redirects=False) + fetchUrl = response.headers.get("Location") + + if response.status_code != 400 and fetchUrl: + await dev.edit("`Image successfully uploaded to Google. Maybe.`\n`Parsing source now. Maybe.`") + else: + return await dev.edit("`Google told me to fu*k off.`") + + os.remove("okgoogle.png") + match_data = await ParseSauce(fetchUrl + "&preferences?hl=en&fg=1#languages") + guess = match_data.get("best_guess") + imgspage = match_data.get("similar_images") + + if guess and imgspage: + await dev.edit(f"[{guess}]({fetchUrl})\n\n`Looking for this Image...`", disable_web_page_preview=True) + else: + return await dev.edit("`Can't find this piece of shit.`") + + lim = 3 + if len(message.command) > 1: + try: + lim = int(message.command[1]) + except ValueError: + pass + + images = await scam(match_data, lim) + yeet = [] + for i in images: + try: + k = requests.get(i) + temp_file = f"temp_{images.index(i)}.jpg" + with open(temp_file, "wb") as f: + f.write(k.content) + yeet.append(temp_file) + except Exception: + pass + + if yeet: + media_group = [InputMediaPhoto(f) for f in yeet] + try: + await app.send_media_group(message.chat.id, media=media_group, reply_to_message_id=message.id) + except Exception: + pass + for f in yeet: + if os.path.exists(f): + os.remove(f) + + await dev.edit(f"[{guess}]({fetchUrl})\n\n[Visually similar images]({imgspage})", disable_web_page_preview=True) + + +async def ParseSauce(googleurl): + source = opener.open(googleurl).read() + soup = BeautifulSoup(source, "html.parser") + results = {"similar_images": "", "best_guess": ""} + try: + for similar_image in soup.findAll("input", {"class": "gLFyf"}): + url = "https://www.google.com/search?tbm=isch&q=" + urllib.parse.quote_plus(similar_image.get("value")) + results["similar_images"] = url + except BaseException: + pass + for best_guess in soup.findAll("div", attrs={"class": "r5a77d"}): + results["best_guess"] = best_guess.get_text() + return results + +async def scam(results, lim): + single = opener.open(results["similar_images"]).read() + decoded = single.decode("utf-8") + imglinks = [] + counter = 0 + pattern = r"^,\[\"(.*[.png|.jpg|.jpeg])\",[0-9]+,[0-9]+\]$" + oboi = re.findall(pattern, decoded, re.I | re.M) + for imglink in oboi: + counter += 1 + if counter < int(lim): + imglinks.append(imglink) + else: + break + return imglinks + +@app.on_message(filters.command("app")) +async def apk(_, message): + try: + app_name = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + if not app_name: + return await message.reply("Please specify an app name.") + + remove_space = app_name.split(" ") + final_name = "+".join(remove_space) + page = requests.get("https://play.google.com/store/search?q=" + final_name + "&c=apps") + soup = bs4.BeautifulSoup(page.content, "html.parser", from_encoding="utf-8") + results = soup.findAll("div", "ZmHEEd") + app_name = results[0].findNext("div", "Vpfmgd").findNext("div", "WsMG1c nnK0zc").text + app_dev = results[0].findNext("div", "Vpfmgd").findNext("div", "KoLSrc").text + app_dev_link = "https://play.google.com" + results[0].findNext("div", "Vpfmgd").findNext("a", "mnKHRc")["href"] + app_rating = results[0].findNext("div", "Vpfmgd").findNext("div", "pf5lIe").find("div")["aria-label"] + app_link = "https://play.google.com" + results[0].findNext("div", "Vpfmgd").findNext("div", "vU6FJ p63iDd").a["href"] + app_icon = results[0].findNext("div", "Vpfmgd").findNext("div", "uzcko").img["data-src"] + + app_details = "<a href='" + app_icon + "'>📲​</a> <b>" + app_name + "</b>\n\n" + app_details += "<code>Developer :</code> <a href='" + app_dev_link + "'>" + app_dev + "</a>\n" + app_details += "<code>Rating :</code> " + app_rating.replace("Rated ", "⭐ ").replace(" out of ", "/").replace(" stars", "", 1).replace(" stars", "⭐ ").replace("five", "5") + "\n" + app_details += "<code>Features :</code> <a href='" + app_link + "'>View in Play Store</a>\n\n===> Group Controller<===" + await message.reply(app_details, disable_web_page_preview=False) + except IndexError: + await message.reply("No result found in search. Please enter **Valid app name**") + except Exception as err: + await message.reply("Exception Occured:- " + str(err)) + +__mod_name__ = "Gᴏᴏɢʟᴇ" +__help__ = """ + ❍ /google <text>*:* Perform a google search + ❍ /img <text>*:* Search Google for images and returns them + ❍ /app <appname>*:* Searches for an app in Play Store and returns its details. + ❍ /reverse |pp |grs: Does a reverse image search of the media which it was replied to. +""" diff --git a/QueenNoxi/modules/gps.py b/QueenNoxi/modules/gps.py new file mode 100644 index 0000000000000000000000000000000000000000..ea08f802320d6a9d569ca7cacaf16068a7931819 --- /dev/null +++ b/QueenNoxi/modules/gps.py @@ -0,0 +1,86 @@ +from pyrogram import enums, filters, idle +from pyrogram.types import InlineKeyboardButton as IKB, InlineKeyboardMarkup as IKM +from requests import get +import asyncio +from QueenNoxi import pbot as queennoxi +from pyrogram.handlers import MessageHandler +from geopy.geocoders import Nominatim +from geopy.distance import great_circle +@queennoxi.on_message(filters.command(["gps"])) +async def gps(bot, message): +# await message.delete() + if len(message.command) < 2: + return await message.reply_text( + "**Example:**\n\n`/gps [latitude , longitude]`") + x = message.text.split(' ')[1].split(',') + + + try: + + """ + ---------github :-NOOB-MUKESH ----- + ---------telegram : @itz_legend_coder----- + """ + geolocator = Nominatim(user_agent="legend-QueenNoxi") +# zoom=[0-18] + + + location = geolocator.reverse(x,addressdetails=True, zoom=18) + address=location.raw['address'] + # Traverse the data + city = address.get('city', '') + state = address.get('state', '') + country = address.get('country', '') + latitude = location.latitude + longitude = location.longitude + url=[ + + [IKB + + ("Open with:🌏ɢᴏᴏɢʟᴇ ᴍᴀᴘs ",url=f"https://www.google.com/maps/search/{latitude},{longitude}") + + ] + + ] + + # await message.reply_text(f"{gm}") + await message.reply_venue(latitude, longitude,f"{city}",f"{state} ,{country}",reply_markup=IKM(url)) + except Exception as e: + await message.reply_text(f"I can't find that \nDue to {e}") +@queennoxi.on_message(filters.command(["distance"])) +async def distance(bot, message): + await message.delete() + if len(message.command) < 2: + return await message.reply_text( + "**Example:**\n\n`/distance [latitude , longitude],[latitude , longitude]`") + + x = message.text.split(" ")[1].split(',',2)[0:2] + y = message.text.split(" ")[1].split(',',4)[2:4] + + + + + try: + + """ + ---------github :-NOOB-MUKESH ----- + ---------telegram : @itz_legend_coder----- + """ + distance=(great_circle(x,y).miles) + + await message.reply_text(f"Total distance between {x[0]},{x[1]} and {y[0]},{y[1]} is {distance}") + + except Exception as e: + await message.reply_text(f"I can't find that \nDue to {e}") + +# queennoxi.add_handler(MessageHandler(gps)) +# queennoxi.add_handler(MessageHandler(distance)) + +__help__ = """ +sᴇɴᴅs ʏᴏᴜ ᴛʜᴇ ɢᴘs ʟᴏᴄᴀᴛɪᴏɴ ᴏғ ᴛʜᴇ ɢɪᴠᴇɴ ǫᴜᴇʀʏ... + + ❍ /gps <ʟᴏᴄᴀᴛɪᴏɴ>*:* ɢᴇᴛ ɢᴘs ʟᴏᴄᴀᴛɪᴏɴ. + ❍ /distance to measure distance +""" + +__mod_name__ = "Gᴘs" diff --git a/QueenNoxi/modules/group.py b/QueenNoxi/modules/group.py new file mode 100644 index 0000000000000000000000000000000000000000..8a03505eb808110df21847435d0f576900b64030 --- /dev/null +++ b/QueenNoxi/modules/group.py @@ -0,0 +1,9 @@ +__help__ = """ + ❍ /setgtitle <ɴᴇᴡᴛɪᴛʟᴇ>*:* sᴇᴛs ɴᴇᴡ ᴄʜᴀᴛ ᴛɪᴛʟᴇ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ. + ❍ /setgpic *:* ᴀs ᴀ ʀᴇᴘʟʏ ᴛᴏ ғɪʟᴇ ᴏʀ ᴘʜᴏᴛᴏ ᴛᴏ sᴇᴛ ɢʀᴏᴜᴘ ᴘʀᴏғɪʟᴇ ᴘɪᴄ! + ❍ /delgpic *:* sᴀᴍᴇ ᴀs ᴀʙᴏᴠᴇ ʙᴜᴛ ᴛᴏ ʀᴇᴍᴏᴠᴇ ɢʀᴏᴜᴘ ᴘʀᴏғɪʟᴇ ᴘɪᴄ. + ❍ /setsticker *:* ᴀs ᴀ ʀᴇᴘʟʏ ᴛᴏ sᴏᴍᴇ sᴛɪᴄᴋᴇʀ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɢʀᴏᴜᴘ sᴛɪᴄᴋᴇʀ sᴇᴛ! + ❍ /setdescription <ᴅᴇsᴄʀɪᴘᴛɪᴏɴ>*:* sᴇᴛs ɴᴇᴡ ᴄʜᴀᴛ ᴅᴇsᴄʀɪᴘᴛɪᴏɴ ɪɴ ɢʀᴏᴜᴘ. +""" + +__mod_name__ = "Gʀᴏᴜᴘ" diff --git a/QueenNoxi/modules/hastaggen.py b/QueenNoxi/modules/hastaggen.py new file mode 100644 index 0000000000000000000000000000000000000000..d40b018362f23d113a10161d777c9add18850c28 --- /dev/null +++ b/QueenNoxi/modules/hastaggen.py @@ -0,0 +1,23 @@ +import requests +from QueenNoxi import pbot as queennoxi +from pyrogram import filters + +@queennoxi.on_message(filters.command("hastag")) +async def hastag(bot, message): + + try: + text = message.text.split(' ',1)[1] + res = requests.get(f"https://queennoxi-api.vercel.app/hastag?query={text}").json()["results"] + + except IndexError: + return await message.reply_text("Example:\n\n`/hastag python`") + + + await message.reply_text(f"ʜᴇʀᴇ ɪs ʏᴏᴜʀ ʜᴀsᴛᴀɢ :\n<pre>{res}</pre>", quote=True) + +__mod_name__ = "Hᴀsʜᴛᴀɢ" +__help__= """ +**Yᴏᴜ ᴄᴀɴ ᴜsᴇ ᴛʜɪs ʜᴀsʜᴛᴀɢ ɢᴇɴᴇʀᴀᴛᴏʀ ᴡʜɪᴄʜ ᴡɪʟʟ ɢɪᴠᴇ ʏᴏᴜ ᴛʜᴇ ᴛᴏᴘ 𝟹𝟶 ᴀɴᴅ ᴍᴏʀᴇ ʜᴀsʜᴛᴀɢs ʙᴀsᴇᴅ ᴏғғ ᴏғ ᴏɴᴇ ᴋᴇʏᴡᴏʀᴅ sᴇʟᴇᴄᴛɪᴏɴ.** +° /hastag enter word to generate hastag. +°Exᴀᴍᴘʟᴇ: ` /hastag python `""" + diff --git a/QueenNoxi/modules/helper_funcs/__init__.py b/QueenNoxi/modules/helper_funcs/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3edc339fcc2a871df59dcedbbe91146ed8818fb5 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/__init__.py @@ -0,0 +1 @@ +"""Helpers, also known as Utilities""" diff --git a/QueenNoxi/modules/helper_funcs/admin_rights.py b/QueenNoxi/modules/helper_funcs/admin_rights.py new file mode 100644 index 0000000000000000000000000000000000000000..7126a58966eb3bef959f0aa44404ad5de8d6faaf --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/admin_rights.py @@ -0,0 +1,33 @@ +from pyrogram import enums +from QueenNoxi import pbot + +async def user_can_promote(chat_id: int, user_id: int) -> bool: + try: + member = await pbot.get_chat_member(chat_id, user_id) + return member.status == enums.ChatMemberStatus.OWNER or (member.privileges and member.privileges.can_promote_members) + except Exception: + return False + + +async def user_can_ban(chat_id: int, user_id: int) -> bool: + try: + member = await pbot.get_chat_member(chat_id, user_id) + return member.status == enums.ChatMemberStatus.OWNER or (member.privileges and member.privileges.can_restrict_members) + except Exception: + return False + + +async def user_can_pin(chat_id: int, user_id: int) -> bool: + try: + member = await pbot.get_chat_member(chat_id, user_id) + return member.status == enums.ChatMemberStatus.OWNER or (member.privileges and member.privileges.can_pin_messages) + except Exception: + return False + + +async def user_can_changeinfo(chat_id: int, user_id: int) -> bool: + try: + member = await pbot.get_chat_member(chat_id, user_id) + return member.status == enums.ChatMemberStatus.OWNER or (member.privileges and member.privileges.can_change_info) + except Exception: + return False diff --git a/QueenNoxi/modules/helper_funcs/alternate.py b/QueenNoxi/modules/helper_funcs/alternate.py new file mode 100644 index 0000000000000000000000000000000000000000..5a5a577c6091ecb5f4c604b7d0f8dee1bec27db5 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/alternate.py @@ -0,0 +1,23 @@ +from functools import wraps +from pyrogram import enums +from pyrogram.types import Message + +async def send_message(message: Message, text: str, *args, **kwargs): + try: + return await message.reply_text(text, *args, **kwargs) + except Exception as err: + if "REPLY_MESSAGE_ID_INVALID" in str(err): + return await message.reply_text(text, quote=False, *args, **kwargs) + + +def typing_action(func): + """Sends typing action while processing func command.""" + + @wraps(func) + async def command_func(client, message, *args, **kwargs): + await client.send_chat_action( + chat_id=message.chat.id, action=enums.ChatAction.TYPING + ) + return await func(client, message, *args, **kwargs) + + return command_func diff --git a/QueenNoxi/modules/helper_funcs/chat_status.py b/QueenNoxi/modules/helper_funcs/chat_status.py new file mode 100644 index 0000000000000000000000000000000000000000..6c5c7c31915fc60f7a681073c48017cafd9390b8 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/chat_status.py @@ -0,0 +1,398 @@ +import asyncio +from functools import wraps +from time import perf_counter + +from cachetools import TTLCache +from pyrogram import enums +from pyrogram.types import Message, CallbackQuery +from pyrogram.errors import RPCError + +from QueenNoxi import ( + DEL_CMDS, + DEMONS, + DEV_USERS, + DRAGONS, + SUPPORT_CHAT, + TIGERS, + WOLVES, + pbot, + LOGGER, +) + +# stores admins in memory for 10 min. +ADMIN_CACHE = TTLCache(maxsize=512, ttl=60 * 10, timer=perf_counter) + +def get_user_chat(update): + if isinstance(update, CallbackQuery): + return update.from_user, update.message.chat if update.message else None + return update.from_user, update.chat + + +async def is_whitelist_plus(chat_id: int, user_id: int) -> bool: + return any(user_id in user for user in [WOLVES, TIGERS, DEMONS, DRAGONS, DEV_USERS]) + + +async def is_support_plus(chat_id: int, user_id: int) -> bool: + return user_id in DEMONS or user_id in DRAGONS or user_id in DEV_USERS + + +async def is_sudo_plus(chat_id: int, user_id: int) -> bool: + return user_id in DRAGONS or user_id in DEV_USERS + + +async def get_admin_ids(chat_id: int) -> list[int]: + """Fetch from cache or API.""" + if chat_id in ADMIN_CACHE: + return ADMIN_CACHE[chat_id] + + try: + admins = [] + async for m in pbot.get_chat_members(chat_id, filter=enums.ChatMembersFilter.ADMINISTRATORS): + if m.user: + admins.append(m.user.id) + elif m.status == enums.ChatMemberStatus.OWNER: + pass + ADMIN_CACHE[chat_id] = admins + return admins + except Exception: + return [] + + +async def is_user_admin(chat, user_id: int) -> bool: + if ( + chat.type == enums.ChatType.PRIVATE + or user_id in DRAGONS + or user_id in DEV_USERS + or user_id in [777000, 1087968824] + ): + return True + + try: + member = await pbot.get_chat_member(chat.id, user_id) + if member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER): + return True + except Exception: + pass + + admin_ids = await get_admin_ids(chat.id) + return user_id in admin_ids + + +async def is_bot_admin(chat, bot_id: int) -> bool: + if chat.type == enums.ChatType.PRIVATE: + return True + + try: + member = await pbot.get_chat_member(chat.id, bot_id) + return member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER) + except Exception: + return False + + +def dev_plus(func): + @wraps(func) + async def is_dev_plus_func(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + if user and user.id in DEV_USERS: + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] dev_plus denied for {user.id if user else 'Anonymous'}") + if DEL_CMDS and hasattr(message, "text") and message.text and " " not in message.text: + try: await message.delete() + except: pass + if hasattr(message, "reply_text"): + await message.reply_text("This is a developer restricted command.") + elif hasattr(message, "answer"): + await message.answer("Developer restricted.", show_alert=True) + + return is_dev_plus_func + + +def sudo_plus(func): + @wraps(func) + async def is_sudo_plus_func(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + + if user and chat and await is_sudo_plus(chat.id, user.id): + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] sudo_plus denied for {user.id if user else 'Anonymous'}") + if DEL_CMDS and hasattr(message, "text") and message.text and " " not in message.text: + try: await message.delete() + except: pass + if hasattr(message, "reply_text"): + await message.reply_text("Who dis non-admin telling me what to do?") + elif hasattr(message, "answer"): + await message.answer("Not allowed.", show_alert=True) + + return is_sudo_plus_func + + +def support_plus(func): + @wraps(func) + async def is_support_plus_func(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + + if user and chat and await is_support_plus(chat.id, user.id): + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] support_plus denied for {user.id if user else 'Anonymous'}") + if hasattr(message, "reply_text"): + await message.reply_text("This is for support users only.") + elif hasattr(message, "answer"): + await message.answer("Support users only.", show_alert=True) + + return is_support_plus_func + + +def whitelist_plus(func): + @wraps(func) + async def is_whitelist_plus_func(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + + if user and chat and await is_whitelist_plus(chat.id, user.id): + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] whitelist_plus denied for {user.id if user else 'Anonymous'}") + if hasattr(message, "reply_text"): + await message.reply_text(f"You don't have access to use this.\nVisit @{SUPPORT_CHAT}") + elif hasattr(message, "answer"): + await message.answer("No access.", show_alert=True) + + return is_whitelist_plus_func + + +def user_admin(func): + @wraps(func) + async def is_admin(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + + if user and chat and await is_user_admin(chat, user.id): + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] user_admin denied for {user.id if user else 'Anonymous'}") + if DEL_CMDS and hasattr(message, "text") and message.text and " " not in message.text: + try: await message.delete() + except: pass + if hasattr(message, "reply_text"): + await message.reply_text("Who dis non-admin telling me what to do?") + elif hasattr(message, "answer"): + await message.answer("Admin required.", show_alert=True) + + return is_admin + + +def user_admin_no_reply(func): + @wraps(func) + async def is_not_admin_no_reply(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + + if user and chat and await is_user_admin(chat, user.id): + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] user_admin_no_reply denied for {user.id if user else 'Anonymous'}") + if hasattr(message, "answer"): + await message.answer("Admin required.", show_alert=True) + + return is_not_admin_no_reply + + +def user_not_admin(func): + @wraps(func) + async def is_not_admin(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + + if user and chat and not await is_user_admin(chat, user.id): + return await func(client, message, *args, **kwargs) + elif not user and chat and chat.type != enums.ChatType.PRIVATE: + return + elif not user: + return await func(client, message, *args, **kwargs) + + return is_not_admin + + +def bot_admin(func): + @wraps(func) + async def is_admin(client, message, *args, **kwargs): + _, chat = get_user_chat(message) + if not chat: return + + if await is_bot_admin(chat, client.me.id if hasattr(client, "me") and client.me else (await client.get_me()).id): + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] bot_admin denied in {chat.id}") + if hasattr(message, "reply_text"): + await message.reply_text("I'm not admin! - REEEEEE") + elif hasattr(message, "answer"): + await message.answer("Bot is not admin!", show_alert=True) + + return is_admin + + +def can_pin(func): + @wraps(func) + async def pin_rights(client, message, *args, **kwargs): + _, chat = get_user_chat(message) + if not chat: return + me = await pbot.get_chat_member(chat.id, client.me.id if hasattr(client, "me") and client.me else (await client.get_me()).id) + + if (me.privileges and me.privileges.can_pin_messages) or me.status == enums.ChatMemberStatus.OWNER: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] can_pin denied in {chat.id}") + if hasattr(message, "reply_text"): + await message.reply_text("I can't pin messages here!") + elif hasattr(message, "answer"): + await message.answer("I can't pin messages here!", show_alert=True) + + return pin_rights + + +def can_promote(func): + @wraps(func) + async def promote_rights(client, message, *args, **kwargs): + _, chat = get_user_chat(message) + if not chat: return + me = await pbot.get_chat_member(chat.id, client.me.id if hasattr(client, "me") and client.me else (await client.get_me()).id) + + if (me.privileges and me.privileges.can_promote_members) or me.status == enums.ChatMemberStatus.OWNER: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] can_promote denied in {chat.id}") + if hasattr(message, "reply_text"): + await message.reply_text("I can't promote/demote people here!") + elif hasattr(message, "answer"): + await message.answer("I can't promote people here!", show_alert=True) + + return promote_rights + + +def can_delete(func): + @wraps(func) + async def delete_rights(client, message, *args, **kwargs): + _, chat = get_user_chat(message) + if not chat: return + me = await pbot.get_chat_member(chat.id, client.me.id if hasattr(client, "me") and client.me else (await client.get_me()).id) + + if (me.privileges and me.privileges.can_delete_messages) or me.status == enums.ChatMemberStatus.OWNER: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] can_delete denied in {chat.id}") + if hasattr(message, "reply_text"): + await message.reply_text("I can't delete messages here!") + elif hasattr(message, "answer"): + await message.answer("I can't delete messages here!", show_alert=True) + + return delete_rights + + +async def is_user_ban_protected(chat, user_id: int, member=None) -> bool: + if ( + chat.type == enums.ChatType.PRIVATE + or user_id in DRAGONS + or user_id in DEV_USERS + or user_id in [777000, 1087968824] + ): + return True + + if not member: + try: member = await pbot.get_chat_member(chat.id, user_id) + except: return False + + return member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER) + + +async def is_user_in_chat(chat, user_id: int) -> bool: + try: + await chat.get_member(user_id) + return True + except Exception: + return False + + +def can_restrict(func): + @wraps(func) + async def restrict_rights(client, message, *args, **kwargs): + _, chat = get_user_chat(message) + if not chat: return + me = await pbot.get_chat_member(chat.id, client.me.id if hasattr(client, "me") and client.me else (await client.get_me()).id) + + if (me.privileges and me.privileges.can_restrict_members) or me.status == enums.ChatMemberStatus.OWNER: + return await func(client, message, *args, **kwargs) + else: + LOGGER.info(f"[PERM] can_restrict denied in {chat.id}") + if hasattr(message, "reply_text"): + await message.reply_text("I can't restrict people here!") + elif hasattr(message, "answer"): + await message.answer("I can't restrict people here!", show_alert=True) + + return restrict_rights + + +def user_can_ban(func): + @wraps(func) + async def user_is_banhammer(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + if not user: + if chat and chat.type != enums.ChatType.PRIVATE: + return await func(client, message, *args, **kwargs) + return + + try: + member = await pbot.get_chat_member(chat.id, user.id) + if ( + not ((member.privileges and member.privileges.can_restrict_members) or member.status == enums.ChatMemberStatus.OWNER) + and user.id not in DRAGONS + and user.id not in [777000, 1087968824] + ): + LOGGER.info(f"[PERM] user_can_ban denied for {user.id}") + if hasattr(message, "reply_text"): + await message.reply_text("😿 Sorry You can't do that") + elif hasattr(message, "answer"): + await message.answer("Sorry You can't do that", show_alert=True) + return + except Exception: + if user.id not in DRAGONS: + return + + return await func(client, message, *args, **kwargs) + + return user_is_banhammer + + +def connection_status(func): + @wraps(func) + async def connected_status(client, message, *args, **kwargs): + user, chat = get_user_chat(message) + if not user: + return await func(client, message, *args, **kwargs) + + from QueenNoxi.modules import connection + conn = await connection.connected(client, message, user.id, need_admin=False) + + if conn: + return await func(client, message, *args, **kwargs) + else: + if chat and chat.type == enums.ChatType.PRIVATE: + LOGGER.info(f"[PERM] connection_status denied for {user.id}") + if hasattr(message, "reply_text"): + await message.reply_text("Send /connect in a group first.") + elif hasattr(message, "answer"): + await message.answer("Send /connect in a group.", show_alert=True) + return + return await func(client, message, *args, **kwargs) + + return connected_status diff --git a/QueenNoxi/modules/helper_funcs/decorators.py b/QueenNoxi/modules/helper_funcs/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..1d85eda36030aec49bf63de0287d255ec5c4e0b0 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/decorators.py @@ -0,0 +1,179 @@ +from typing import List, Optional, Union + +from telegram.ext import ( + CallbackQueryHandler, + CommandHandler, + InlineQueryHandler, + MessageHandler, +) +from telegram.ext.filters import MessageFilter + +from QueenNoxi import LOGGER +from QueenNoxi import dispatcher as n +from QueenNoxi.modules.disable import DisableAbleCommandHandler, DisableAbleMessageHandler + + +class QueenNoxiTelegramHandler: + def __init__(self, n): + self._dispatcher = n + + def command( + self, + command: str, + filters: Optional[MessageFilter] = None, + admin_ok: bool = False, + pass_args: bool = False, + pass_chat_data: bool = False, + run_async: bool = True, + can_disable: bool = True, + group: Optional[Union[int, str]] = 40, + ): + def _command(func): + try: + if can_disable: + self._dispatcher.add_handler( + DisableAbleCommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + admin_ok=admin_ok, + ), + group, + ) + else: + self._dispatcher.add_handler( + CommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + ), + group, + ) + LOGGER.debug( + f"[QueenNoxiCmd] Loaded handler {command} for function {func.__name__} in group {group}" + ) + except TypeError: + if can_disable: + self._dispatcher.add_handler( + DisableAbleCommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + admin_ok=admin_ok, + pass_chat_data=pass_chat_data, + ) + ) + else: + self._dispatcher.add_handler( + CommandHandler( + command, + func, + filters=filters, + run_async=run_async, + pass_args=pass_args, + pass_chat_data=pass_chat_data, + ) + ) + LOGGER.debug( + f"[QueenNoxiCMd] Loaded handler {command} for function {func.__name__}" + ) + + return func + + return _command + + def message( + self, + pattern: Optional[str] = None, + can_disable: bool = True, + run_async: bool = True, + group: Optional[Union[int, str]] = 60, + friendly=None, + ): + def _message(func): + try: + if can_disable: + self._dispatcher.add_handler( + DisableAbleMessageHandler( + pattern, func, friendly=friendly, run_async=run_async + ), + group, + ) + else: + self._dispatcher.add_handler( + MessageHandler(pattern, func, run_async=run_async), group + ) + LOGGER.debug( + f"[QueenNoxiMsg] Loaded filter pattern {pattern} for function {func.__name__} in group {group}" + ) + except TypeError: + if can_disable: + self._dispatcher.add_handler( + DisableAbleMessageHandler( + pattern, func, friendly=friendly, run_async=run_async + ) + ) + else: + self._dispatcher.add_handler( + MessageHandler(pattern, func, run_async=run_async) + ) + LOGGER.debug( + f"[QueenNoxi-Msg] Loaded filter pattern {pattern} for function {func.__name__}" + ) + + return func + + return _message + + def callbackquery(self, pattern: str = None, run_async: bool = True): + def _callbackquery(func): + self._dispatcher.add_handler( + CallbackQueryHandler( + pattern=pattern, callback=func, run_async=run_async + ) + ) + LOGGER.debug( + f"[QueenNoxi-Callback] Loaded callbackquery handler with pattern {pattern} for function {func.__name__}" + ) + return func + + return _callbackquery + + def inlinequery( + self, + pattern: Optional[str] = None, + run_async: bool = True, + pass_user_data: bool = True, + pass_chat_data: bool = True, + chat_types: List[str] = None, + ): + def _inlinequery(func): + self._dispatcher.add_handler( + InlineQueryHandler( + pattern=pattern, + callback=func, + run_async=run_async, + pass_user_data=pass_user_data, + pass_chat_data=pass_chat_data, + chat_types=chat_types, + ) + ) + LOGGER.debug( + f"[QueenNoxiINLINE] Loaded inlinequery handler with pattern {pattern} for function {func.__name__} | PASSES " + f"USER DATA: {pass_user_data} | PASSES CHAT DATA: {pass_chat_data} | CHAT TYPES: {chat_types}" + ) + return func + + return _inlinequery + + +QueenNoxicmd = QueenNoxiTelegramHandler(n).command +QueenNoximsg = QueenNoxiTelegramHandler(n).message +QueenNoxicallback = QueenNoxiTelegramHandler(n).callbackquery +QueenNoxiinline = QueenNoxiTelegramHandler(n).inlinequery diff --git a/QueenNoxi/modules/helper_funcs/extraction.py b/QueenNoxi/modules/helper_funcs/extraction.py new file mode 100644 index 0000000000000000000000000000000000000000..96434b85266103fe38bc2277e1758ba70e08473d --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/extraction.py @@ -0,0 +1,102 @@ +from typing import List, Optional, Tuple + +from pyrogram.types import Message +from pyrogram import enums + +from QueenNoxi import LOGGER, pbot +from QueenNoxi.modules.users import get_user_id + + +async def id_from_reply(message: Message) -> Tuple[Optional[int], Optional[str]]: + prev_message = message.reply_to_message + if not prev_message: + return None, None + user_id = prev_message.from_user.id + res = message.text.split(None, 1) + if len(res) < 2: + return user_id, "" + return user_id, res[1] + + +async def extract_user(message: Message, args: List[str]) -> Optional[int]: + user_id, _ = await extract_user_and_text(message, args) + return user_id + + +async def extract_user_and_text( + message: Message, args: List[str] +) -> Tuple[Optional[int], Optional[str]]: + prev_message = message.reply_to_message + split_text = message.text.split(None, 1) + + if len(split_text) < 2: + return await id_from_reply(message) + + text_to_parse = split_text[1] + text = "" + user_id = None + + # Check for text mention entities + if message.entities: + for ent in message.entities: + if ent.type == enums.MessageEntityType.TEXT_MENTION: + user_id = ent.user.id + text = message.text[ent.offset + ent.length :].strip() + break + + if user_id: + pass + elif len(args) >= 1 and args[0][0] == "@": + user_name = args[0] + user_id = get_user_id(user_name) + if not user_id: + try: + user = await pbot.get_users(user_name) + user_id = user.id + except Exception: + await message.reply_text( + "No idea who this user is. You'll be able to interact with them if " + "you reply to that person's message instead, or forward one of that user's messages." + ) + return None, None + + res = message.text.split(None, 2) + if len(res) >= 3: + text = res[2] + + elif len(args) >= 1 and args[0].isdigit(): + user_id = int(args[0]) + res = message.text.split(None, 2) + if len(res) >= 3: + text = res[2] + + elif prev_message: + user_id, text = await id_from_reply(message) + + else: + return None, None + + + return user_id, text + + + + +def extract_text(message: Message) -> str: + return ( + message.text + or message.caption + or (message.sticker.emoji if message.sticker else None) + ) + + +async def extract_unt_fedban( + message: Message, args: List[str] +) -> Tuple[Optional[int], Optional[str]]: + # Fedban extraction is often similar or same as standard extraction + return await extract_user_and_text(message, args) + + +async def extract_user_fban(message: Message, args: List[str]) -> Optional[int]: + user_id, _ = await extract_unt_fedban(message, args) + return user_id diff --git a/QueenNoxi/modules/helper_funcs/filters.py b/QueenNoxi/modules/helper_funcs/filters.py new file mode 100644 index 0000000000000000000000000000000000000000..7b65e1b4abde1afe934ee2d1533349ad8cb4510b --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/filters.py @@ -0,0 +1,15 @@ +from pyrogram import filters +from QueenNoxi import DEMONS, DEV_USERS, DRAGONS + +class CustomFilters: + support_filter = filters.create(lambda _, __, m: bool(m.from_user and m.from_user.id in DEMONS)) + sudo_filter = filters.create(lambda _, __, m: bool(m.from_user and m.from_user.id in DRAGONS)) + dev_filter = filters.create(lambda _, __, m: bool(m.from_user and m.from_user.id in DEV_USERS)) + + @staticmethod + def mime_type(mimetype): + return filters.create(lambda _, __, m: bool(m.document and m.document.mime_type == mimetype)) + + has_text = filters.create(lambda _, __, m: bool( + m.text or m.sticker or m.photo or m.document or m.video + )) diff --git a/QueenNoxi/modules/helper_funcs/handlers.py b/QueenNoxi/modules/helper_funcs/handlers.py new file mode 100644 index 0000000000000000000000000000000000000000..fe613434208e39490090c6d9ce9c27c8a47a029c --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/handlers.py @@ -0,0 +1,44 @@ +from pyrate_limiter import ( + BucketFullException, + Duration, + Limiter, + MemoryListBucket, + RequestRate, +) +import QueenNoxi.modules.sql.blacklistusers_sql as sql +from QueenNoxi import DEMONS, DEV_USERS, DRAGONS, TIGERS, WOLVES + +class AntiSpam: + def __init__(self): + self.whitelist = ( + (DEV_USERS or []) + + (DRAGONS or []) + + (WOLVES or []) + + (DEMONS or []) + + (TIGERS or []) + ) + # 6 requests per 15 seconds, 20 per min, 100 per hour, 1000 per day + self.rates = [ + RequestRate(6, 15 * Duration.SECOND), + RequestRate(20, Duration.MINUTE), + RequestRate(100, Duration.HOUR), + RequestRate(1000, Duration.DAY), + ] + self.limiter = Limiter(*self.rates, bucket_class=MemoryListBucket) + + def check_user(self, user_id: int): + """ + Return True if user is to be ignored (spaming) else False + """ + if user_id in self.whitelist: + return False + try: + self.limiter.try_acquire(user_id) + return False + except BucketFullException: + return True + +SpamChecker = AntiSpam() + +def is_user_blacklisted(user_id: int) -> bool: + return sql.is_user_blacklisted(user_id) diff --git a/QueenNoxi/modules/helper_funcs/misc.py b/QueenNoxi/modules/helper_funcs/misc.py new file mode 100644 index 0000000000000000000000000000000000000000..180ba0b48984ac8c83d71dc7ff311a2e24161961 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/misc.py @@ -0,0 +1,120 @@ +from math import ceil +from typing import Dict, List +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + InlineQueryResultArticle, + InputTextMessageContent, +) +from QueenNoxi import NO_LOAD, pbot + +class EqInlineKeyboardButton(InlineKeyboardButton): + def __eq__(self, other): + return self.text == other.text + + def __lt__(self, other): + return self.text < other.text + + def __gt__(self, other): + return self.text > other.text + +def split_message(msg: str) -> List[str]: + if len(msg) < 4096: + return [msg] + + lines = msg.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < 4096: + small_msg += line + else: + result.append(small_msg) + small_msg = line + else: + result.append(small_msg) + + return result + +def paginate_modules(page_n: int, module_dict: Dict, prefix, chat=None) -> List: + if not chat: + modules = sorted( + [ + EqInlineKeyboardButton( + x.__mod_name__, + callback_data="{}_module({})".format( + prefix, x.__mod_name__.lower() + ), + ) + for x in module_dict.values() + ] + ) + else: + modules = sorted( + [ + EqInlineKeyboardButton( + x.__mod_name__, + callback_data="{}_module({},{})".format( + prefix, chat, x.__mod_name__.lower() + ), + ) + for x in module_dict.values() + ] + ) + + pairs = [modules[i * 3 : (i + 1) * 3] for i in range((len(modules) + 3 - 1) // 3)] + + round_num = len(modules) / 3 + calc = len(modules) - round(round_num) + if calc in [1, 2]: + pairs.append((modules[-1],)) + + max_num_pages = ceil(len(pairs) / 4) + modulo_page = page_n % max_num_pages + + if len(pairs) > 3: + pairs = pairs[modulo_page * 6: 6* (modulo_page + 1)] + [ + ( + EqInlineKeyboardButton( + "◁", callback_data="{}_prev({})".format(prefix, modulo_page) + ), + EqInlineKeyboardButton( + "• ʜᴏᴍᴇ •", callback_data="queennoxi_back" + ), + EqInlineKeyboardButton( + "▷", callback_data="{}_next({})".format(prefix, modulo_page) + ), + ) + ] + else: + pairs += [[EqInlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="queennoxi_back")]] + + return pairs + +async def send_to_list(client, send_to: list, message: str, parse_mode=None) -> None: + for user_id in set(send_to): + try: + await client.send_message(user_id, message, parse_mode=parse_mode) + except Exception: + pass + +def build_keyboard(buttons): + keyb = [] + for btn in buttons: + if btn.same_line and keyb: + keyb[-1].append(InlineKeyboardButton(btn.name, url=btn.url)) + else: + keyb.append([InlineKeyboardButton(btn.name, url=btn.url)]) + return keyb + +def revert_buttons(buttons): + res = "" + for btn in buttons: + if btn.same_line: + res += "\n[{}](buttonurl://{}:same)".format(btn.name, btn.url) + else: + res += "\n[{}](buttonurl://{})".format(btn.name, btn.url) + return res + +def is_module_loaded(name): + return name not in NO_LOAD diff --git a/QueenNoxi/modules/helper_funcs/msg_types.py b/QueenNoxi/modules/helper_funcs/msg_types.py new file mode 100644 index 0000000000000000000000000000000000000000..57dd7c85e6eca52edf70f09e2881a15ff70e936a --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/msg_types.py @@ -0,0 +1,163 @@ +from enum import IntEnum, unique +from pyrogram.types import Message +from QueenNoxi.modules.helper_funcs.string_handling import button_markdown_parser + +@unique +class Types(IntEnum): + TEXT = 0 + BUTTON_TEXT = 1 + STICKER = 2 + DOCUMENT = 3 + PHOTO = 4 + AUDIO = 5 + VOICE = 6 + VIDEO = 7 + VIDEO_NOTE = 8 + +async def get_note_type(msg: Message): + data_type = None + content = None + text = "" + raw_text = msg.text or msg.caption + args = raw_text.split(None, 2) + if len(args) < 2: + return None, None, None, None, [] + + note_name = args[1] + buttons = [] + + if len(args) >= 3: + offset = 0 # In Pyrogram, entities offsets start from 0 relative to the text they are in + text, buttons = button_markdown_parser( + args[2], + entities=msg.entities or msg.caption_entities, + offset=offset, + ) + data_type = Types.BUTTON_TEXT if buttons else Types.TEXT + + elif msg.reply_to_message: + reply = msg.reply_to_message + msgtext = reply.text or reply.caption or "" + entities = reply.entities or reply.caption_entities + + if reply.text or reply.caption: + text, buttons = button_markdown_parser(msgtext, entities=entities) + data_type = Types.BUTTON_TEXT if buttons else Types.TEXT + + if reply.sticker: + content = reply.sticker.file_id + data_type = Types.STICKER + elif reply.document: + content = reply.document.file_id + data_type = Types.DOCUMENT + elif reply.photo: + content = reply.photo.file_id + data_type = Types.PHOTO + elif reply.audio: + content = reply.audio.file_id + data_type = Types.AUDIO + elif reply.voice: + content = reply.voice.file_id + data_type = Types.VOICE + elif reply.video: + content = reply.video.file_id + data_type = Types.VIDEO + elif reply.video_note: + content = reply.video_note.file_id + data_type = Types.VIDEO_NOTE + + return note_name, text, data_type, content, buttons + +async def get_welcome_type(msg: Message): + data_type = None + content = None + text = "" + buttons = [] + + reply = msg.reply_to_message + if reply: + if reply.sticker: + content = reply.sticker.file_id + data_type = Types.STICKER + elif reply.document: + content = reply.document.file_id + text = reply.caption + data_type = Types.DOCUMENT + elif reply.photo: + content = reply.photo.file_id + text = reply.caption + data_type = Types.PHOTO + elif reply.audio: + content = reply.audio.file_id + text = reply.caption + data_type = Types.AUDIO + elif reply.voice: + content = reply.voice.file_id + text = reply.caption + data_type = Types.VOICE + elif reply.video: + content = reply.video.file_id + text = reply.caption + data_type = Types.VIDEO + elif reply.video_note: + content = reply.video_note.file_id + data_type = Types.VIDEO_NOTE + + msgtext = reply.text or reply.caption or "" + entities = reply.entities or reply.caption_entities + text, buttons = button_markdown_parser(msgtext, entities=entities) + else: + args = msg.text.split(None, 1) + if len(args) >= 2: + text, buttons = button_markdown_parser(args[1], entities=msg.entities) + + if not data_type: + data_type = Types.BUTTON_TEXT if buttons else Types.TEXT if text else None + + return text, data_type, content, buttons + +async def get_filter_type(msg: Message): + text = None + data_type = None + content = None + + if not msg.reply_to_message and msg.text and len(msg.text.split()) >= 3: + text = msg.text.split(None, 2)[2] + data_type = Types.TEXT + + elif msg.reply_to_message: + reply = msg.reply_to_message + if reply.text: + text = reply.text + data_type = Types.TEXT + elif reply.sticker: + content = reply.sticker.file_id + data_type = Types.STICKER + elif reply.document: + content = reply.document.file_id + text = reply.caption + data_type = Types.DOCUMENT + elif reply.photo: + content = reply.photo.file_id + text = reply.caption + data_type = Types.PHOTO + elif reply.audio: + content = reply.audio.file_id + text = reply.caption + data_type = Types.AUDIO + elif reply.voice: + content = reply.voice.file_id + text = reply.caption + data_type = Types.VOICE + elif reply.video: + content = reply.video.file_id + text = reply.caption + data_type = Types.VIDEO + elif reply.video_note: + content = reply.video_note.file_id + data_type = Types.VIDEO_NOTE + + return text, data_type, content + + +## diff --git a/QueenNoxi/modules/helper_funcs/regex_helper.py b/QueenNoxi/modules/helper_funcs/regex_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..f88591c6a683fd2da3d687e5871cf09cab8dfb79 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/regex_helper.py @@ -0,0 +1,27 @@ +import regex + + +def regex_searcher(regex_string, string): + try: + search = regex.search(regex_string, string, timeout=6) + except TimeoutError: + return False + except Exception: + return False + return search + + +def infinite_loop_check(regex_string): + loop_matches = [ + r"\((.{1,}[\+\*]){1,}\)[\+\*].", + r"[\(\[].{1,}\{\d(,)?\}[\)\]]\{\d(,)?\}", + r"\(.{1,}\)\{.{1,}(,)?\}\(.*\)(\+|\* |\{.*\})", + ] + for match in loop_matches: + match_1 = regex.search(match, regex_string) + if match_1: + return True + return False + + +# diff --git a/QueenNoxi/modules/helper_funcs/string_handling.py b/QueenNoxi/modules/helper_funcs/string_handling.py new file mode 100644 index 0000000000000000000000000000000000000000..1ec8b0743092db071aee9144b56b1bf3e0bf7098 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/string_handling.py @@ -0,0 +1,175 @@ +import re +import time +from typing import List +import bleach +import markdown2 +from pyrogram import enums +from pyrogram.types import MessageEntity + +MATCH_MD = re.compile( + r"\*(.*?)\*|" + r"_(.*?)_|" + r"`(.*?)`|" + r"(?<!\\)(\[.*?\])(\(.*?\))|" + r"(?P<esc>[*_`\[])" +) + +LINK_REGEX = re.compile(r"(?<!\\)\[.+?\]\((.*?)\)") +BTN_URL_REGEX = re.compile(r"(\[([^\[]+?)\]\(buttonurl:(?:/{0,2})(.+?)(:same)?\))") + +def _selective_escape(to_parse: str) -> str: + offset = 0 + for match in MATCH_MD.finditer(to_parse): + if match.group("esc"): + ent_start = match.start() + to_parse = to_parse[: ent_start + offset] + "\\" + to_parse[ent_start + offset :] + offset += 1 + return to_parse + +def markdown_parser(txt: str, entities: List[MessageEntity] = None, offset: int = 0) -> str: + if not entities: + return _selective_escape(txt) + + prev = 0 + res = "" + for ent in entities: + if ent.offset < -offset: + continue + + start = ent.offset + offset + end = ent.offset + offset + ent.length + ent_text = txt[start:end] + + if ent.type in (enums.MessageEntityType.CODE, enums.MessageEntityType.URL, enums.MessageEntityType.TEXT_LINK): + if ent.type == enums.MessageEntityType.URL: + if any(match.start(1) <= start and end <= match.end(1) for match in LINK_REGEX.finditer(txt)): + continue + else: + res += _selective_escape(txt[prev:start]) + ent_text + elif ent.type == enums.MessageEntityType.CODE: + res += _selective_escape(txt[prev:start]) + "`" + ent_text + "`" + elif ent.type == enums.MessageEntityType.TEXT_LINK: + res += _selective_escape(txt[prev:start]) + f"[{ent_text}]({ent.url})" + prev = end + + res += _selective_escape(txt[prev:]) + return res + +def button_markdown_parser(txt: str, entities: List[MessageEntity] = None, offset: int = 0) -> (str, List): + markdown_note = markdown_parser(txt, entities, offset) + prev = 0 + note_data = "" + buttons = [] + for match in BTN_URL_REGEX.finditer(markdown_note): + n_escapes = 0 + to_check = match.start(1) - 1 + while to_check > 0 and markdown_note[to_check] == "\\": + n_escapes += 1 + to_check -= 1 + + if n_escapes % 2 == 0: + buttons.append((match.group(2), match.group(3), bool(match.group(4)))) + note_data += markdown_note[prev : match.start(1)] + prev = match.end(1) + else: + note_data += markdown_note[prev : match.start(1) - 1] + prev = match.start(1) - 1 + else: + note_data += markdown_note[prev:] + + return note_data, buttons + +def escape_invalid_curly_brackets(text: str, valids: List[str]) -> str: + new_text = "" + idx = 0 + while idx < len(text): + if text[idx] == "{": + if idx + 1 < len(text) and text[idx + 1] == "{": + idx += 2 + new_text += "{{{{" + continue + else: + success = False + for v in valids: + if text[idx:].startswith("{" + v + "}"): + success = True + break + if success: + new_text += text[idx : idx + len(v) + 2] + idx += len(v) + 2 + continue + else: + new_text += "{{" + elif text[idx] == "}": + if idx + 1 < len(text) and text[idx + 1] == "}": + idx += 2 + new_text += "}}}}" + continue + else: + new_text += "}}" + else: + new_text += text[idx] + idx += 1 + return new_text + +SMART_OPEN = "“" +SMART_CLOSE = "”" +START_CHAR = ("'", '"', SMART_OPEN) + +def split_quotes(text: str) -> List: + if not any(text.startswith(char) for char in START_CHAR): + return text.split(None, 1) + counter = 1 + while counter < len(text): + if text[counter] == "\\": + counter += 1 + elif text[counter] == text[0] or (text[0] == SMART_OPEN and text[counter] == SMART_CLOSE): + break + counter += 1 + else: + return text.split(None, 1) + + key = remove_escapes(text[1:counter].strip()) + rest = text[counter + 1 :].strip() + if not key: + key = text[0] + text[0] + return list(filter(None, [key, rest])) + +def remove_escapes(text: str) -> str: + res = "" + is_escaped = False + for counter in range(len(text)): + if is_escaped: + res += text[counter] + is_escaped = False + elif text[counter] == "\\": + is_escaped = True + else: + res += text[counter] + return res + +async def extract_time(message, time_val): + if any(time_val.endswith(unit) for unit in ("m", "h", "d")): + unit = time_val[-1] + time_num = time_val[:-1] + if not time_num.isdigit(): + await message.reply_text("Invalid time amount specified.") + return None + + if unit == "m": + return int(time.time() + int(time_num) * 60) + elif unit == "h": + return int(time.time() + int(time_num) * 60 * 60) + elif unit == "d": + return int(time.time() + int(time_num) * 24 * 60 * 60) + else: + await message.reply_text(f"Invalid time type specified. Expected m,h, or d, got: {time_val[-1]}") + return None + +def markdown_to_html(text): + text = text.replace("*", "**").replace("`", "```").replace("~", "~~") + _html = markdown2.markdown(text, extras=["strike", "underline"]) + return bleach.clean(_html, tags=["strong", "em", "a", "code", "pre", "strike", "u"], strip=True)[:-1] + +def escape_markdown(text: str) -> str: + return _selective_escape(text) diff --git a/QueenNoxi/modules/helper_funcs/telethn/__init__.py b/QueenNoxi/modules/helper_funcs/telethn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1b4161e949661a19787e73d424c06aff1f8d7f29 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/telethn/__init__.py @@ -0,0 +1,7 @@ +from QueenNoxi import DEMONS, DEV_USERS, DRAGONS, TIGERS, WOLVES, telethn + +IMMUNE_USERS = DRAGONS + WOLVES + DEMONS + TIGERS + DEV_USERS + +IMMUNE_USERS = ( + list(DRAGONS) + list(WOLVES) + list(DEMONS) + list(TIGERS) + list(DEV_USERS) +) diff --git a/QueenNoxi/modules/helper_funcs/telethn/chatstatus.py b/QueenNoxi/modules/helper_funcs/telethn/chatstatus.py new file mode 100644 index 0000000000000000000000000000000000000000..36b063a8e26a131b4bfe7a7dd5fc5ccddb2b93d1 --- /dev/null +++ b/QueenNoxi/modules/helper_funcs/telethn/chatstatus.py @@ -0,0 +1,110 @@ +from telethon.tl.types import ChannelParticipantsAdmins + +from QueenNoxi import DRAGONS +from QueenNoxi.modules.helper_funcs.telethn import IMMUNE_USERS, telethn + + +async def user_is_ban_protected(user_id: int, message): + status = False + if message.is_private or user_id in (IMMUNE_USERS): + return True + + async for user in telethn.iter_participants( + message.chat_id, filter=ChannelParticipantsAdmins + ): + if user_id == user.id: + status = True + break + return status + + +async def user_is_admin(user_id: int, message): + status = False + if message.is_private: + return True + + async for user in telethn.iter_participants( + message.chat_id, filter=ChannelParticipantsAdmins + ): + if user_id == user.id or user_id in DRAGONS: + status = True + break + return status + + +async def is_user_admin(user_id: int, chat_id): + status = False + async for user in telethn.iter_participants( + chat_id, filter=ChannelParticipantsAdmins + ): + if user_id == user.id or user_id in DRAGONS: + status = True + break + return status + + +async def queennoxi_is_admin(chat_id: int): + status = False + queennoxi = await telethn.get_me() + async for user in telethn.iter_participants( + chat_id, filter=ChannelParticipantsAdmins + ): + if queennoxi.id == user.id: + status = True + break + return status + + +async def is_user_in_chat(chat_id: int, user_id: int): + status = False + async for user in telethn.iter_participants(chat_id): + if user_id == user.id: + status = True + break + return status + + +async def can_change_info(message): + status = False + if message.chat.admin_rights: + status = message.chat.admin_rights.change_info + return status + + +async def can_ban_users(message): + status = False + if message.chat.admin_rights: + status = message.chat.admin_rights.ban_users + return status + + +async def can_pin_messages(message): + status = False + if message.chat.admin_rights: + status = message.chat.admin_rights.pin_messages + return status + + +async def can_invite_users(message): + status = False + if message.chat.admin_rights: + status = message.chat.admin_rights.invite_users + return status + + +async def can_add_admins(message): + status = False + if message.chat.admin_rights: + status = message.chat.admin_rights.add_admins + return status + + +async def can_delete_messages(message): + + if message.is_private: + return True + elif message.chat.admin_rights: + status = message.chat.admin_rights.delete_messages + return status + else: + return False diff --git a/QueenNoxi/modules/json.py b/QueenNoxi/modules/json.py new file mode 100644 index 0000000000000000000000000000000000000000..640058f1401f60094a8f097ba4adf325e748b0c4 --- /dev/null +++ b/QueenNoxi/modules/json.py @@ -0,0 +1,31 @@ +import io +from pyrogram import filters +from QueenNoxi import pbot as app + +@app.on_message(filters.command("json")) +async def _(_, message): + if getattr(message, "forward_from", None): + return + if message.chat.type.name != "PRIVATE": + try: + member = await app.get_chat_member(message.chat.id, message.from_user.id) + if member.privileges is None: + await message.reply("🥴 ɴᴇᴇᴅ ᴀᴅᴍɪɴ ᴩᴏᴡᴇʀ ᴛᴏ ᴜsᴇ ᴛʜɪs ɪɴ ɢʀᴏᴜᴩs, ʙᴜᴛ ʏᴏᴜ ᴄᴀɴ ᴜsᴇ ɪᴛ ɪɴ ᴍʏ ᴩᴍ.") + return + except Exception: + return + + reply_to_id = message.reply_to_message.id if message.reply_to_message else message.id + the_real_message = str(message.reply_to_message) if message.reply_to_message else str(message) + + if len(the_real_message) > 4095: + with io.BytesIO(str.encode(the_real_message)) as out_file: + out_file.name = "json.text" + await app.send_document( + message.chat.id, + document=out_file, + reply_to_message_id=reply_to_id, + ) + await message.delete() + else: + await message.reply("`{}`".format(the_real_message)) diff --git a/QueenNoxi/modules/karma.py b/QueenNoxi/modules/karma.py new file mode 100644 index 0000000000000000000000000000000000000000..ead4480f8c5fe5e5d30d0994b3119a8886d035d7 --- /dev/null +++ b/QueenNoxi/modules/karma.py @@ -0,0 +1,167 @@ +import asyncio + +from pyrogram import filters + +from QueenNoxi import OWNER_ID, pbot +from QueenNoxi.utils.admins import can_change_info +from QueenNoxi.utils.errors import capture_err +from QueenNoxi.utils.mongo import ( + alpha_to_int, + get_karma, + get_karmas, + int_to_alpha, + is_karma_on, + karma_off, + karma_on, + update_karma, +) + +regex_upvote =r"^(\+|\+\+|\+1|thx|thanx|thanks|🖤|❣️|💝|💖|💕|❤️|💘|cool|good|\👍|baby|queennoxi|thank you|gud|thankyou|love|pro)$" +regex_downvote = r"^(\-|\-\-|\-1|\👎|💔|noob|weak|fuck off|nub|gey|mf)$" + + +karma_positive_group = 3 +karma_negative_group = 4 + + +@pbot.on_message( + filters.text + & filters.incoming + & filters.reply + & filters.regex(regex_upvote) + & ~filters.via_bot + & ~filters.bot, + group=karma_positive_group, +) +@capture_err +async def upvote(_, message): + if not await is_karma_on(message.chat.id): + return + if not message.reply_to_message.from_user: + return + if not message.from_user: + return + if message.reply_to_message.from_user.id == OWNER_ID: + await message.reply_text( + "ᴡᴇʟʟ, ʜᴇ's ᴍʏ ᴏᴡɴᴇʀ. sᴏ ʏᴇᴀʜ, ʜᴇ ɪs ᴀʟᴡᴀʏs ʀɪɢʜᴛ ᴀɴᴅ ᴇᴠᴇʀʏᴏɴᴇ ᴋɴᴏᴡs ʜᴇ ɪs ᴀ ɢᴏᴏᴅ ᴘᴇʀsᴏɴ ᴛᴏᴏ." + ) + return + if message.reply_to_message.from_user.id == message.from_user.id: + return + chat_id = message.chat.id + user_id = message.reply_to_message.from_user.id + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma + 1 + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"ɪɴᴄʀᴇᴍᴇɴᴛᴇᴅ ᴋᴀʀᴍᴀ ᴏғ {user_mention} ʙʏ 1.\n**ᴛᴏᴛᴀʟ ᴩᴏɪɴᴛs :** {karma}" + ) + + +@pbot.on_message( + filters.text + & filters.incoming + & filters.reply + & filters.regex(regex_downvote) + & ~filters.via_bot + & ~filters.bot, + group=karma_negative_group, +) +@capture_err +async def downvote(_, message): + if not is_karma_on(message.chat.id): + return + if not message.reply_to_message.from_user: + return + if not message.from_user: + return + if message.reply_to_message.from_user.id == OWNER_ID: + await message.reply_text( + "ᴡᴛғ !, ʏᴏᴜ ᴅᴏɴ'ᴛ ᴀɢʀᴇᴇ ᴡɪᴛʜ ᴍʏ ᴏᴡɴᴇʀ. ʟᴏᴏᴋs ʟɪᴋᴇ ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀɴ ɢᴏᴏᴅ ᴩᴇʀsᴏɴ." + ) + return + if message.reply_to_message.from_user.id == message.from_user.id: + return + chat_id = message.chat.id + user_id = message.reply_to_message.from_user.id + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma - 1 + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"ᴅᴇᴄʀᴇᴍᴇɴᴛᴇᴅ ᴋᴀʀᴍᴀ ᴏғ {user_mention} ʙʏ 1.\n**ᴛᴏᴛᴀʟ ᴩᴏɪɴᴛs :** {karma}" + ) + + +@pbot.on_message(filters.command("karmastat") & filters.group) +@capture_err +async def karma(_, message): + chat_id = message.chat.id + if not message.reply_to_message: + m = await message.reply_text("Analyzing Karma...Will Take 10 Seconds") + karma = await get_karmas(chat_id) + if not karma: + await m.edit("No karma in DB for this chat.") + return + msg = f"**Karma list of {message.chat.title}:- **\n" + limit = 0 + karma_dicc = {} + for i in karma: + user_id = await alpha_to_int(i) + user_karma = karma[i]["karma"] + karma_dicc[str(user_id)] = user_karma + karma_arranged = dict( + sorted(karma_dicc.items(), key=lambda item: item[1], reverse=True) + ) + if not karma_dicc: + await m.edit("No karma in DB for this chat.") + return + for user_idd, karma_count in karma_arranged.items(): + if limit > 9: + break + try: + user = await app.get_users(int(user_idd)) + await asyncio.sleep(0.8) + except Exception: + continue + first_name = user.first_name + if not first_name: + continue + username = user.username + msg += f"**{karma_count}** {(first_name[0:12] + '...') if len(first_name) > 12 else first_name} `{('@' + username) if username else user_idd}`\n" + limit += 1 + await m.edit(msg) + else: + user_id = message.reply_to_message.from_user.id + karma = await get_karma(chat_id, await int_to_alpha(user_id)) + karma = karma["karma"] if karma else 0 + await message.reply_text(f"**ᴛᴏᴛᴀʟ ᴩᴏɪɴᴛs :** {karma}") + + +@pbot.on_message(filters.command("karma") & ~filters.private) +@can_change_info +async def captcha_state(_, message): + usage = "**Usage:**\n/karma [ON|OFF]" + if len(message.command) != 2: + return await message.reply_text(usage) + state = message.text.split(None, 1)[1].strip() + state = state.lower() + if state == "on": + await karma_on(message.chat.id) + await message.reply_text("Enabled karma system.") + elif state == "off": + await karma_off(message.chat.id) + await message.reply_text("Disabled karma system.") + else: + await message.reply_text(usage) diff --git a/QueenNoxi/modules/locks.py b/QueenNoxi/modules/locks.py new file mode 100644 index 0000000000000000000000000000000000000000..f3678e3275f7dd8fe493b26df043c200ca9cd602 --- /dev/null +++ b/QueenNoxi/modules/locks.py @@ -0,0 +1,168 @@ +import html +from alphabet_detector import AlphabetDetector +from pyrogram import filters, Client, enums +from pyrogram.types import ( + ChatPermissions, + Message, +) +from pyrogram.errors import BadRequest + +import QueenNoxi.modules.sql.locks_sql as sql +from QueenNoxi import pbot, LOGGER +from QueenNoxi.modules.helper_funcs.chat_status import ( + can_restrict, + connection_status, + user_admin, + is_user_admin, +) + +ad = AlphabetDetector() + +LOCK_TYPES = { + "audio": filters.audio, + "voice": filters.voice, + "contact": filters.contact, + "video": filters.video, + "videonote": filters.video_note, + "document": filters.document, + "sticker": filters.sticker, + "animation": filters.animation, + "url": filters.regex(r"(?i)http|t.me"), + "bots": filters.new_chat_members, + "forward": filters.forwarded, + "game": filters.game, + "location": filters.location, +} + +@pbot.on_message(filters.command("locktypes") & filters.group) +async def locktypes(client: Client, message: Message): + await message.reply_text( + "Available lock types:\n\n" + "\n".join([f" • {l}" for l in LOCK_TYPES.keys()]) + ) + +@pbot.on_message(filters.command("lock") & filters.group) +@connection_status +@user_admin +@can_restrict +async def lock(client: Client, message: Message): + args = message.command + if len(args) < 2: + await message.reply_text("You didn't specify what to lock!") + return + + lock_type = args[1].lower() + chat_id = message.chat.id + + if lock_type == "all": + await client.set_chat_permissions(chat_id, ChatPermissions(can_send_messages=False)) + await message.reply_text("Locked all messages!") + elif lock_type in LOCK_TYPES: + sql.update_lock(chat_id, lock_type, True) + await message.reply_text(f"Locked {lock_type}!") + else: + await message.reply_text("Invalid lock type!") + +@pbot.on_message(filters.command("unlock") & filters.group) +@connection_status +@user_admin +@can_restrict +async def unlock(client: Client, message: Message): + args = message.command + if len(args) < 2: + await message.reply_text("You didn't specify what to unlock!") + return + + lock_type = args[1].lower() + chat_id = message.chat.id + + if lock_type == "all": + await client.set_chat_permissions( + chat_id, + ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + can_send_polls=True, + ) + ) + await message.reply_text("Unlocked all messages!") + elif lock_type in LOCK_TYPES: + sql.update_lock(chat_id, lock_type, False) + await message.reply_text(f"Unlocked {lock_type}!") + else: + await message.reply_text("Invalid lock type!") + +@pbot.on_message(filters.group, group=1) +async def del_lockables(client: Client, message: Message): + if not message.chat or message.chat.type in (enums.ChatType.PRIVATE, enums.ChatType.CHANNEL): + return + + if await is_user_admin(message.chat, message.from_user.id): + return + + chat_id = message.chat.id + locks = sql.get_locks(chat_id) + if not locks: + return + + should_del = False + if locks.audio and message.audio: should_del = True + elif locks.voice and message.voice: should_del = True + elif locks.contact and message.contact: should_del = True + elif locks.video and message.video: should_del = True + elif locks.videonote and message.video_note: should_del = True + elif locks.document and message.document: should_del = True + elif locks.sticker and message.sticker: should_del = True + elif locks.animation and message.animation: should_del = True + elif locks.forward and (message.forward_from or message.forward_from_chat): should_del = True + elif locks.location and message.location: should_del = True + elif locks.url and (message.entities and any(e.type in (enums.MessageEntityType.URL, enums.MessageEntityType.TEXT_LINK) for e in message.entities)): should_del = True + + if should_del: + try: + await message.delete() + except Exception: + pass + +@pbot.on_message(filters.command("locks") & filters.group) +@connection_status +async def list_locks(client: Client, message: Message): + chat_id = message.chat.id + locks = sql.get_locks(chat_id) + + res = "<b>Current locks in this chat:</b>\n" + if locks: + if locks.audio: res += " • audio\n" + if locks.voice: res += " • voice\n" + if locks.contact: res += " • contact\n" + if locks.video: res += " • video\n" + if locks.videonote: res += " • videonote\n" + if locks.document: res += " • document\n" + if locks.sticker: res += " • sticker\n" + if locks.animation: res += " • animation\n" + if locks.url: res += " • url\n" + if locks.forward: res += " • forward\n" + if locks.bots: res += " • bots\n" + if locks.game: res += " • game\n" + if locks.location: res += " • location\n" + + perms = (await client.get_chat(chat_id)).permissions + res += "\n<b>Chat permissions:</b>\n" + res += f" • messages: {perms.can_send_messages}\n" + res += f" • media: {perms.can_send_media_messages}\n" + res += f" • polls: {perms.can_send_polls}\n" + res += f" • other: {perms.can_send_other_messages}\n" + res += f" • previews: {perms.can_add_web_page_previews}\n" + + await message.reply_text(res) + +__mod_name__ = "Locks" +__help__ = """ + • `/lock <type>`: Lock a certain type of message. + • `/unlock <type>`: Unlock a certain type of message. + • `/locks`: List active locks. + • `/locktypes`: List available lock types. + +Types: `audio`, `voice`, `contact`, `video`, `videonote`, `document`, `sticker`, `animation`, `url`, `forward`, `bots`, `game`, `location`, `all`. +""" diff --git a/QueenNoxi/modules/log_channel.py b/QueenNoxi/modules/log_channel.py new file mode 100644 index 0000000000000000000000000000000000000000..7bf1ac835c5d8bb0d38ac713a981aabf5942e88b --- /dev/null +++ b/QueenNoxi/modules/log_channel.py @@ -0,0 +1,168 @@ +from datetime import datetime +from functools import wraps + +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import EVENT_LOGS, LOGGER, pbot +from QueenNoxi.modules.helper_funcs.chat_status import user_admin +from QueenNoxi.modules.sql import log_channel_sql as sql + + +def loggable(func): + @wraps(func) + async def log_action(client: Client, message, *args, **kwargs): + result = await func(client, message, *args, **kwargs) + is_cb = hasattr(message, "data") and hasattr(message, "message") + chat = message.message.chat if is_cb and message.message else message.chat + + if result: + datetime_fmt = "%H:%M - %d-%m-%Y" + result += f"\n<b>Event Stamp</b>: <code>{datetime.utcnow().strftime(datetime_fmt)}</code>" + + if chat.type == enums.ChatType.SUPERGROUP and chat.username: + result += f'\n<b>Link:</b> <a href="https://t.me/{chat.username}/{message.id}">click here</a>' + + log_chat = sql.get_chat_log_channel(chat.id) + if log_chat: + await send_log(client, int(log_chat), chat.id, result) + + return result + + return log_action + + +def gloggable(func): + @wraps(func) + async def glog_action(client: Client, message, *args, **kwargs): + result = await func(client, message, *args, **kwargs) + is_cb = hasattr(message, "data") and hasattr(message, "message") + chat = message.message.chat if is_cb and message.message else message.chat + + if result: + datetime_fmt = "%H:%M - %d-%m-%Y" + result += f"\n<b>Event Stamp</b>: <code>{datetime.utcnow().strftime(datetime_fmt)}</code>" + + if chat.type == enums.ChatType.SUPERGROUP and chat.username: + result += f'\n<b>Link:</b> <a href="https://t.me/{chat.username}/{message.id}">click here</a>' + + log_chat = EVENT_LOGS + if log_chat: + await send_log(client, int(log_chat), chat.id, result) + + return result + + return glog_action + + +async def send_log(client: Client, log_chat_id: int, orig_chat_id: int, result: str): + try: + # Attempt to resolve the peer first to avoid CHANNEL_INVALID + try: + await client.get_chat(log_chat_id) + except Exception: + pass + + await client.send_message( + log_chat_id, + result, + disable_web_page_preview=True, + ) + except Exception as excp: + if "CHAT_WRITE_FORBIDDEN" in str(excp) or "CHANNEL_INVALID" in str(excp): + LOGGER.warning(f"Logging disabled for {orig_chat_id} due to: {excp}") + sql.stop_chat_logging(orig_chat_id) + else: + LOGGER.warning(f"Could not send log: {excp}") + + +@pbot.on_message(filters.command("logchannel") & filters.group) +@user_admin +async def logging(client: Client, message: Message): + chat = message.chat + log_channel = sql.get_chat_log_channel(chat.id) + if log_channel: + try: + log_channel_info = await client.get_chat(int(log_channel)) + await message.reply_text( + f"This group has all its logs sent to: {log_channel_info.title} (`{log_channel}`)" + ) + except Exception: + await message.reply_text(f"This group has its logs sent to channel: `{log_channel}`") + else: + await message.reply_text("No log channel has been set for this group!") + + +@pbot.on_message(filters.command("setlog") & filters.group) +@user_admin +async def setlog(client: Client, message: Message): + if message.forward_from_chat and message.forward_from_chat.type == enums.ChatType.CHANNEL: + sql.set_chat_log_channel(message.chat.id, message.forward_from_chat.id) + try: + await message.delete() + except: + pass + + try: + await client.send_message( + message.forward_from_chat.id, + f"This channel has been set as the log channel for {message.chat.title}.", + ) + except Exception as e: + LOGGER.warning(f"Error in setlog: {e}") + + await message.reply_text("Successfully set log channel!") + else: + await message.reply_text( + "The steps to set a log channel are:\n" + " - Add bot to the desired channel as an admin\n" + " - Send /setlog to the channel\n" + " - Forward the /setlog to the group\n" + ) + + +@pbot.on_message(filters.command("unsetlog") & filters.group) +@user_admin +async def unsetlog(client: Client, message: Message): + log_channel = sql.stop_chat_logging(message.chat.id) + if log_channel: + try: + await client.send_message(int(log_channel), f"Channel has been unlinked from {message.chat.title}") + except: + pass + await message.reply_text("Log channel has been unset.") + else: + await message.reply_text("No log channel has been set yet!") + + +def __stats__(): + return f"• {sql.num_logchannels()} ʟᴏɢ ᴄʜᴀɴɴᴇʟs sᴇᴛ." + + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + + +async def __chat_settings__(chat_id, user_id): + log_channel = sql.get_chat_log_channel(chat_id) + if log_channel: + try: + log_channel_info = await pbot.get_chat(int(log_channel)) + return f"This group has all its logs sent to: {log_channel_info.title} (`{log_channel}`)" + except: + return f"This group has its logs sent to channel ID: `{log_channel}`" + return "No log channel is set for this group!" + + +__mod_name__ = "Logs" +__help__ = """ +*Admins only:* + ❍ /logchannel: Get log channel info + ❍ /setlog: Set the log channel. + ❍ /unsetlog: Unset the log channel. + +Setting the log channel is done by: +1. Adding the bot to the desired channel as an admin. +2. Sending /setlog in the channel. +3. Forwarding that /setlog message to the group. +""" diff --git a/QueenNoxi/modules/logo.py b/QueenNoxi/modules/logo.py new file mode 100644 index 0000000000000000000000000000000000000000..268a9348003f7c4f4c20cb3b47acf1be364c5472 --- /dev/null +++ b/QueenNoxi/modules/logo.py @@ -0,0 +1,312 @@ +import glob +import io +import os +import random + +import requests +from PIL import Image, ImageDraw, ImageFont +from pyrogram import filters +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from QueenNoxi import pbot as app +from QueenNoxi import BOT_USERNAME, OWNER_ID, BOT_NAME, SUPPORT_CHAT +LOGO_LINKS = [ + "https://telegra.ph/file/d1838efdafce9fe611d0c.jpg", + "https://telegra.ph/file/c1ff2d5ec5e1b5bd1b200.jpg", + "https://telegra.ph/file/08c5fbe14cc4b13d1de05.jpg", + "https://telegra.ph/file/66614a049d74fe2a220dc.jpg", + "https://telegra.ph/file/9cc1e4b24bfa13873bd66.jpg", + "https://telegra.ph/file/792d38bd74b0c3165c11d.jpg", + "https://telegra.ph/file/e1031e28a4aa4d8bd7c9b.jpg", + "https://telegra.ph/file/2be9027c55b5ed463fc18.jpg", + "https://telegra.ph/file/9fd71f8d08158d0cc393c.jpg", + "https://telegra.ph/file/627105074f0456f42058b.jpg", + "https://telegra.ph/file/62b712f741382d3c171cd.jpg", + "https://telegra.ph/file/496651e0d5e4d22b8f72d.jpg", + "https://telegra.ph/file/6619d0eee2c35e022ee74.jpg", + "https://telegra.ph/file/f72fcb27c9b1e762d184b.jpg", + "https://telegra.ph/file/01eac0fe1a722a864d7de.jpg", + "https://telegra.ph/file/bdcb746fbfdf38f812873.jpg", + "https://telegra.ph/file/d13e036a129df90651deb.jpg", + "https://telegra.ph/file/ab6715ce9a63523bd0219.jpg", + "https://telegra.ph/file/c243f4e80ebf0110f9f00.jpg", + "https://telegra.ph/file/ff9053f2c7bfb2badc99e.jpg", + "https://telegra.ph/file/00b9ebbb816285d9a59f9.jpg", + "https://telegra.ph/file/ad92e1c829d14afa25cf2.jpg", + "https://telegra.ph/file/58d45cc3374e7b28a1e67.jpg", + "https://telegra.ph/file/4140a0b3f27c302fd81cb.jpg", + "https://telegra.ph/file/c4db2b5c84c1d90f5ac8a.jpg", + "https://telegra.ph/file/c0da5080a3ff7643ddeb4.jpg", + "https://telegra.ph/file/79fad473ffe888ed771b2.jpg", + "https://telegra.ph/file/eafd526d9dcc164d7269f.jpg", + "https://telegra.ph/file/98b50e8424dd2be9fc127.jpg", + "https://telegra.ph/file/c1ad29c189162a1404749.jpg", + "https://telegra.ph/file/2d288450ebecc500addbd.jpg", + "https://telegra.ph/file/9715353976a99becd7632.jpg", + "https://telegra.ph/file/87670b02a1004bc02bd8d.jpg", + "https://telegra.ph/file/70789cd69114939a78242.jpg", + "https://telegra.ph/file/1566bd334f00645cfa993.jpg", + "https://telegra.ph/file/9727c37bb8c633208b915.jpg", + "https://telegra.ph/file/27467ef55fab117ccb278.jpg", + "https://telegra.ph/file/b9c62ff7810d9e84e9e2c.jpg", + "https://telegra.ph/file/87d22f2c95413059dda4e.jpg", + "https://telegra.ph/file/e528a731accbcdea140e3.jpg", + "https://telegra.ph/file/ee3f20c3ce71dc37fecb2.jpg", + "https://telegra.ph/file/a049f78377a5b8257294d.jpg", + "https://telegra.ph/file/54d22d39ea89423b7533f.jpg", + "https://telegra.ph/file/d90baa59b6fe2bc3091d3.jpg", + "https://telegra.ph/file/b9b3f80dc4635faaeb472.jpg", + "https://telegra.ph/file/d64be0a98f441a33d2aef.jpg", + "https://telegra.ph/file/e2c59ac97a900bab5ad7d.jpg", + "https://telegra.ph/file/41baf461b0a34f1a881a9.jpg", + "https://telegra.ph/file/8d4082052b4bd0a8cc862.jpg", + "https://telegra.ph/file/e7d6e0c511137ad67d843.jpg", + "https://telegra.ph/file/d7b97ea806d4a905b71c4.jpg", + "https://telegra.ph/file/6bec48ea2c96cf3d668a4.jpg", + "https://telegra.ph/file/aa64389b70e0de02d18c5.jpg", + "https://telegra.ph/file/2f75d964a59a3a4ae90e0.jpg", + "https://telegra.ph/file/f408df72c57cfc05e734f.jpg", + "https://telegra.ph/file/9d88d9dfb50106bc43c91.jpg", + "https://telegra.ph/file/a5a6e0f9d172fa386621e.jpg", + "https://telegra.ph/file/b0fc771c91409ee5cd4dc.jpg", + "https://telegra.ph/file/b0fc771c91409ee5cd4dc.jpg", + "https://telegra.ph/file/f75e59ebd4059f394479e.jpg", + "https://telegra.ph/file/fc0308f59023d0c997166.jpg", + "https://telegra.ph/file/7e1c04947f6afb6cdf25c.jpg", + "https://telegra.ph/file/6279bb4be7e48da194353.jpg", + "https://telegra.ph/file/616784fcd89f13e789685.jpg", + "https://telegra.ph/file/803e7dd9fafdb086bce4a.jpg", + "https://telegra.ph/file/d7338861b7f996ec9d40d.jpg", + "https://telegra.ph/file/828730cd4d73333eaf129.jpg", + "https://telegra.ph/file/36c9321161d49c4b3d671.jpg", + "https://telegra.ph/file/ebeae90b99fe482d11784.jpg", + "https://telegra.ph/file/70f38f92fe8d3060a31e4.jpg", + "https://telegra.ph/file/db12cf905f557487abc60.jpg", + "https://telegra.ph/file/0f9be531164c927ded8ec.jpg", + "https://telegra.ph/file/57fb7a6df3d666878c6f3.jpg", + "https://telegra.ph/file/242930d9f7aaa0b0729fd.jpg", + "https://telegra.ph/file/883f255792d2c2ebdd5f5.jpg", + "https://telegra.ph/file/36a9c0c26967edf90d42d.jpg", + "https://telegra.ph/file/03bdaf253c43fc97adbbe.jpg", + "https://telegra.ph/file/5826715ff0895a5321d2d.jpg", + "https://telegra.ph/file/74807bfbc85057899ea8d.png", + "https://telegra.ph/file/e390f7531557c12379acb.jpg", + "https://telegra.ph/file/0b83432e72bb0ce0ed0f1.jpg", + "https://telegra.ph/file/23276d7f831611e347a7c.jpg", + "https://telegra.ph/file/109789c7dcc615c6731fa.jpg", + "https://telegra.ph/file/127ef2c311b42b2dbfb62.jpg", + "https://telegra.ph/file/bfd7fcd13b2c353030ef0.jpg", + "https://telegra.ph/file/0f7773c27b1379e2f3bea.jpg", + "https://telegra.ph/file/4606e5c76a4a6c893a721.png", + "https://telegra.ph/file/f46c4569d77d9a6be6aed.jpg", + "https://telegra.ph/file/2b4718637a7396e3b23d9.jpg", + "https://telegra.ph/file/40bce3c8e8ae3cd0198b9.jpg", + "https://telegra.ph/file/ac61cfac3290ed635f8cc.jpg", + "https://telegra.ph/file/55313171c70692e838451.jpg", + "https://telegra.ph/file/f503ce00794cadbdacdd2.jpg", + "https://telegra.ph/file/2153d9fad3613041fcd28.jpg", + "https://telegra.ph/file/6a7a790fe964c8c264b61.jpg", + "https://telegra.ph/file/103d2f4b7b1088890ae24.jpg", + "https://telegra.ph/file/63501bb4f1de53a81dba1.jpg", + "https://telegra.ph/file/00fdb4e3a06a6f6e81c35.jpg", + "https://telegra.ph/file/e2fbfce637048d2e042da.jpg", + "https://telegra.ph/file/29d3c7c297c40a17cde4b.jpg", + "https://telegra.ph/file/97c7aa91c51f72f82c2d9.jpg", + "https://telegra.ph/file/0096988891ba9b884d2dd.jpg", + "https://telegra.ph/file/12cb5cb6512b754deb92d.jpg", + "https://telegra.ph/file/38387c8384879e0ddb803.jpg", + "https://telegra.ph/file/3353253a27522219cc1ce.jpg", + "https://telegra.ph/file/daae7def66cb1d1aefa23.jpg", + "https://telegra.ph/file/e5fe618ad651777061c54.jpg", + "https://telegra.ph/file/3c56aa160ec242b1670eb.jpg", + "https://telegra.ph/file/0794ddfefdc770646c478.jpg", + "https://telegra.ph/file/05bc05a4b878e54ed3b20.jpg", + "https://telegra.ph/file/ef7ffbd3839645e33a0ec.jpg", + "https://telegra.ph/file/1daa50b9d3e26a5509cc2.png", + "https://telegra.ph/file/510600a5b93d83ce048f3.jpg", + "https://telegra.ph/file/0ede8bd4788327111ecbf.jpg", + "https://telegra.ph/file/e9f546797e42e821a91e1.jpg", + "https://telegra.ph/file/fc7fbefe92599bd79d038.jpg", + "https://telegra.ph/file/b88d6e78e206eb73e2e54.jpg", + "https://telegra.ph/file/48f8c62829953e82441e8.jpg", + "https://telegra.ph/file/56f7b34cae98a491e2b35.jpg", + "https://telegra.ph/file/9c23b4302926d40c46e12.jpg", + "https://telegra.ph/file/9bf850ea98a2b252ff233.jpg", + "https://telegra.ph/file/e764f0b3e2ecc56167803.jpg", + "https://telegra.ph/file/289f9cebe37f31a943f98.jpg", + "https://telegra.ph/file/0c647be0f5a48d576d692.jpg", + "https://telegra.ph/file/41c5b44c4f5978828b5b5.jpg", + "https://telegra.ph/file/9cdce279bdf240a933c14.jpg", + "https://telegra.ph/file/f20424687f94e9c285133.jpg", + "https://telegra.ph/file/e7858eb025e1ddb2f6267.jpg", + "https://telegra.ph/file/3e984aa5ab96df166f2a4.jpg", + "https://telegra.ph/file/e43e28aa952eaee6a5315.jpg", + "https://telegra.ph/file/6d222dcaf9ba1072c6062.jpg", + "https://telegra.ph/file/21e696bbefcfe39c6e74e.jpg", + "https://telegra.ph/file/64ec61e41da3d4aded33d.jpg", + "https://telegra.ph/file/5b1d8766504ff75c1bd1f.jpg", + "https://telegra.ph/file/731879a344b3b49fe51bd.jpg", + "https://telegra.ph/file/6221afc84b357ed0d1fc5.jpg", + "https://telegra.ph/file/499bb1117771d8c020038.jpg", + "https://telegra.ph/file/2690d73bc32cfdb986629.jpg", + "https://telegra.ph/file/21255de971701b9df0902.jpg", + "https://telegra.ph/file/434a35e7fe5e2c000c598.jpg", + "https://telegra.ph/file/22a5d3621aba0b370d0b6.png", + "https://telegra.ph/file/ae31845d1df2c4a84915b.png", + "https://telegra.ph/file/ae2b809c8d11e7fa4121d.png", + "https://telegra.ph/file/ccb7f3113994d5d2b26f6.png", + "https://telegra.ph/file/5e53f0257ff12a7b0737a.png", + "https://telegra.ph/file/a613600a9f9f8ee29f0f7.jpg", + "https://telegra.ph/file/129c14fada1a8c0b151f5.jpg", + "https://telegra.ph/file/c7552ed4246ccd8efd301.jpg", + "https://telegra.ph/file/e794f772243d46467bcce.jpg", + "https://telegra.ph/file/b6c43b9bd63f5f764d60b.jpg", + "https://telegra.ph/file/11585459a3950de7f307c.png", + "https://telegra.ph/file/37cde08802c3cea25a03f.jpg", + "https://telegra.ph/file/d8d2db623223dee65963e.png", + "https://telegra.ph/file/b7229017ffee2d814c646.jpg", + "https://telegra.ph/file/65630efca60bfbdf84bc9.jpg", + "https://telegra.ph/file/b8ce571c2f66a7c7070e5.jpg", + "https://telegra.ph/file/a39f63f61f143ec00f19f.jpg", + "https://telegra.ph/file/f7d946d8caaa21bf96dbf.jpg", + "https://telegra.ph/file/9e4bef8ae0725d6b62108.png", + "https://telegra.ph/file/3550089b22f3c8f506226.jpg", + "https://telegra.ph/file/4275a4d4d6d433406b5fa.jpg", + "https://telegra.ph/file/c476583ff55e1947461ad.jpg", + "https://telegra.ph/file/87d2e5c0170ead00a2bc2.jpg", + "https://telegra.ph/file/5027dd7379cc432c06e73.jpg", + "https://telegra.ph/file/9e447fcaf3c66ddefb603.jpg", + "https://telegra.ph/file/e5375f8233bea4f74b0f8.jpg", + "https://telegra.ph/file/a1297510a64733cc5845f.jpg", + "https://telegra.ph/file/ff04b594b699ce72316d7.jpg", + "https://telegra.ph/file/093836a52cb166f161819.jpg", + "https://telegra.ph/file/1e64bae43ca10d628ff6d.jpg", + "https://telegra.ph/file/678ff9bb3405158a9155e.jpg", + "https://telegra.ph/file/ab332ced3f63b96c375c5.jpg", + "https://telegra.ph/file/a736d6cac93294c323303.jpg", + "https://telegra.ph/file/dce8565bf7742f3d7122b.jpg", + "https://telegra.ph/file/3f97672eb7b50426d15ff.jpg", + "https://telegra.ph/file/19c6250369f8588a169c7.jpg", + "https://telegra.ph/file/13d53b03a48448156564c.jpg", + "https://telegra.ph/file/d21ff0d35553890e8cf34.jpg", + "https://telegra.ph/file/a5e4cb43178642ba3709d.jpg", + "https://telegra.ph/file/ecf108d25a6f5f56f91f4.jpg", + "https://telegra.ph/file/8bd2b561b4c1f7164f934.png", + "https://telegra.ph/file/7717658e6930c8196a904.jpg", + "https://telegra.ph/file/dc85d43c4fc5062de7274.jpg", + "https://telegra.ph/file/ff05c19f228ab2ed3d39d.jpg", + "https://telegra.ph/file/ff05c19f228ab2ed3d39d.jpg", + "https://telegra.ph/file/0d686bfffcb92a2fbdb0f.jpg", + "https://telegra.ph/file/0d686bfffcb92a2fbdb0f.jpg", + "https://telegra.ph/file/cdc66f16fbfb75971df2f.jpg", + "https://telegra.ph/file/5c575892b9f9534fd4f31.jpg", + "https://telegra.ph/file/78ffc400d4f3236b00e6b.jpg", + "https://telegra.ph/file/89d32e5bbf084a376c803.jpg", + "https://telegra.ph/file/b5d7dbcdce241013a061b.jpg", + "https://telegra.ph/file/c1d228bc1859213d258d7.jpg", + "https://telegra.ph/file/c6b0720b9f765809ea20a.jpg", + "https://telegra.ph/file/df7e648f2e68ff8e1a1e6.jpg", + "https://telegra.ph/file/5148f764cbc4700519909.jpg", + "https://telegra.ph/file/479e7f51c682dcd1f013f.jpg", + "https://telegra.ph/file/54a9eb0afe7a0f9c7c2f3.jpg", + "https://telegra.ph/file/73c52ee54567a61dac47a.jpg", + "https://telegra.ph/file/1427dbba81bd21b1bfc56.jpg", + "https://telegra.ph/file/1427dbba81bd21b1bfc56.jpg", + "https://telegra.ph/file/b0816374b470a5f9c66a6.jpg", + "https://telegra.ph/file/e10840ec9bea9bbfaff0e.jpg", + "https://telegra.ph/file/5935275d3ee09bc5a47b8.png", + "https://telegra.ph/file/c27e64f1e8ece187c8161.jpg", + "https://telegra.ph/file/055e9af8500ab92755358.jpg", + "https://telegra.ph/file/f18f71167f9318ea28571.jpg", + "https://telegra.ph/file/e2e26f252a5e25a1563c5.jpg", + "https://telegra.ph/file/47ccb13820d6fc54d872b.jpg", + "https://telegra.ph/file/f2ddccd28ceaeae90b2a3.jpg", + "https://telegra.ph/file/951c872f7f8d551995652.jpg", + "https://telegra.ph/file/8e8842f9fe207b8abd951.jpg", + "https://telegra.ph/file/8a14ecd2347ef88e81201.jpg", + "https://telegra.ph/file/b3869374ce0af9f26f92a.jpg", + "https://telegra.ph/file/8e17f8d3633a5696a1ccf.jpg", + "https://telegra.ph/file/b29d8956ae249773b0ec7.png", + "https://telegra.ph/file/d0eebe724b67d2ef7647e.jpg", + "https://telegra.ph/file/5780b3273162d2b9ba9ec.jpg", + "https://telegra.ph/file/e2d56d5dbb108ba7af20c.jpg", + "https://telegra.ph/file/1a4f50dd1e4ec9f04bfa1.jpg", + "https://telegra.ph/file/99b56305fa9c50767f574.jpg", + "https://telegra.ph/file/0859e0104c671bc9b6b7d.jpg", + "https://telegra.ph/file/b3af2980caf7040702171.jpg", + "https://telegra.ph/file/14be160df3b84c59e268e.jpg", + "https://telegra.ph/file/b958155e1e8e9ab9a0416.jpg", + "https://telegra.ph/file/24fff051c39b815e5078a.jpg", + "https://telegra.ph/file/258c02c002e89287d5d9b.jpg", + "https://telegra.ph/file/d2abc99773a9d4954c2ba.jpg", + "https://telegra.ph/file/9849b3940f063b065f4e3.jpg", +] + + +@app.on_message(filters.command("logo")) +async def lego(_, message): + quew = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + if message.from_user.id != OWNER_ID and not quew: + await message.reply( + "`ɢɪᴠᴇ sᴏᴍᴇ ᴛᴇxᴛ ᴛᴏ ᴄʀᴇᴀᴛᴇ ʟᴏɢᴏ ʙᴀʙᴇ​ !`\n`Example /logo <queennoxi>`" + ) + return + pesan = await message.reply("**ᴄʀᴇᴀᴛɪɴɢ ʏᴏᴜʀ ʀᴇǫᴜᴇsᴛᴇᴅ ʟᴏɢᴏ ᴘʟᴇᴀsᴇ ᴡᴀɪᴛ ᴀ sᴇᴄ​...**") + try: + text = quew + randc = random.choice(LOGO_LINKS) + img = Image.open(io.BytesIO(requests.get(randc).content)) + draw = ImageDraw.Draw(img) + image_widthz, image_heightz = img.size + fnt = glob.glob("./QueenNoxi/resources/fonts/*") + randf = random.choice(fnt) + font = ImageFont.truetype(randf, 120) + bbox= draw.textbbox((0,0),text, font=font) + w, h = bbox[2] - bbox[0], bbox[3] - bbox[1] + h += int(h * 0.21) + draw.text( + ((image_widthz - w) / 2, (image_heightz - h) / 2), + text, + font=font, + fill=(255, 255, 255), + ) + x = (image_widthz - w) / 2 + y = (image_heightz - h) / 2 + 6 + draw.text( + (x, y), text, font=font, fill="white", stroke_width=1, stroke_fill="black" + ) + image_width, image_height = img.size + draw.text( + ((image_widthz - w) / 2, (image_heightz - h) / 2), + text, + font=font, + fill=(255, 255, 255), + ) + x = (image_widthz - w) / 2 + y = (image_heightz - h) / 2 + 6 + draw.text( + (x, y), text, font=font, fill="white", stroke_width=1, stroke_fill="black" + ) + fname = "queennoxi.png" + img.save(fname, "png") + + button = InlineKeyboardMarkup([[InlineKeyboardButton("Aᴅᴅ Mᴇ Tᴏ Yᴏᴜʀ Gʀᴏᴜᴘ", url=f"https://t.me/{BOT_USERNAME}?startgroup=new")]]) + await message.reply_photo( + photo=fname, + caption=f"━━━━━━━{BOT_NAME}━━━━━━━\n\n☘️ ʟᴏɢᴏ ᴄʀᴇᴀᴛᴇᴅ ꜱᴜᴄᴄᴇꜱꜱꜰᴜʟʟʏ ☘️\n◈──────────────◈\n🔥 ᴄʀᴇᴀᴛᴇᴅ ʙʏ : @{BOT_USERNAME}\n━━━━━━━{BOT_NAME}━━━━━━━", + reply_markup=button + ) + await pesan.delete() + if os.path.exists(fname): + os.remove(fname) + except Exception as e: + await message.reply(f"ᴇʀʀᴏʀ {e}, ʀᴇᴩᴏʀᴛ ᴛʜɪs ᴀᴛ @{SUPPORT_CHAT} ") + + +__mod_name__ = "Lᴏɢᴏ" + +__help__ = f""" +@{BOT_USERNAME} ᴄᴀɴ ᴄʀᴇᴀᴛᴇ sᴏᴍᴇ ʙᴇᴀᴜᴛɪғᴜʟ ᴀɴᴅ ᴀᴛᴛʀᴀᴄᴛɪᴠᴇ ʟᴏɢᴏ ғᴏʀ ʏᴏᴜʀ ᴘʀᴏғɪʟᴇ ᴘɪᴄs. + + +❍ /logo (Text) *:* ᴄʀᴇᴀᴛᴇ ᴀ ʟᴏɢᴏ ᴏғ ʏᴏᴜʀ ɢɪᴠᴇɴ ᴛᴇxᴛ ᴡɪᴛʜ ʀᴀɴᴅᴏᴍ ᴠɪᴇᴡ. +""" diff --git a/QueenNoxi/modules/memify.py b/QueenNoxi/modules/memify.py new file mode 100644 index 0000000000000000000000000000000000000000..a207258ebfebdb6fc75bc982619b6bd7e327fc1e --- /dev/null +++ b/QueenNoxi/modules/memify.py @@ -0,0 +1,181 @@ +import os +import textwrap + +from PIL import Image, ImageDraw, ImageFont + +from pyrogram import filters +from QueenNoxi import pbot as app + +@app.on_message(filters.command("mmf")) +async def handler(_, message): + if getattr(message, "forward_from", None): + return + + if not message.reply_to_message: + await message.reply("Provide Some Text To Draw!") + return + + reply_message = message.reply_to_message + + if not reply_message.media: + await message.reply("```Reply to a image/sticker.```") + return + + file = await reply_message.download() + + msg = await message.reply("```Memifying this image! ✊🏻 ```") + + text = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + + if len(text) < 1: + os.remove(file) + return await msg.edit_text("You might want to try `/mmf text`") + + meme = await drawText(file, text) + + await message.reply_document(document=meme) + + await msg.delete() + + os.remove(meme) + + + +async def drawText(image_path, text): + img = Image.open(image_path) + + os.remove(image_path) + + i_width, i_height = img.size + + if os.name == "nt": + fnt = "ariel.ttf" + + else: + fnt = "./QueenNoxi/resources/default.ttf" + + m_font = ImageFont.truetype(fnt, int((70 / 640) * i_width)) + + if ";" in text: + upper_text, lower_text = text.split(";") + + else: + upper_text = text + + lower_text = "" + + draw = ImageDraw.Draw(img) + + current_h, pad = 10, 5 + + if upper_text: + for u_text in textwrap.wrap(upper_text, width=15): + uwl, uht, uwr, uhb = m_font.getbbox(u_text) + u_width, u_height = uwr - uwl, uhb - uht + + draw.text( + xy=(((i_width - u_width) / 2) - 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=(((i_width - u_width) / 2) + 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=((i_width - u_width) / 2, int(((current_h / 640) * i_width)) - 2), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=(((i_width - u_width) / 2), int(((current_h / 640) * i_width)) + 2), + text=u_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=((i_width - u_width) / 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(255, 255, 255), + ) + + current_h += u_height + pad + + if lower_text: + for l_text in textwrap.wrap(lower_text, width=15): + uwl, uht, uwr, uhb = m_font.getbbox(l_text) + u_width, u_height = uwr - uwl, uhb - uht + + draw.text( + xy=( + ((i_width - u_width) / 2) - 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=( + ((i_width - u_width) / 2) + 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=( + (i_width - u_width) / 2, + (i_height - u_height - int((20 / 640) * i_width)) - 2, + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=( + (i_width - u_width) / 2, + (i_height - u_height - int((20 / 640) * i_width)) + 2, + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + ) + + draw.text( + xy=( + (i_width - u_width) / 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(255, 255, 255), + ) + + current_h += u_height + pad + + image_name = "memify.webp" + + webp_file = os.path.join(image_name) + + img.save(webp_file, "webp") + + return webp_file + + +__mod_name__ = "Mᴍғ" +__help__ = """ +⫸ /mmf <ᴛᴇxᴛ> ◉ ᴛᴏ ᴍᴇᴍɪғʏ """ diff --git a/QueenNoxi/modules/misc.py b/QueenNoxi/modules/misc.py new file mode 100644 index 0000000000000000000000000000000000000000..300dfe509aec9c0ead1cdc033d7f1163aa076ff2 --- /dev/null +++ b/QueenNoxi/modules/misc.py @@ -0,0 +1,51 @@ +from pyrogram import filters, Client, enums +from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton + +from QueenNoxi import pbot +from QueenNoxi.modules.helper_funcs.chat_status import user_admin + +MARKDOWN_HELP = """ +ᴍᴀʀᴋᴅᴏᴡɴ ɪs ᴀ ᴠᴇʀʏ ᴘᴏᴡᴇʀғᴜʟ ғᴏʀᴍᴀᴛᴛɪɴɢ ᴛᴏᴏʟ sᴜᴘᴘᴏʀᴛᴇᴅ ʙʏ ᴛᴇʟᴇɢʀᴀᴍ. + +• `_italic_`: ᴡʀᴀᴘᴘɪɴɢ ᴛᴇxᴛ ᴡɪᴛʜ '_' ᴡɪʟʟ ᴘʀᴏᴅᴜᴄᴇ ɪᴛᴀʟɪᴄ ᴛᴇxᴛ +• `*bold*`: ᴡʀᴀᴘᴘɪɴɢ ᴛᴇxᴛ ᴡɪᴛʜ '*' ᴡɪʟʟ ᴘʀᴏᴅᴜᴄᴇ ʙᴏʟᴅ ᴛᴇxᴛ +• ``code``: ᴡʀᴀᴘᴘɪɴɢ ᴛᴇxᴛ ᴡɪᴛʜ '`' ᴡɪʟʟ ᴘʀᴏᴅᴜᴄᴇ ᴍᴏɴᴏsᴘᴀᴄᴇᴅ ᴛᴇxᴛ +• `[text](link)`: ᴄʀᴇᴀᴛᴇ ᴀ ʜʏᴘᴇʀʟɪɴᴋ +• `[text](buttonurl:link)`: ᴄʀᴇᴀᴛᴇ ʟɪɴᴋ ʙᴜᴛᴛᴏɴs +• `[text](buttonurl:link:same)`: ᴍᴜʟᴛɪᴘʟᴇ ʙᴜᴛᴛᴏɴs ᴘᴇʀ ʟɪɴᴇ +""" + +@pbot.on_message(filters.command("echo") & filters.group) +@user_admin +async def echo(client: Client, message: Message): + args = message.text.split(None, 1) + if len(args) < 2: + return + + msg_to_reply = message.reply_to_message or message + await msg_to_reply.reply_text(args[1], parse_mode=enums.ParseMode.MARKDOWN, disable_web_page_preview=True) + + try: + await message.delete() + except: + pass + +@pbot.on_message(filters.command("markdownhelp")) +async def markdown_help(client: Client, message: Message): + if message.chat.type != enums.ChatType.PRIVATE: + await message.reply_text( + "Contact me in PM for markdown help!", + reply_markup=InlineKeyboardMarkup([[ + InlineKeyboardButton("Markdown help", url=f"t.me/{(await client.get_me()).username}?start=markdownhelp") + ]]) + ) + return + + await message.reply_text(MARKDOWN_HELP) + await message.reply_text("Try saving a test note: `/save test *Bold Text* [Google](google.com)`") + +__mod_name__ = "Exᴛʀᴀs" +__help__ = """ + ❍ /echo <ᴛᴇxᴛ>: ʀᴇᴘʟʏ ᴡɪᴛʜ ᴛʜᴇ sᴀᴍᴇ ᴛᴇxᴛ (ᴀᴅᴍɪɴs ᴏɴʟʏ) + ❍ /markdownhelp: ǫᴜɪᴄᴋ sᴜᴍᴍᴀʀʏ ᴏғ ʜᴏᴡ ᴍᴀʀᴋᴅᴏᴡɴ ᴡᴏʀᴋs +""" diff --git a/QueenNoxi/modules/modules.py b/QueenNoxi/modules/modules.py new file mode 100644 index 0000000000000000000000000000000000000000..331b3e06941f080744bc864880e097f5de0ea58e --- /dev/null +++ b/QueenNoxi/modules/modules.py @@ -0,0 +1,22 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import pbot, DEV_USERS +from QueenNoxi.modules import ALL_MODULES + +@pbot.on_message(filters.command("listmodules")) +async def list_modules(client: Client, message: Message): + if message.from_user.id not in DEV_USERS: + return + + module_list = "**Loaded Modules:**\n\n" + for module in sorted(ALL_MODULES): + module_list += f" • `{module}`\n" + + await message.reply_text(module_list) + +__mod_name__ = "Modules" +__help__ = """ +• `/listmodules`: List all loaded modules (Devs only). +""" diff --git a/QueenNoxi/modules/muting.py b/QueenNoxi/modules/muting.py new file mode 100644 index 0000000000000000000000000000000000000000..aa05084c62c7b4bd5cc40db81ee34e0a4cec6db7 --- /dev/null +++ b/QueenNoxi/modules/muting.py @@ -0,0 +1,237 @@ +import html +from typing import Optional + +from pyrogram import filters, Client, enums +from pyrogram.types import Message, ChatPermissions +from pyrogram.errors import RPCError + +from QueenNoxi import LOGGER, TIGERS, pbot, BOT_ID +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import ( + bot_admin, + can_restrict, + connection_status, + is_user_admin, + user_admin, +) +from QueenNoxi.modules.helper_funcs.extraction import ( + extract_user, + extract_user_and_text, +) +from QueenNoxi.modules.helper_funcs.string_handling import extract_time +from QueenNoxi.modules.log_channel import loggable + + +async def check_user(user_id: int, chat) -> Optional[str]: + if not user_id: + return "You don't seem to be referring to a user or the ID specified is incorrect.." + + try: + member = await chat.get_member(user_id) + except RPCError: + return "I can't seem to find this user" + + if user_id == BOT_ID: + return "I'm not gonna MUTE myself, How high are you?" + + if await is_user_admin(chat, user_id) or user_id in TIGERS: + return "Can't. Find someone else to mute but not this one." + + return None + + +@pbot.on_message(filters.command("mute") & filters.group) +@connection_status +@bot_admin +@user_admin +@loggable +async def mute(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + reply = await check_user(user_id, chat) + + if reply: + await message.reply_text(reply) + return "" + + member = await chat.get_member(user_id) + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#MUTE\n" + f"<b>Admin:</b> {user.mention}\n" + f"<b>User:</b> {member.user.mention}" + ) + if reason: + log += f"\n<b>Reason:</b> {html.escape(reason)}" + + if member.permissions and member.permissions.can_send_messages: + try: + await chat.restrict_member(user_id, ChatPermissions(can_send_messages=False)) + await message.reply_text(f"Muted <b>{html.escape(member.user.first_name)}</b> with no expiration date!") + return log + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + else: + await message.reply_text("This user is already muted!") + + return "" + + +@pbot.on_message(filters.command("dmute") & filters.group) +@connection_status +@bot_admin +@user_admin +@loggable +async def dmute(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + + try: + await message.delete() + except: + pass + + reply = await check_user(user_id, chat) + if reply: + await message.reply_text(reply) + return "" + + member = await chat.get_member(user_id) + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#MUTE\n" + f"<b>Admin:</b> {user.mention}\n" + f"<b>User:</b> {member.user.mention}" + ) + if reason: + log += f"\n<b>Reason:</b> {html.escape(reason)}" + + if member.permissions and member.permissions.can_send_messages: + try: + await chat.restrict_member(user_id, ChatPermissions(can_send_messages=False)) + return log + except RPCError as e: + LOGGER.warning(f"Error dmute: {e}") + return "" + + +@pbot.on_message(filters.command("unmute") & filters.group) +@connection_status +@bot_admin +@user_admin +@loggable +async def unmute(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id = await extract_user(message, message.command[1:]) + + if not user_id: + await message.reply_text("You'll need to either give me a username to unmute, or reply to someone.") + return "" + + try: + member = await chat.get_member(user_id) + except RPCError: + await message.reply_text("I can't seem to find this user") + return "" + + if member.status in [enums.ChatMemberStatus.BANNED, enums.ChatMemberStatus.LEFT]: + await message.reply_text("This user isn't even in the chat!") + return "" + + if member.permissions and member.permissions.can_send_messages: + await message.reply_text("This user already has the right to speak.") + return "" + + try: + await chat.restrict_member( + user_id, + ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + can_send_polls=True, + can_change_info=True, + can_invite_users=True, + can_pin_messages=True + ) + ) + await message.reply_text(f"I shall allow <b>{html.escape(member.user.first_name)}</b> to text!") + return ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#UNMUTE\n" + f"<b>Admin:</b> {user.mention}\n" + f"<b>User:</b> {member.user.mention}" + ) + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + return "" + + +@pbot.on_message(filters.command(["tmute", "tempmute"]) & filters.group) +@connection_status +@bot_admin +@can_restrict +@user_admin +@loggable +async def temp_mute(client: Client, message: Message) -> str: + chat = message.chat + user = message.from_user + user_id, reason = await extract_user_and_text(message, message.command[1:]) + reply = await check_user(user_id, chat) + + if reply: + await message.reply_text(reply) + return "" + + member = await chat.get_member(user_id) + if not reason: + await message.reply_text("You haven't specified a time to mute this user for!") + return "" + + split_reason = reason.split(None, 1) + time_val = split_reason[0].lower() + reason = split_reason[1] if len(split_reason) > 1 else "" + mutetime = await extract_time(message, time_val) + + if not mutetime: + return "" + + log = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"#TEMP MUTED\n" + f"<b>Admin:</b> {user.mention}\n" + f"<b>User:</b> {member.user.mention}\n" + f"<b>Time:</b> {time_val}" + ) + if reason: + log += f"\n<b>Reason:</b> {html.escape(reason)}" + + if member.permissions and member.permissions.can_send_messages: + try: + await chat.restrict_member( + user_id, + ChatPermissions(can_send_messages=False), + until_date=mutetime + ) + await message.reply_text(f"Muted <b>{html.escape(member.user.first_name)}</b> for {time_val}!") + return log + except RPCError as e: + await message.reply_text(f"Error: {e.MESSAGE}") + else: + await message.reply_text("This user is already muted.") + + return "" + + +__mod_name__ = "Mute" +__help__ = """ +*Admins only:* + ❍ /mute <userhandle>: Silences a user. + ❍ /tmute <userhandle> x(m/h/d): Mutes a user for x time. + ❍ /unmute <userhandle>: Unmutes a user. + ❍ /dmute <userhandle>: Silences a user and deletes the command. +""" diff --git a/QueenNoxi/modules/myinfo.py b/QueenNoxi/modules/myinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..aa260f0a68e92a2175cc1e5af025c63314b78f2f --- /dev/null +++ b/QueenNoxi/modules/myinfo.py @@ -0,0 +1,45 @@ +import asyncio +import re +from pyrogram import filters +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, InputMediaPhoto +from QueenNoxi import pbot as app, BOT_NAME + +edit_time = 5 +file1 = "https://telegra.ph/file/9a85d0a873e2dd80d278d.jpg" +file2 = "https://telegra.ph/file/9e7815284031452afa9e5.jpg" +file3 = "https://telegra.ph/file/dcc5e003287f69acea368.jpg" +file4 = "https://telegra.ph/file/ed1ce7fee94f46b0f671e.jpg" +file5 = "https://telegra.ph/file/701028ce085ecfa961a36.jpg" + +@app.on_message(filters.command("myinfo")) +async def proboyx(_, message): + firstname = message.from_user.first_name if message.from_user else "Unknown" + button = InlineKeyboardMarkup([[InlineKeyboardButton("ɪɴғᴏʀᴍᴀᴛɪᴏɴ", callback_data="informations")]]) + msg = await message.reply_photo( + photo=file2, + caption=f"ʜᴇʏ {firstname}, \nᴄʟɪᴄᴋ ᴏɴ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ \n ᴛᴏ ɢᴇᴛ ɪɴғᴏ ᴀʙᴏᴜᴛ ʏᴏᴜ", + reply_markup=button, + ) + + for f in [file3, file5, file1, file4, file2, file1, file3, file5, file4]: + try: + await asyncio.sleep(edit_time) + await msg.edit_media(InputMediaPhoto(media=f, caption=f"ʜᴇʏ {firstname}, \nᴄʟɪᴄᴋ ᴏɴ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ \n ᴛᴏ ɢᴇᴛ ɪɴғᴏ ᴀʙᴏᴜᴛ ʏᴏᴜ"), reply_markup=button) + except Exception: + pass + +@app.on_callback_query(filters.regex(r"^informations$")) +async def callback_query_handler(_, query): + try: + user = query.from_user + LILIE = f"ᴘᴏᴡᴇʀᴇᴅ ʙʏ {BOT_NAME}\n\n" + LILIE += f"ғɪʀsᴛ ɴᴀᴍᴇ: {user.first_name} \n" + (f"ʟᴀsᴛ ɴᴀᴍᴇ: {user.last_name}\n" if user.last_name else '') + LILIE += f"ʏᴏᴜ ʙᴏᴛ : {user.is_bot} \n" + LILIE += f"ʀᴇsᴛʀɪᴄᴛᴇᴅ : {user.is_restricted} \n" + LILIE += f"ᴜsᴇʀ ɪᴅ: {user.id}\n" + LILIE += f"ᴜsᴇʀɴᴀᴍᴇ : @{user.username}\n" if user.username else f"ᴜsᴇʀɴᴀᴍᴇ : `{user.username}`\n" + await query.answer(LILIE, show_alert=True) + except Exception as e: + await query.message.reply(f"{e}") + +__command_list__ = ["myinfo"] diff --git a/QueenNoxi/modules/nightmode.py b/QueenNoxi/modules/nightmode.py new file mode 100644 index 0000000000000000000000000000000000000000..89ba5f66ead6d2d48ede2a5aa5c74d91f4b0985b --- /dev/null +++ b/QueenNoxi/modules/nightmode.py @@ -0,0 +1,128 @@ +import asyncio +from datetime import datetime +import pytz +from pyrogram import filters +from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton +from QueenNoxi import pbot as app, BOT_NAME, BOT_USERNAME +from QueenNoxi.modules.sql.night_mode_sql import ( + add_nightmode, + get_all_chat_id, + is_nightmode_indb, + rmnightmode, +) + +async def is_register_admin(chat_id, user_id): + try: + member = await app.get_chat_member(chat_id, user_id) + return member.privileges is not None + except Exception: + return False + +hehes = ChatPermissions( + can_send_messages=False, + can_send_media_messages=False, + can_send_polls=False, + can_change_info=False, + can_invite_users=False, + can_pin_messages=False +) +openhehe = ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_polls=True, + can_change_info=True, + can_invite_users=True, + can_pin_messages=True +) + +button_row = InlineKeyboardMarkup([[InlineKeyboardButton('Aᴅᴅ Mᴇ Tᴏ Yᴏᴜʀ Gʀᴏᴜᴘ', url=f'https://t.me/{BOT_USERNAME}?startgroup=new')]]) + +@app.on_message(filters.command("nightmode")) +async def close_ws(_, message): + if message.chat.type.name == "PRIVATE": + await message.reply("ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ ᴇɴᴀʙʟᴇ ɴɪɢʜᴛ ᴍᴏᴅᴇ ɪɴ ɢʀᴏᴜᴘꜱ.") + return + if not await is_register_admin(message.chat.id, message.from_user.id): + await message.reply("🤦🏻‍♂️ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ ꜱᴏ ʏᴏᴜ ᴄᴀɴ'ᴛ ᴜꜱᴇ ᴛʜɪꜱ ᴄᴏᴍᴍᴀɴᴅ...") + return + + if is_nightmode_indb(str(message.chat.id)): + await message.reply("ᴛʜɪꜱ ᴄʜᴀᴛ ɪꜱ ʜᴀꜱ ᴀʟʀᴇᴀᴅʏ ᴇɴᴀʙʟᴇᴅ ɴɪɢʜᴛ ᴍᴏᴅᴇ") + return + add_nightmode(str(message.chat.id)) + await message.reply( + f"​ᴀᴅᴅᴇᴅ ᴄʜᴀᴛ​ ​​: {message.chat.title} \n​ɪᴅ​: {message.chat.id} ᴛᴏ ᴅᴀᴛᴀʙᴀꜱᴇ. \n**ᴛʜɪꜱ ɢʀᴏᴜᴘ ᴡɪʟʟ ʙᴇ ᴄʟᴏꜱᴇᴅ ᴏɴ 12ᴀᴍ(ɪꜱᴛ) ᴀɴᴅ ᴡɪʟʟ ᴏᴘᴇɴᴇᴅ ᴏɴ 06ᴀᴍ(ɪꜱᴛ)**", + reply_markup=button_row + ) + +@app.on_message(filters.command("rmnight")) +async def disable_ws(_, message): + if message.chat.type.name == "PRIVATE": + await message.reply("ʏᴏᴜ ᴄᴀɴ ᴏɴʟʏ ᴅɪꜱᴀʙʟᴇ ɴɪɢʜᴛ ᴍᴏᴅᴇ ɪɴ ɢʀᴏᴜᴘꜱ.") + return + if not await is_register_admin(message.chat.id, message.from_user.id): + await message.reply("🤦🏻‍♂️ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴅᴍɪɴ ꜱᴏ ʏᴏᴜ ᴄᴀɴ'ᴛ ᴜꜱᴇ ᴛʜɪꜱ ᴄᴏᴍᴍᴀɴᴅ..") + return + + if not is_nightmode_indb(str(message.chat.id)): + await message.reply("ᴛʜɪꜱ ᴄʜᴀᴛ ɪꜱ ​ɴᴏᴛ ᴀʟʀᴇᴀᴅʏ ᴇɴᴀʙʟᴇᴅ ɴɪɢʜᴛ ᴍᴏᴅᴇ") + return + rmnightmode(str(message.chat.id)) + await message.reply( + f"ʀᴇᴍᴏᴠᴇᴅ ᴄʜᴀᴛ : {message.chat.title} \n​ɪᴅ​: {message.chat.id} ꜰʀᴏᴍ ᴅᴀᴛᴀʙᴀꜱᴇ." + ) + +async def job_close(): + ws_chats = get_all_chat_id() + if len(ws_chats) == 0: + return + for warner in ws_chats: + try: + await app.send_message( + int(warner.chat_id), + f"12:00 ᴀᴍ, ɢʀᴏᴜᴘ ɪꜱ ᴄʟᴏꜱɪɴɢ ᴛɪʟʟ 6 ᴀᴍ.\n ɴɪɢʜᴛ ᴍᴏᴅᴇ ꜱᴛᴀʀᴛᴇᴅ ! \n**ᴘᴏᴡᴇʀᴇᴅ ʙʏ {BOT_NAME}**", + reply_markup=button_row + ) + await app.set_chat_permissions(int(warner.chat_id), hehes) + except Exception: + pass + +async def job_open(): + ws_chats = get_all_chat_id() + if len(ws_chats) == 0: + return + for warner in ws_chats: + try: + await app.send_message( + int(warner.chat_id), + f"06:00 ᴀᴍ, ɢʀᴏᴜᴘ ɪꜱ ᴏᴘᴇɴɪɴɢ.\n**ᴘᴏᴡᴇʀᴇᴅ ʙʏ {BOT_NAME}**", + ) + await app.set_chat_permissions(int(warner.chat_id), openhehe) + except Exception: + pass + +async def scheduler_loop(): + tz = pytz.timezone("Asia/Kolkata") + while True: + now = datetime.now(tz) + if now.hour == 23 and now.minute == 59: + await job_close() + await asyncio.sleep(60) + elif now.hour == 6 and now.minute == 1: + await job_open() + await asyncio.sleep(60) + await asyncio.sleep(20) + +# Start background task safely without blocking +asyncio.get_event_loop().create_task(scheduler_loop()) + +__help__ = """ +*ᴀᴅᴍɪɴs ᴏɴʟʏ* + + ❍ /nightmode *:* ᴀᴅᴅs ɢʀᴏᴜᴘ ᴛᴏ ɴɪɢʜᴛᴍᴏᴅᴇ ᴄʜᴀᴛs + ❍ /rmnight *:* ʀᴇᴍᴏᴠᴇs ɢʀᴏᴜᴘ ғʀᴏᴍ ɴɪɢʜᴛᴍᴏᴅᴇ ᴄʜᴀᴛs + +*ɴᴏᴛᴇ:* ɴɪɢʜᴛ ᴍᴏᴅᴇ ᴄʜᴀᴛs ɢᴇᴛ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴄʟᴏsᴇᴅ ᴀᴛ 12 ᴀᴍ(ɪsᴛ) ᴀɴᴅ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ ᴏᴘᴇɴɴᴇᴅ ᴀᴛ 6 ᴀᴍ(ɪsᴛ) ᴛᴏ ᴘʀᴇᴠᴇɴᴛ ɴɪɢʜᴛ sᴘᴀᴍs. +""" + +__mod_name__ = "Nɪɢʜᴛ​" diff --git a/QueenNoxi/modules/no_sql/__init__.py b/QueenNoxi/modules/no_sql/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..755cd480a4742bfdf51255e72ae05243cd71ff9d --- /dev/null +++ b/QueenNoxi/modules/no_sql/__init__.py @@ -0,0 +1,78 @@ +from motor.motor_asyncio import AsyncIOMotorClient as MongoCli +from pymongo import MongoClient, collection +from pymongo.errors import PyMongoError +from QueenNoxi import MONGO_DB_URI + +# ── Motor async client (used by users_db, chats_db, gban_db) ───────────────── +mongo = MongoCli(MONGO_DB_URI) +QueenNoxidb = mongo.MUK_ROB + +# ── Sync client for legacy MongoDB class ───────────────────────────────────── +try: + client = MongoClient(MONGO_DB_URI) +except PyMongoError: + exit(1) +main_db = client["MUKESH_ROBOT"] +QueenNoxiXdb = main_db + +# Sub-module imports AFTER QueenNoxidb is defined to avoid circular imports +from QueenNoxi.modules.no_sql.users_db import * +from QueenNoxi.modules.no_sql.chats_db import * +from QueenNoxi.modules.no_sql.gban_db import * + + +def get_collection(name: str) -> collection: + """Get the collection from database.""" + return QueenNoxiXdb[name] + + +class MongoDB: + """Class for interacting with Bot database.""" + + def __init__(self, collection_name) -> None: + self.collection = QueenNoxiXdb[collection_name] + + def insert_one(self, document): + result = self.collection.insert_one(document) + return repr(result.inserted_id) + + def find_one(self, query): + result = self.collection.find_one(query) + return result if result else False + + def find_all(self, query=None): + if query is None: + query = {} + return list(self.collection.find(query)) + + def count(self, query=None): + if query is None: + query = {} + return self.collection.count_documents(query) + + def delete_one(self, query): + self.collection.delete_many(query) + return self.collection.count_documents({}) + + def replace(self, query, new_data): + old = self.collection.find_one(query) + _id = old["_id"] + self.collection.replace_one({"_id": _id}, new_data) + new = self.collection.find_one({"_id": _id}) + return old, new + + def update(self, query, update): + result = self.collection.update_one(query, {"$set": update}) + new_document = self.collection.find_one(query) + return result.modified_count, new_document + + @staticmethod + def close(): + return client.close() + + +def __connect_first(): + _ = MongoDB("test") + + +__connect_first() diff --git a/QueenNoxi/modules/no_sql/afk_db.py b/QueenNoxi/modules/no_sql/afk_db.py new file mode 100644 index 0000000000000000000000000000000000000000..dfb9d26fb0f1a8829bb8d8cd20bdea09689e67af --- /dev/null +++ b/QueenNoxi/modules/no_sql/afk_db.py @@ -0,0 +1,32 @@ +from QueenNoxi.modules.no_sql import QueenNoxidb + +afkdb = QueenNoxidb.afk + + +async def is_afk(user_id: int) -> bool: + user = await afkdb.find_one({"user_id": user_id}) + if not user: + return False, {} + return True, user["reason"] + + +async def add_afk(user_id: int, mode): + await afkdb.update_one( + {"user_id": user_id}, {"$set": {"reason": mode}}, upsert=True + ) + + +async def remove_afk(user_id: int): + user = await afkdb.find_one({"user_id": user_id}) + if user: + return await afkdb.delete_one({"user_id": user_id}) + + +async def get_afk_users() -> list: + users = afkdb.find({"user_id": {"$gt": 0}}) + if not users: + return [] + users_list = [] + for user in await users.to_list(length=1000000000): + users_list.append(user) + return users_list diff --git a/QueenNoxi/modules/no_sql/chats_db.py b/QueenNoxi/modules/no_sql/chats_db.py new file mode 100644 index 0000000000000000000000000000000000000000..4ec1d301f439ee85cb343bdaae2afa666badd4b4 --- /dev/null +++ b/QueenNoxi/modules/no_sql/chats_db.py @@ -0,0 +1,21 @@ +from QueenNoxi.modules.no_sql import QueenNoxidb + +chatsdb = QueenNoxidb.served_chats + +async def get_served_chats() -> list: + chats_list = [] + async for chat in chatsdb.find({"chat_id": {"$lt": 0}}): + chats_list.append(chat) + return chats_list + +async def is_served_chat(chat_id: int) -> bool: + chat = await chatsdb.find_one({"chat_id": chat_id}) + return chat is not None + +async def add_served_chat(chat_id: int): + if await is_served_chat(chat_id): + return + await chatsdb.insert_one({"chat_id": chat_id}) + +async def remove_served_chat(chat_id: int): + await chatsdb.delete_one({"chat_id": chat_id}) diff --git a/QueenNoxi/modules/no_sql/fsub_db.py b/QueenNoxi/modules/no_sql/fsub_db.py new file mode 100644 index 0000000000000000000000000000000000000000..e45c536ad1a7673ca1cff008249c5ca0ec1f31fa --- /dev/null +++ b/QueenNoxi/modules/no_sql/fsub_db.py @@ -0,0 +1,18 @@ +from . import QueenNoxiXdb as db + +fsub = db.force_sub + + +def fs_settings(chat_id: int): + _x = fsub.find_one({"chat_id": chat_id}) + if _x: + return _x + return None + + +def add_channel(chat_id: int, channel): + fsub.update_one({"chat_id": chat_id}, {"$set": {"channel": channel}}, upsert=True) + + +def disapprove(chat_id: int): + fsub.delete_one({"chat_id": chat_id}) diff --git a/QueenNoxi/modules/no_sql/gban_db.py b/QueenNoxi/modules/no_sql/gban_db.py new file mode 100644 index 0000000000000000000000000000000000000000..516e52b5282ed3181d1011a05ac7a0cc4474ac95 --- /dev/null +++ b/QueenNoxi/modules/no_sql/gban_db.py @@ -0,0 +1,39 @@ +from QueenNoxi import MONGO_DB_URI +from pymongo import MongoClient + +myclient = MongoClient(MONGO_DB_URI) + +users =myclient["MUK_users"] + +ban_db=users["gban_db"] + +# banned db + +def is_user_ingbanned(user_id: int) -> bool: + user = ban_db.find_one({"user_id": user_id}) + if user is not None: + return True + return False + +def add_gban(user_id: int, gban_reason: str = None): + is_served = is_user_ingbanned(user_id) + if not is_served: + ban_db.insert_one({"user_id": user_id, "gban_reason": gban_reason or "None"}) + + +def remove_gban(user_id: int): + is_served = is_user_ingbanned(user_id) + if is_served: + ban_db.delete_one({"user_id": user_id}) + + +def is_gban(user_id: int) -> bool: + user = ban_db.find_one({"user_id": user_id, "gban_reason": {"$ne": "None"}}) + if user: + return True + return False +def get_gban_list() -> list: + gban_list = [] + for user in ban_db.find({"gban_reason": {"$ne": "None"}}): + gban_list.append(user) + return gban_list diff --git a/QueenNoxi/modules/no_sql/users_db.py b/QueenNoxi/modules/no_sql/users_db.py new file mode 100644 index 0000000000000000000000000000000000000000..5a3095b93e46a7e0c865316c9b90baeb64c374ad --- /dev/null +++ b/QueenNoxi/modules/no_sql/users_db.py @@ -0,0 +1,21 @@ +from QueenNoxi.modules.no_sql import QueenNoxidb + +usersdb = QueenNoxidb.served_users + +async def is_served_user(user_id: int) -> bool: + user = await usersdb.find_one({"user_id": user_id}) + return user is not None + +async def get_served_users() -> list: + users_list = [] + async for user in usersdb.find({"user_id": {"$gt": 0}}): + users_list.append(user) + return users_list + +async def save_id(user_id: int): + if await is_served_user(user_id): + return + await usersdb.insert_one({"user_id": user_id}) + +async def remove_served_users(user_id: int): + await usersdb.delete_one({"user_id": user_id}) diff --git a/QueenNoxi/modules/notes.py b/QueenNoxi/modules/notes.py new file mode 100644 index 0000000000000000000000000000000000000000..ce119915e9a0c10ffd3cfcba91435b9eacd657de --- /dev/null +++ b/QueenNoxi/modules/notes.py @@ -0,0 +1,241 @@ +import html +import re +from typing import Optional + +from pyrogram import filters, Client, enums +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) +from pyrogram.errors import RPCError + +import QueenNoxi.modules.sql.notes_sql as sql +from QueenNoxi import DRAGONS, pbot, BOT_ID, SUPPORT_CHAT +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import connection_status, user_admin +from QueenNoxi.modules.helper_funcs.misc import build_keyboard, revert_buttons +from QueenNoxi.modules.helper_funcs.msg_types import get_note_type, Types +from QueenNoxi.modules.helper_funcs.string_handling import ( + escape_invalid_curly_brackets, + escape_markdown +) + +# Do not async +@connection_status +async def get(client: Client, message: Message, notename: str, show_none=True, no_format=False): + chat_id = message.chat.id + note = sql.get_note(chat_id, notename) + + if note: + reply_id = message.reply_to_message.id if message.reply_to_message else message.id + + if note.is_reply: + # logic for forward_message - in Pyrogram we use message.forward() or client.copy_message() + # but note.value stores message_id if is_reply was True in PTB version + # We'll try copy_message + try: + await client.copy_message(chat_id, chat_id, int(note.value), reply_to_message_id=reply_id) + return + except RPCError: + await message.reply_text("This message seems to have been lost.") + sql.rm_note(chat_id, notename) + return + + VALID_NOTE_FORMATTERS = [ + "first", + "last", + "fullname", + "username", + "id", + "chatname", + "mention", + ] + valid_format = escape_invalid_curly_brackets(note.value, VALID_NOTE_FORMATTERS) + if valid_format: + text = valid_format.format( + first=escape_markdown(message.from_user.first_name), + last=escape_markdown(message.from_user.last_name or message.from_user.first_name), + fullname=escape_markdown( + " ".join( + [message.from_user.first_name, message.from_user.last_name] + if message.from_user.last_name + else [message.from_user.first_name] + ) + ), + username="@" + message.from_user.username if message.from_user.username else message.from_user.mention, + mention=message.from_user.mention, + chatname=escape_markdown(message.chat.title if message.chat.type != enums.ChatType.PRIVATE else message.from_user.first_name), + id=message.from_user.id, + ) + else: + text = "" + + buttons = sql.get_buttons(chat_id, notename) + keyb = [] + parse_mode = enums.ParseMode.MARKDOWN + + if no_format: + parse_mode = None + text += revert_buttons(buttons) + else: + keyb = build_keyboard(buttons) + + keyboard = InlineKeyboardMarkup(keyb) if keyb else None + + try: + if note.msgtype in (Types.BUTTON_TEXT, Types.TEXT): + await message.reply_text( + text, + reply_to_message_id=reply_id, + parse_mode=parse_mode, + reply_markup=keyboard, + ) + elif note.msgtype == Types.STICKER: + await client.send_sticker(chat_id, note.file, reply_to_message_id=reply_id, reply_markup=keyboard) + elif note.msgtype == Types.DOCUMENT: + await client.send_document(chat_id, note.file, caption=text, reply_to_message_id=reply_id, parse_mode=parse_mode, reply_markup=keyboard) + elif note.msgtype == Types.PHOTO: + await client.send_photo(chat_id, note.file, caption=text, reply_to_message_id=reply_id, parse_mode=parse_mode, reply_markup=keyboard) + elif note.msgtype == Types.AUDIO: + await client.send_audio(chat_id, note.file, caption=text, reply_to_message_id=reply_id, parse_mode=parse_mode, reply_markup=keyboard) + elif note.msgtype == Types.VOICE: + await client.send_voice(chat_id, note.file, caption=text, reply_to_message_id=reply_id, parse_mode=parse_mode, reply_markup=keyboard) + elif note.msgtype == Types.VIDEO: + await client.send_video(chat_id, note.file, caption=text, reply_to_message_id=reply_id, parse_mode=parse_mode, reply_markup=keyboard) + elif note.msgtype == Types.VIDEO_NOTE: + await client.send_video_note(chat_id, note.file, reply_to_message_id=reply_id, reply_markup=keyboard) + + except RPCError as e: + await message.reply_text(f"This note could not be sent. Error: {e.MESSAGE}") + LOGGER.error(f"Could not send note {notename} in {chat_id}: {e}") + elif show_none: + await message.reply_text("This note doesn't exist") + + +@pbot.on_message(filters.command("get") & filters.group) +@connection_status +async def cmd_get(client: Client, message: Message): + args = message.command[1:] + if len(args) >= 2 and args[1].lower() == "noformat": + await get(client, message, args[0].lower(), show_none=True, no_format=True) + elif len(args) >= 1: + await get(client, message, args[0].lower(), show_none=True) + else: + await message.reply_text("Please specify a note name to get.") + + +@pbot.on_message(filters.regex(r"^#[^\s]+") & filters.group) +@connection_status +async def hash_get(client: Client, message: Message): + no_hash = message.text[1:].lower().split()[0] + await get(client, message, no_hash, show_none=False) + + +@pbot.on_message(filters.command("save") & filters.group) +@user_admin +@connection_status +async def save(client: Client, message: Message): + chat_id = message.chat.id + note_name, text, data_type, content, buttons = await get_note_type(message) + if not note_name: + await message.reply_text("Dude, you need to specify a note name!") + return + + note_name = note_name.lower() + if data_type is None: + await message.reply_text("Dude, there's no note content!") + return + + sql.add_note_to_db(chat_id, note_name, text, data_type, buttons=buttons, file=content) + await message.reply_text( + f"Yas! Added `{note_name}`.\nGet it with /get `{note_name}`, or `#{note_name}`", + parse_mode=enums.ParseMode.MARKDOWN, + ) + + +@pbot.on_message(filters.command("clear") & filters.group) +@user_admin +@connection_status +async def clear(client: Client, message: Message): + args = message.command[1:] + chat_id = message.chat.id + if len(args) >= 1: + notename = args[0].lower() + if sql.rm_note(chat_id, notename): + await message.reply_text("Successfully removed note.") + else: + await message.reply_text("That's not a note in my database!") + + +@pbot.on_message(filters.command("removeallnotes") & filters.group) +async def clearall(client: Client, message: Message): + chat = message.chat + user = message.from_user + member = await chat.get_member(user.id) + if member.status != enums.ChatMemberStatus.OWNER: + await message.reply_text("Only the chat owner can clear all notes at once.") + return + + buttons = InlineKeyboardMarkup( + [ + [InlineKeyboardButton(text="Delete all notes", callback_data="notes_rmall")], + [InlineKeyboardButton(text="Cancel", callback_data="notes_cancel")], + ] + ) + await message.reply_text( + f"Are you sure you would like to clear ALL notes in {chat.title}? This action cannot be undone.", + reply_markup=buttons, + ) + + +@pbot.on_callback_query(filters.regex(r"notes_.*")) +async def clearall_btn(client: Client, query): + chat = query.message.chat + member = await chat.get_member(query.from_user.id) + if query.data == "notes_rmall": + if member.status == enums.ChatMemberStatus.OWNER or query.from_user.id in DRAGONS: + note_list = sql.get_all_chat_notes(chat.id) + for note in note_list: + sql.rm_note(chat.id, note.name.lower()) + await query.message.edit_text("Deleted all notes.") + else: + await query.answer("Only the owner of the chat can do this.", show_alert=True) + elif query.data == "notes_cancel": + if member.status == enums.ChatMemberStatus.OWNER or query.from_user.id in DRAGONS: + await query.message.edit_text("Clearing of all notes has been cancelled.") + else: + await query.answer("Only the owner of the chat can do this.", show_alert=True) + + +@DisableAbleCommandHandler(["notes", "saved"]) +@connection_status +async def list_notes(client: Client, message: Message): + chat_id = message.chat.id + note_list = sql.get_all_chat_notes(chat_id) + if not note_list: + await message.reply_text("No notes in this chat!") + return + + msg = "Get note by `#notename` \n\n *ID* *Note* \n" + for i, note in enumerate(note_list, 1): + note_name = f"`{i:2}.` `#{(note.name.lower())}`\n" + if len(msg) + len(note_name) > 4096: + await message.reply_text(msg, parse_mode=enums.ParseMode.MARKDOWN) + msg = "" + msg += note_name + + if msg: + await message.reply_text(msg, parse_mode=enums.ParseMode.MARKDOWN) + + +__mod_name__ = "Notes" +__help__ = """ + ❍ `/get <notename>`: get the note with this notename + ❍ `#<notename>`: same as /get + ❍ `/notes` or `/saved`: list all saved notes in this chat + ❍ `/save <notename> <notedata>`: saves notedata as a note with name notename + ❍ `/save <notename>`: save the replied message as a note with name notename + ❍ `/clear <notename>`: clear note with this name + ❍ `/removeallnotes`: removes all notes from the group +""" diff --git a/QueenNoxi/modules/nsfw.py b/QueenNoxi/modules/nsfw.py new file mode 100644 index 0000000000000000000000000000000000000000..e19f51d544f4031b1b1bca336fc47feef3f721f8 --- /dev/null +++ b/QueenNoxi/modules/nsfw.py @@ -0,0 +1,820 @@ +import html +import os + +import nekos +import requests +from PIL import Image +from telegram import Update +from telegram.error import BadRequest, RetryAfter, Unauthorized +from telegram.ext import CallbackContext, CommandHandler, run_async +from telegram.utils.helpers import mention_html + +import QueenNoxi.modules.sql.nsfw_sql as sql +from QueenNoxi import dispatcher +from QueenNoxi.modules.helper_funcs.chat_status import user_admin +from QueenNoxi.modules.helper_funcs.filters import CustomFilters +from QueenNoxi.modules.log_channel import gloggable + + +@run_async +@user_admin +@gloggable +def add_nsfw(update: Update, context: CallbackContext): + chat = update.effective_chat + msg = update.effective_message + user = update.effective_user + is_nsfw = sql.is_nsfw(chat.id) + if not is_nsfw: + sql.set_nsfw(chat.id) + msg.reply_text("ᴀᴄᴛɪᴠᴀᴛɪᴏɴ ɴsғᴡ ᴍᴏᴅᴇ!") + message = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"ᴀᴄᴛɪᴠᴀᴛᴇᴅ_ɴsғᴡ\n" + f"<b>ᴀᴅᴍɪɴ:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + ) + return message + else: + msg.reply_text("ɴsғᴡ ᴍᴏᴅᴇ ɪs ᴀʟʀᴇᴀᴅʏ ᴀᴄᴛɪᴠᴀᴛᴇᴅ ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ") + return "" + + +@run_async +@user_admin +@gloggable +def rem_nsfw(update: Update, context: CallbackContext): + msg = update.effective_message + chat = update.effective_chat + user = update.effective_user + is_nsfw = sql.is_nsfw(chat.id) + if not is_nsfw: + msg.reply_text("ɴsғᴡ ᴍᴏᴅᴇ ɪs ᴀʟʀᴇᴀᴅʏ ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ") + return "" + else: + sql.rem_nsfw(chat.id) + msg.reply_text("ʀᴏʟʟᴇᴅ ʙᴀᴄᴋ ᴛᴏ ɴsғᴡ ᴍᴏᴅᴇ") + message = ( + f"<b>{html.escape(chat.title)}:</b>\n" + f"ᴅᴇᴀᴄᴛɪᴠᴀᴛᴇᴅ_ɴsғᴡ\n" + f"<b>Admin:</b> {mention_html(user.id, html.escape(user.first_name))}\n" + ) + return message + + +def list_nsfw_chats(update: Update, context: CallbackContext): + chats = sql.get_all_nsfw_chats() + text = "<b>ɴsғᴡ ᴀᴄᴛɪᴠᴀᴛᴇᴅ ᴄʜᴀᴛs</b>\n" + for chat in chats: + try: + x = context.bot.get_chat(int(*chat)) + name = x.title if x.title else x.first_name + text += f"• <code>{name}</code>\n" + except BadRequest: + sql.rem_nsfw(*chat) + except Unauthorized: + sql.rem_nsfw(*chat) + except RetryAfter as e: + sleep(e.retry_after) + update.effective_message.reply_text(text, parse_mode="HTML") + + +def neko(update, context): + msg = update.effective_message + target = "neko" + msg.reply_photo(nekos.img(target)) + + +def feet(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "feet" + msg.reply_photo(nekos.img(target)) + + +def yuri(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "yuri" + msg.reply_photo(nekos.img(target)) + + +def trap(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "trap" + msg.reply_photo(nekos.img(target)) + + +def futanari(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "futanari" + msg.reply_photo(nekos.img(target)) + + +def hololewd(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "hololewd" + msg.reply_photo(nekos.img(target)) + + +def lewdkemo(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "lewdkemo" + msg.reply_photo(nekos.img(target)) + + +def sologif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "solog" + msg.reply_video(nekos.img(target)) + + +def feetgif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "feetg" + msg.reply_video(nekos.img(target)) + + +def cumgif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "cum" + msg.reply_video(nekos.img(target)) + + +def erokemo(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "erokemo" + msg.reply_photo(nekos.img(target)) + + +def lesbian(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "les" + msg.reply_video(nekos.img(target)) + + +def wallpaper(update, context): + msg = update.effective_message + target = "wallpaper" + msg.reply_photo(nekos.img(target)) + + +def lewdk(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "lewdk" + msg.reply_photo(nekos.img(target)) + + +def ngif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "ngif" + msg.reply_video(nekos.img(target)) + + +def tickle(update, context): + msg = update.effective_message + target = "tickle" + msg.reply_video(nekos.img(target)) + + +def lewd(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "lewd" + msg.reply_photo(nekos.img(target)) + + +def feed(update, context): + msg = update.effective_message + target = "feed" + msg.reply_video(nekos.img(target)) + + +def eroyuri(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "eroyuri" + msg.reply_photo(nekos.img(target)) + + +def eron(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "eron" + msg.reply_photo(nekos.img(target)) + + +def cum(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "cum_jpg" + msg.reply_photo(nekos.img(target)) + + +def bjgif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "bj" + msg.reply_video(nekos.img(target)) + + +def bj(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "blowjob" + msg.reply_photo(nekos.img(target)) + + +def nekonsfw(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "nsfw_neko_gif" + msg.reply_video(nekos.img(target)) + + +def solo(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "solo" + msg.reply_photo(nekos.img(target)) + + +def kemonomimi(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "kemonomimi" + msg.reply_photo(nekos.img(target)) + + +def avatarlewd(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "nsfw_avatar" + with open("temp.png", "wb") as f: + f.write(requests.get(nekos.img(target)).content) + img = Image.open("temp.png") + img.save("temp.webp", "webp") + msg.reply_document(open("temp.webp", "rb")) + os.remove("temp.webp") + + +def gasm(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "gasm" + with open("temp.png", "wb") as f: + f.write(requests.get(nekos.img(target)).content) + img = Image.open("temp.png") + img.save("temp.webp", "webp") + msg.reply_document(open("temp.webp", "rb")) + os.remove("temp.webp") + + +def poke(update, context): + msg = update.effective_message + target = "poke" + msg.reply_video(nekos.img(target)) + + +def anal(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "anal" + msg.reply_video(nekos.img(target)) + + +def hentai(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "hentai" + msg.reply_photo(nekos.img(target)) + + +def avatar(update, context): + msg = update.effective_message + target = "nsfw_avatar" + with open("temp.png", "wb") as f: + f.write(requests.get(nekos.img(target)).content) + img = Image.open("temp.png") + img.save("temp.webp", "webp") + msg.reply_document(open("temp.webp", "rb")) + os.remove("temp.webp") + + +def erofeet(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "erofeet" + msg.reply_photo(nekos.img(target)) + + +def holo(update, context): + msg = update.effective_message + target = "holo" + msg.reply_photo(nekos.img(target)) + + +def keta(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "keta" + if not target: + msg.reply_text("No URL was received from the API!") + return + msg.reply_photo(nekos.img(target)) + + +def pussygif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "pussy" + msg.reply_video(nekos.img(target)) + + +def tits(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "tits" + msg.reply_photo(nekos.img(target)) + + +def holoero(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "holoero" + msg.reply_photo(nekos.img(target)) + + +def pussy(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "pussy_jpg" + msg.reply_photo(nekos.img(target)) + + +def hentaigif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "random_hentai_gif" + msg.reply_video(nekos.img(target)) + + +def classic(update, context): + msg = update.effective_message + target = "classic" + msg.reply_video(nekos.img(target)) + + +def kuni(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "kuni" + msg.reply_video(nekos.img(target)) + + +def waifu(update, context): + msg = update.effective_message + target = "waifu" + with open("temp.png", "wb") as f: + f.write(requests.get(nekos.img(target)).content) + img = Image.open("temp.png") + img.save("temp.webp", "webp") + msg.reply_document(open("temp.webp", "rb")) + os.remove("temp.webp") + + +def kiss(update, context): + msg = update.effective_message + target = "kiss" + msg.reply_video(nekos.img(target)) + + +def femdom(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "femdom" + msg.reply_photo(nekos.img(target)) + + +def hug(update, context): + msg = update.effective_message + target = "cuddle" + msg.reply_video(nekos.img(target)) + + +def erok(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "erok" + msg.reply_photo(nekos.img(target)) + + +def foxgirl(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "fox_girl" + msg.reply_photo(nekos.img(target)) + + +def titsgif(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "boobs" + msg.reply_video(nekos.img(target)) + + +def ero(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + target = "ero" + msg.reply_photo(nekos.img(target)) + + +def smug(update, context): + msg = update.effective_message + target = "smug" + msg.reply_video(nekos.img(target)) + + +def baka(update, context): + msg = update.effective_message + target = "baka" + msg.reply_video(nekos.img(target)) + + +def dva(update, context): + chat_id = update.effective_chat.id + if not update.effective_message.chat.type == "private": + is_nsfw = sql.is_nsfw(chat_id) + if not is_nsfw: + return + msg = update.effective_message + nsfw = requests.get("https://api.computerfreaker.cf/v1/dva").json() + url = nsfw.get("url") + # do shit with url if you want to + if not url: + msg.reply_text("No URL was received from the API!") + return + msg.reply_photo(url) + + +ADD_NSFW_HANDLER = CommandHandler("addnsfw", add_nsfw) +REMOVE_NSFW_HANDLER = CommandHandler("rmnsfw", rem_nsfw) +LIST_NSFW_CHATS_HANDLER = CommandHandler( + "nsfwchats", list_nsfw_chats, filters=CustomFilters.dev_filter +) +LEWDKEMO_HANDLER = CommandHandler("lewdkemo", lewdkemo) +NEKO_HANDLER = CommandHandler("neko", neko) +FEET_HANDLER = CommandHandler("feet", feet) +YURI_HANDLER = CommandHandler("yuri", yuri) +TRAP_HANDLER = CommandHandler("trap", trap) +FUTANARI_HANDLER = CommandHandler("futanari", futanari) +HOLOLEWD_HANDLER = CommandHandler("hololewd", hololewd) +SOLOGIF_HANDLER = CommandHandler("sologif", sologif) +CUMGIF_HANDLER = CommandHandler("cumgif", cumgif) +EROKEMO_HANDLER = CommandHandler("erokemo", erokemo) +LESBIAN_HANDLER = CommandHandler("lesbian", lesbian) +WALLPAPER_HANDLER = CommandHandler("wallpaper", wallpaper) +LEWDK_HANDLER = CommandHandler("lewdk", lewdk) +NGIF_HANDLER = CommandHandler("ngif", ngif) +TICKLE_HANDLER = CommandHandler("tickle", tickle) +LEWD_HANDLER = CommandHandler("lewd", lewd) +FEED_HANDLER = CommandHandler("feed", feed) +EROYURI_HANDLER = CommandHandler("eroyuri", eroyuri) +ERON_HANDLER = CommandHandler( + "eron", + eron, +) +CUM_HANDLER = CommandHandler("cum", cum) +BJGIF_HANDLER = CommandHandler("bjgif", bjgif) +BJ_HANDLER = CommandHandler("bj", bj) +NEKONSFW_HANDLER = CommandHandler("nekonsfw", nekonsfw) +SOLO_HANDLER = CommandHandler("solo", solo) +KEMONOMIMI_HANDLER = CommandHandler("kemonomimi", kemonomimi) +AVATARLEWD_HANDLER = CommandHandler("avatarlewd", avatarlewd) +GASM_HANDLER = CommandHandler("gasm", gasm) +POKE_HANDLER = CommandHandler("poke", poke) +ANAL_HANDLER = CommandHandler("anal", anal) +HENTAI_HANDLER = CommandHandler("hentai", hentai) +AVATAR_HANDLER = CommandHandler("avatar", avatar) +EROFEET_HANDLER = CommandHandler("erofeet", erofeet) +HOLO_HANDLER = CommandHandler("holo", holo) +TITS_HANDLER = CommandHandler("tits", tits) +PUSSYGIF_HANDLER = CommandHandler("pussygif", pussygif) +HOLOERO_HANDLER = CommandHandler("holoero", holoero) +PUSSY_HANDLER = CommandHandler("pussy", pussy) +HENTAIGIF_HANDLER = CommandHandler("hentaigif", hentaigif) +CLASSIC_HANDLER = CommandHandler("classic", classic) +KUNI_HANDLER = CommandHandler("kuni", kuni) +WAIFU_HANDLER = CommandHandler("waifu", waifu) +LEWD_HANDLER = CommandHandler("lewd", lewd) +KISS_HANDLER = CommandHandler("kiss", kiss) +FEMDOM_HANDLER = CommandHandler("femdom", femdom) +CUDDLE_HANDLER = CommandHandler("hug", hug) +EROK_HANDLER = CommandHandler("erok", erok) +FOXGIRL_HANDLER = CommandHandler("foxgirl", foxgirl) +TITSGIF_HANDLER = CommandHandler("titsgif", titsgif) +ERO_HANDLER = CommandHandler("ero", ero) +SMUG_HANDLER = CommandHandler("smug", smug) +BAKA_HANDLER = CommandHandler("baka", baka) +DVA_HANDLER = CommandHandler("dva", dva) + + +dispatcher.add_handler(ADD_NSFW_HANDLER) +dispatcher.add_handler(REMOVE_NSFW_HANDLER) +dispatcher.add_handler(LIST_NSFW_CHATS_HANDLER) +dispatcher.add_handler(LEWDKEMO_HANDLER) +dispatcher.add_handler(NEKO_HANDLER) +dispatcher.add_handler(FEET_HANDLER) +dispatcher.add_handler(YURI_HANDLER) +dispatcher.add_handler(TRAP_HANDLER) +dispatcher.add_handler(FUTANARI_HANDLER) +dispatcher.add_handler(HOLOLEWD_HANDLER) +dispatcher.add_handler(SOLOGIF_HANDLER) +dispatcher.add_handler(CUMGIF_HANDLER) +dispatcher.add_handler(EROKEMO_HANDLER) +dispatcher.add_handler(LESBIAN_HANDLER) +dispatcher.add_handler(WALLPAPER_HANDLER) +dispatcher.add_handler(LEWDK_HANDLER) +dispatcher.add_handler(NGIF_HANDLER) +dispatcher.add_handler(TICKLE_HANDLER) +dispatcher.add_handler(LEWD_HANDLER) +dispatcher.add_handler(FEED_HANDLER) +dispatcher.add_handler(EROYURI_HANDLER) +dispatcher.add_handler(ERON_HANDLER) +dispatcher.add_handler(CUM_HANDLER) +dispatcher.add_handler(BJGIF_HANDLER) +dispatcher.add_handler(BJ_HANDLER) +dispatcher.add_handler(NEKONSFW_HANDLER) +dispatcher.add_handler(SOLO_HANDLER) +dispatcher.add_handler(KEMONOMIMI_HANDLER) +dispatcher.add_handler(AVATARLEWD_HANDLER) +dispatcher.add_handler(GASM_HANDLER) +dispatcher.add_handler(POKE_HANDLER) +dispatcher.add_handler(ANAL_HANDLER) +dispatcher.add_handler(HENTAI_HANDLER) +dispatcher.add_handler(AVATAR_HANDLER) +dispatcher.add_handler(EROFEET_HANDLER) +dispatcher.add_handler(HOLO_HANDLER) +dispatcher.add_handler(TITS_HANDLER) +dispatcher.add_handler(PUSSYGIF_HANDLER) +dispatcher.add_handler(HOLOERO_HANDLER) +dispatcher.add_handler(PUSSY_HANDLER) +dispatcher.add_handler(HENTAIGIF_HANDLER) +dispatcher.add_handler(CLASSIC_HANDLER) +dispatcher.add_handler(KUNI_HANDLER) +dispatcher.add_handler(WAIFU_HANDLER) +dispatcher.add_handler(LEWD_HANDLER) +dispatcher.add_handler(KISS_HANDLER) +dispatcher.add_handler(FEMDOM_HANDLER) +dispatcher.add_handler(CUDDLE_HANDLER) +dispatcher.add_handler(EROK_HANDLER) +dispatcher.add_handler(FOXGIRL_HANDLER) +dispatcher.add_handler(TITSGIF_HANDLER) +dispatcher.add_handler(ERO_HANDLER) +dispatcher.add_handler(SMUG_HANDLER) +dispatcher.add_handler(BAKA_HANDLER) +dispatcher.add_handler(DVA_HANDLER) + +__handlers__ = [ + ADD_NSFW_HANDLER, + REMOVE_NSFW_HANDLER, + LIST_NSFW_CHATS_HANDLER, + NEKO_HANDLER, + FEET_HANDLER, + YURI_HANDLER, + TRAP_HANDLER, + FUTANARI_HANDLER, + HOLOLEWD_HANDLER, + SOLOGIF_HANDLER, + CUMGIF_HANDLER, + EROKEMO_HANDLER, + LESBIAN_HANDLER, + WALLPAPER_HANDLER, + LEWDK_HANDLER, + NGIF_HANDLER, + TICKLE_HANDLER, + LEWD_HANDLER, + FEED_HANDLER, + EROYURI_HANDLER, + ERON_HANDLER, + CUM_HANDLER, + BJGIF_HANDLER, + BJ_HANDLER, + NEKONSFW_HANDLER, + SOLO_HANDLER, + KEMONOMIMI_HANDLER, + AVATARLEWD_HANDLER, + GASM_HANDLER, + POKE_HANDLER, + ANAL_HANDLER, + HENTAI_HANDLER, + AVATAR_HANDLER, + EROFEET_HANDLER, + HOLO_HANDLER, + TITS_HANDLER, + PUSSYGIF_HANDLER, + HOLOERO_HANDLER, + PUSSY_HANDLER, + HENTAIGIF_HANDLER, + CLASSIC_HANDLER, + KUNI_HANDLER, + WAIFU_HANDLER, + LEWD_HANDLER, + KISS_HANDLER, + FEMDOM_HANDLER, + LEWDKEMO_HANDLER, + CUDDLE_HANDLER, + EROK_HANDLER, + FOXGIRL_HANDLER, + TITSGIF_HANDLER, + ERO_HANDLER, + SMUG_HANDLER, + BAKA_HANDLER, + DVA_HANDLER, +] +__mod_name__ = "Nsғᴡ" + +__help__ = """ +*ɴsғᴡ:* +❂ /addnsfw : ᴇɴᴀʙʟᴇ ɴsғᴡ ᴍᴏᴅᴇ +❂ /rmnsfw : ᴅɪsᴀʙʟᴇ ɴsғᴡ ᴍᴏᴅᴇ + +*ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs:* +❂ /neko : sᴇɴᴅs ʀᴀɴᴅᴏᴍ sғᴡ ɴᴇᴋᴏ sᴏᴜʀᴄᴇ ɪᴍᴀɢᴇs. +❂ /ngif : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ɴᴇᴋᴏ ɢɪғs. +❂ /tickle : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴛɪᴄᴋʟᴇ ɢɪғs. +❂ /feed : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ғᴇᴇᴅɪɴɢ ɢɪғs. +❂ /gasm : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴏʀɢᴀsᴍ sᴛɪᴄᴋᴇʀs. +❂ /kuni : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴘᴜssʏ ʟɪᴄᴋ ɢɪғs. +❂ /waifu : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴡᴀɪғᴜ sᴛɪᴄᴋᴇʀs. +❂ /kiss : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴋɪssɪɴɢ ɢɪғs. +❂ /erok : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ᴇʀᴏ-ᴋɪᴛsᴜɴᴇ sᴏᴜʀᴄᴇ ɪᴍᴀɢᴇs. +❂ /foxgirl : sᴇɴᴅs ʀᴀɴᴅᴏᴍ ғᴏxɢɪʀʟ sᴏᴜʀᴄᴇ ɪᴍᴀɢᴇs. +❂ /smug : sᴇɴᴅs ʀᴀɴᴅᴏᴍ sᴍᴜɢ ɢɪғs. +""" diff --git a/QueenNoxi/modules/paste.py b/QueenNoxi/modules/paste.py new file mode 100644 index 0000000000000000000000000000000000000000..74c4a4d0770eea3da8a56a0b67ab66986976e8ae --- /dev/null +++ b/QueenNoxi/modules/paste.py @@ -0,0 +1,104 @@ +"""MIT License + +Copyright (c) 2023-24 Noob-QueenNoxi + + GITHUB: NOOB-MUKESH + TELEGRAM: @MR_SUKKUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.""" +import asyncio +import os +import re + +import requests +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton,InlineKeyboardMarkup + +from .. import pbot as app +import QueenNoxi +from aiohttp import ClientSession + +pattern = re.compile(r"^text/|json$|yaml$|xml$|toml$|x-sh$|x-shellscript$") +import socket +from asyncio import get_running_loop +from functools import partial +import aiofiles + + +def paste(content): + url ="https://pastebin.com/api/api_post.php" + data = {"api_dev_key":"9Rfu50iV5l3EuRWATw7EDLuC37RED-C4","api_paste_code": content,"api_option": "paste"} + response = requests.post(url, data=data) + link=response.text + return link +def paste2(content): + url = 'https://dpaste.org/api/' + payload = {'content': content.encode('utf-8'), 'format': 'url'} + response = requests.post(url, data=payload) + link2=response.text + return link2 +async def isPreviewUp(preview: str) -> bool: + for _ in range(7): + try: + async with QueenNoxi.aiohttpsession.head(preview, timeout=2) as resp: + status = resp.status + size = resp.content_length + except asyncio.exceptions.TimeoutError: + return False + if status == 404 or (status == 200 and size == 0): + await asyncio.sleep(0.4) + else: + return True if status == 200 else False + return False + +@app.on_message(filters.command("paste")) + +async def paste_func(_, message): + if not message.reply_to_message: + return await message.reply_text("Reply To A Message With /paste") + m = await message.reply_text("Pasting...") + if message.reply_to_message.text: + content = str(message.reply_to_message.text) + elif message.reply_to_message.document: + document = message.reply_to_message.document + if document.file_size > 1048576: + return await m.edit("You can only paste files smaller than 1MB.") + if not pattern.search(document.mime_type): + return await m.edit("Only text files can be pasted.") + doc = await message.reply_to_message.download() + async with aiofiles.open(doc, mode="r") as f: + content = await f.read() + os.remove(doc) + link = paste(content) + # link2 = paste2(content) + button=InlineKeyboardMarkup([[(InlineKeyboardButton(text="• ᴘᴀsᴛᴇ ʟɪɴᴋ •", url=link))]]) + # button.add(InlineKeyboardButton("• ᴘᴀsᴛᴇ ʟɪɴᴋ 2 •", url=link2)) + try: + await message.reply("ʜᴇʀᴇ ɪs ʏᴏᴜʀ ᴘᴀsᴛᴇ ʟɪɴᴋ :",reply_markup=button) + await m.delete() + except Exception as e: + await m.edit(f"Error: {e}") + + +__mod_name__ = "Pᴀs​ᴛᴇ" +__help__ = """ + ᴘᴀsᴛᴇs ᴛʜᴇ ɢɪᴠᴇɴ ғɪʟᴇ ᴀɴᴅ sʜᴏᴡs ʏᴏᴜ ᴛʜᴇ ʀᴇsᴜʟᴛ + + ❍ /paste *:* ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇxᴛ ғɪʟᴇ + """ diff --git a/QueenNoxi/modules/phone.py b/QueenNoxi/modules/phone.py new file mode 100644 index 0000000000000000000000000000000000000000..d84bb78653a31a2caadb5f645c4f867f76b2c9ff --- /dev/null +++ b/QueenNoxi/modules/phone.py @@ -0,0 +1,57 @@ +import json + +import requests +from telegram.ext import CommandHandler, run_async + +from QueenNoxi import dispatcher +from QueenNoxi.modules.helper_funcs.alternate import send_message +from QueenNoxi.modules.helper_funcs.chat_status import user_admin + +__mod_name__ = "Pʜᴏɴᴇ" +__help__ = """ +» /phone ꜰɪʟʟ ᴀɴʏ ᴍᴏʙɪʟᴇ ɴᴜᴍʙᴇʀ ᴛᴏ ᴄʜᴇᴄᴋ ɪɴꜰᴏ. +""" + + +@run_async +@user_admin +def phone(update, context): + + args = update.effective_message.text.split(None, 1) + information = args[1] + number = information + key = "f66950368a61ebad3cba9b5924b4532d" + api = ( + "http://apilayer.net/api/validate?access_key=" + + key + + "&number=" + + number + + "&country_code=&format=1" + ) + output = requests.get(api) + content = output.text + obj = json.loads(content) + country_code = obj["country_code"] + country_name = obj["country_name"] + location = obj["location"] + carrier = obj["carrier"] + line_type = obj["line_type"] + validornot = obj["valid"] + aa = "Valid: " + str(validornot) + a = "Phone number: " + str(number) + b = "Country: " + str(country_code) + c = "Country Name: " + str(country_name) + d = "Location: " + str(location) + e = "Carrier: " + str(carrier) + f = "Device: " + str(line_type) + g = f"{aa}\n{a}\n{b}\n{c}\n{d}\n{e}\n{f}" + send_message(update.effective_message, g) + + +PHONE_HANDLER = CommandHandler("phone", phone) + +dispatcher.add_handler(PHONE_HANDLER) + + +__command_list__ = ["phone"] +__handlers__ = [PHONE_HANDLER] diff --git a/QueenNoxi/modules/ping.py b/QueenNoxi/modules/ping.py new file mode 100644 index 0000000000000000000000000000000000000000..18474dc99a159809a52feea2a83f529926173e6f --- /dev/null +++ b/QueenNoxi/modules/ping.py @@ -0,0 +1,76 @@ +import time +import aiohttp +from pyrogram import filters, Client +from pyrogram.types import Message + +from QueenNoxi import StartTime, pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import sudo_plus +from QueenNoxi.utils.formatter import get_readable_time + +sites_list = { + "Telegram": "https://api.telegram.org", + "Kaizoku": "https://animekaizoku.com", + "Kayo": "https://animekayo.com", + "Jikan": "https://api.jikan.moe/v3", +} + + + +async def ping_func(to_ping: list) -> list: + ping_result = [] + async with aiohttp.ClientSession() as session: + for each_ping in to_ping: + start_time = time.time() + site_to_ping = sites_list[each_ping] + try: + async with session.get(site_to_ping) as response: + status_code = response.status + except Exception: + status_code = "Error" + end_time = time.time() + ping_time = str(round((end_time - start_time), 2)) + "s" + + pinged_site = f"<b>{each_ping}</b>" + if each_ping == "Kaizoku" or each_ping == "Kayo": + pinged_site = f'<a href="{sites_list[each_ping]}">{each_ping}</a>' + ping_time = f"<code>{ping_time} (Status: {status_code})</code>" + + ping_text = f"{pinged_site}: <code>{ping_time}</code>" + ping_result.append(ping_text) + return ping_result + +@pbot.on_message(filters.command("ping")) +@DisableAbleCommandHandler("ping") +@sudo_plus +async def ping(client: Client, message: Message): + start_time = time.time() + reply = await message.reply_text("🏓 ᴘɪɴɢɪɴɢ ʙᴀʙʏ....​") + end_time = time.time() + telegram_ping = str(round((end_time - start_time) * 1000, 3)) + " ms" + uptime = get_readable_time((time.time() - StartTime)) + + await reply.edit_text( + "ɪ ᴀᴍ ᴀʟɪᴠᴇ ʙᴀʙʏ! ❤️\n" + "<b>ᴛɪᴍᴇ ᴛᴀᴋᴇɴ:</b> <code>{}</code>\n" + "<b>ᴜᴘᴛɪᴍᴇ:</b> <code>{}</code>".format(telegram_ping, uptime) + ) + +@pbot.on_message(filters.command("pingall")) +@DisableAbleCommandHandler("pingall") +@sudo_plus +async def pingall(client: Client, message: Message): + to_ping = ["Telegram"] + pinged_list = await ping_func(to_ping) + uptime = get_readable_time((time.time() - StartTime)) + + reply_msg = "⏱ᴘɪɴɢ ʀᴇsᴜʟᴛs ᴀʀᴇ:\n" + reply_msg += "\n".join(pinged_list) + reply_msg += f"\n<b>ᴜᴘᴛɪᴍᴇ:</b> <code>{uptime}</code>" + + await message.reply_text( + reply_msg, disable_web_page_preview=True + ) + +__mod_name__ = "Ping" +__command_list__ = ["ping", "pingall"] diff --git a/QueenNoxi/modules/purge.py b/QueenNoxi/modules/purge.py new file mode 100644 index 0000000000000000000000000000000000000000..f2e53785655b4a8c2ce55fe9d6be3b2766245232 --- /dev/null +++ b/QueenNoxi/modules/purge.py @@ -0,0 +1,126 @@ +import time + +from telethon import events + +from QueenNoxi import telethn,pbot +from QueenNoxi.modules.helper_funcs.telethn.chatstatus import ( + can_delete_messages, + user_is_admin, +) +from pyrogram import filters +from pyrogram.enums import ChatMemberStatus +from QueenNoxi import DRAGONS + + +async def purge_messages(event): + start = time.perf_counter() + if event.from_id is None: + return + + if not await user_is_admin( + user_id=event.sender_id, message=event + ) and event.from_id not in [1087968824]: + await event.reply("Only Admins are allowed to use this command") + return + + if not await can_delete_messages(message=event): + await event.reply("Can't seem to purge the message") + return + + reply_msg = await event.get_reply_message() + if not reply_msg: + await event.reply("Reply to a message to select where to start purging from.") + return + messages = [] + message_id = reply_msg.id + delete_to = event.message.id + + messages.append(event.reply_to_msg_id) + for msg_id in range(message_id, delete_to + 1): + messages.append(msg_id) + if len(messages) == 100: + await event.client.delete_messages(event.chat_id, messages) + messages = [] + + try: + await event.client.delete_messages(event.chat_id, messages) + except: + pass + time_ = time.perf_counter() - start + text = f"ᴘᴜʀɢᴇᴅ ꜱᴜᴄᴄᴇꜱꜱꜰᴜʟʟʏ ɪɴ {time_:0.2f} ꜱᴇᴄᴏɴᴅ(s)\nꜰᴀꜱᴛ ᴀꜰ 😎" + await event.respond(text, parse_mode="markdown") + + +async def delete_messages(event): + if event.from_id is None: + return + + if not await user_is_admin( + user_id=event.sender_id, message=event + ) and event.from_id not in [1087968824]: + await event.reply("Only Admins are allowed to use this command") + return + + if not await can_delete_messages(message=event): + await event.reply("Can't seem to delete this?") + return + + message = await event.get_reply_message() + if not message: + await event.reply("Whadya want to delete?") + return + chat = await event.get_input_chat() + del_message = [message, event.message] + await event.client.delete_messages(chat, del_message) +async def spurge_messages(event): + if event.from_id is None: + return + if not await user_is_admin( + user_id=event.sender_id, message=event + ) and event.from_id not in [1087968824]: + await event.reply("Only Admins are allowed to use this command") + return + + if not await can_delete_messages(message=event): + await event.reply("Can't seem to purge the message") + return + + reply_msg = await event.get_reply_message() + if not reply_msg: + await event.reply("Reply to a message to select where to start purging from.") + return + messages = [] + message_id = reply_msg.id + delete_to = event.message.id + + messages.append(event.reply_to_msg_id) + for msg_id in range(message_id, delete_to + 1): + messages.append(msg_id) + if len(messages) == 100: + await event.client.delete_messages(event.chat_id, messages) + messages = [] + + try: + await event.client.delete_messages(event.chat_id, messages) + except: + pass + + +__help__ = """ + ❍ /del *:* ᴅᴇʟᴇᴛᴇs ᴛʜᴇ ᴍᴇssᴀɢᴇ ʏᴏᴜ ʀᴇᴘʟɪᴇᴅ ᴛᴏ + + ❍ /purge *:* ᴅᴇʟᴇᴛᴇs ᴀʟʟ ᴍᴇssᴀɢᴇs ʙᴇᴛᴡᴇᴇɴ ᴛʜɪs ᴀɴᴅ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴍᴇssᴀɢᴇ. + ❍ /purge <ɪɴᴛᴇɢᴇʀ x>*:* ᴅᴇʟᴇᴛᴇs ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴍᴇssᴀɢᴇ, ᴀɴᴅ x ᴍᴇssᴀɢᴇs ғᴏʟʟᴏᴡɪɴɢ ɪᴛ ɪғ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ. + ❍ /spurge *:* ᴅᴇʟᴇᴛᴇs ᴀʟʟ ᴍᴇssᴀɢᴇs ʙᴇᴛᴡᴇᴇɴ ᴛʜɪs ᴀɴᴅ ᴛʜᴇ ʀᴇᴘʟɪᴇᴅ ᴛᴏ ᴍᴇssᴀɢᴇ. +""" + +PURGE_HANDLER = purge_messages, events.NewMessage(pattern="^[!/]purge$") + +DEL_HANDLER = delete_messages, events.NewMessage(pattern="^[!/]del$") +SPURGE_HANDLER = spurge_messages, events.NewMessage(pattern="^[!/]spurge$") +telethn.add_event_handler(*PURGE_HANDLER) +telethn.add_event_handler(*DEL_HANDLER) +telethn.add_event_handler(*SPURGE_HANDLER) +__mod_name__ = "Pᴜʀɢᴇ" +__command_list__ = ["del", "purge","spurge"] +__handlers__ = [PURGE_HANDLER, DEL_HANDLER,SPURGE_HANDLER] diff --git a/QueenNoxi/modules/qrcode.py b/QueenNoxi/modules/qrcode.py new file mode 100644 index 0000000000000000000000000000000000000000..b456e28be41e56a144c8afbdf3f1bb0495658bdb --- /dev/null +++ b/QueenNoxi/modules/qrcode.py @@ -0,0 +1,55 @@ +"""MIT License + +Copyright (c) 2023-24 Noob-QueenNoxi + + GITHUB: NOOB-MUKESH + TELEGRAM: @MR_SUKKUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.""" +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.enums import ChatAction + +from .. import pbot as QueenNoxi,BOT_USERNAME +import requests + + +@QueenNoxi.on_message(filters.command("qrcode")) +async def qrcode_(_, message: Message): + if message.reply_to_message: + text = message.reply_to_message.text + else: + text =message.text.split(None, 1)[1] + m =await message.reply_text( "`Please wait...,\n\nCreating your Qrcode ...`") + write = requests.get(f"https://queennoxi-api.vercel.app/qrcode?query={text}").json()["results"] + + caption = f""" +sᴜᴄᴇssғᴜʟʟʏ Gᴇɴᴇʀᴀᴛᴇᴅ Qʀᴄᴏᴅᴇ 💘 +✨ **Gᴇɴᴇʀᴀᴛᴇᴅ ʙʏ :** @{BOT_USERNAME} +🥀 **ʀᴇǫᴜᴇsᴛᴇᴅ ʙʏ :** {message.from_user.mention} +""" + await m.delete() + await message.reply_photo(photo=write,caption=caption) +# -----------CREDITS ----------- +# telegram : @legend_coder +# github : Alexainc +__mod_name__ = "Qʀᴄᴏᴅᴇ" +__help__ = """ + ➻ /qrcode : ɢɪᴠᴇ sᴏᴍᴇ ᴛᴇxᴛ ᴛᴏ ɢᴇɴᴇʀᴀᴛᴇ ǫʀᴄᴏᴅᴇ + """ diff --git a/QueenNoxi/modules/quotly.py b/QueenNoxi/modules/quotly.py new file mode 100644 index 0000000000000000000000000000000000000000..b01e425476bb1ea2eabac3557bb566d13243a162 --- /dev/null +++ b/QueenNoxi/modules/quotly.py @@ -0,0 +1,229 @@ +import asyncio +import base64 +import os +import aiohttp +import json +import io +from pyrogram import filters, enums +from pyrogram.types import Message +from pyrogram.errors import RPCError +from QueenNoxi import pbot, LOGGER, BOT_ID + +# --- QUOTLY API CONFIG (Matching alexatg project) --- +API_URL = "https://quotlytga-quoteapi.hf.space/api/generate" + +async def download_thumb(client, message: Message): + """Select the best thumbnail based on JS reference priority""" + try: + media = message.photo or message.sticker or message.document or message.animation or message.video + if not media: + return None + + if message.sticker: + if not (message.sticker.is_video or message.sticker.is_animated): + path = await client.download_media(message.sticker.file_id) + if path: + with open(path, "rb") as f: data = f.read() + os.remove(path) + return data + + if hasattr(media, "thumbs") and media.thumbs: + priority = ['v', 'm', 'y', 'x', 'w', 's'] + thumb_to_download = None + for p in priority: + for thumb in media.thumbs: + if hasattr(thumb, "type") and thumb.type == p: + thumb_to_download = thumb + break + if thumb_to_download: break + if not thumb_to_download: thumb_to_download = media.thumbs[-1] + path = await client.download_media(thumb_to_download.file_id) + if path: + with open(path, "rb") as f: data = f.read() + os.remove(path) + return data + + if message.photo: + path = await client.download_media(message.photo.file_id) + if path: + with open(path, "rb") as f: data = f.read() + os.remove(path) + return data + except Exception as e: + LOGGER.warning(f"Thumb download failed: {e}") + return None + +async def get_avatar_b64(client, user_id): + """Fetch avatar and return as Data URL (using avatarBase64 key in API)""" + try: + chat = await client.get_chat(user_id) + if chat.photo: + path = await client.download_media(chat.photo.small_file_id) + if path: + with open(path, "rb") as f: content = f.read() + os.remove(path) + return "data:image/png;base64," + base64.b64encode(content).decode() + except Exception: + pass + return "" + +def format_entities(entities): + if not entities: return [] + res = [] + for ent in entities: + ent_type = ent.type.name.lower() + if ent_type == "text_mention": ent_type = "mention" + elif ent_type == "strikethrough": ent_type = "strike" + entry = {"type": ent_type, "offset": ent.offset, "length": ent.length} + # Pass custom emoji document_id so the API can render premium emoji + if ent_type == "custom_emoji" and hasattr(ent, "custom_emoji_id") and ent.custom_emoji_id: + entry["document_id"] = ent.custom_emoji_id + res.append(entry) + return res + +async def get_message_data(client, message: Message, include_reply=False): + user = message.from_user or message.sender_chat + user_id = user.id if user else 1 + + if hasattr(user, "first_name"): + first_name = user.first_name or "User" + last_name = getattr(user, "last_name", "") or "" + else: + first_name = getattr(user, "title", "User") + last_name = "" + + avatar_b64 = await get_avatar_b64(client, user_id) + entities = format_entities(message.entities or message.caption_entities) + media_data = await download_thumb(client, message) + media_b64 = ("data:image/png;base64," + base64.b64encode(media_data).decode()) if media_data else None + + # Try to get emoji status (custom emoji on profile) + emoji_status_id = None + try: + full_user = await client.get_users(user_id) + if hasattr(full_user, "emoji_status") and full_user.emoji_status: + emoji_status_id = getattr(full_user.emoji_status, "custom_emoji_id", None) + except Exception: + pass + + # schema strictly aligned with alexatg/generatequote2.js + msg_data = { + "id": str(user_id), + "firstName": first_name, + "lastName": last_name, + "avatarBase64": avatar_b64, + "message": message.text or message.caption or "", + "nameColorId": user_id % 7, + "entities": entities, + "mediaBase64": media_b64, + "isSticker": bool(media_b64 and not (message.text or message.caption)), + "isAbsoluteLast": False + } + + if emoji_status_id: + msg_data["emojiStatusId"] = emoji_status_id + + if message.forward_origin: + origin = message.forward_origin + try: + if origin.type == enums.MessageOriginType.USER: + u = origin.sender_user + msg_data["forwardName"] = f"{u.first_name} {u.last_name or ''}".strip() + elif origin.type == enums.MessageOriginType.CHAT: + msg_data["forwardName"] = origin.sender_chat.title + elif origin.type == enums.MessageOriginType.CHANNEL: + msg_data["forwardName"] = origin.chat.title + except: pass + + if include_reply and message.reply_to_message: + rep = message.reply_to_message + rep_user = rep.from_user or rep.sender_chat + msg_data["replySender"] = (rep_user.first_name if hasattr(rep_user, "first_name") else getattr(rep_user, "title", "User")) if rep_user else "User" + msg_data["replyMessage"] = rep.text or rep.caption or (rep.sticker.emoji if rep.sticker else "Media") + msg_data["replysendercolor"] = (rep_user.id if rep_user else 0) % 7 + + return msg_data + +async def generate_quote(messages, background_color="#1b1429"): + payload = { + "type": "quote", + "format": "webp", + "backgroundColor": background_color, + "width": 512, + "height": 768, + "scale": 2, + "messages": messages + } + + async with aiohttp.ClientSession() as session: + try: + async with session.post(API_URL, json=payload, timeout=60) as resp: + if resp.status == 200: + content_type = resp.headers.get("Content-Type", "") + if "image" in content_type: + return await resp.read() + data = await resp.json() + if data.get("ok"): + return base64.b64decode(data["result"]["image"]) + elif "result" in data and "image" in data["result"]: + return base64.b64decode(data["result"]["image"]) + except Exception as e: + LOGGER.error(f"Generate Quote Error: {e}") + return None + +@pbot.on_message(filters.command(["q", "quote"])) +async def quotly(client, message: Message): + if not message.reply_to_message: + return await message.reply_text("Reply to a message with `/q` to create a quote sticker.") + + args = message.text.split()[1:] + count = 1 + include_reply = "r" in [a.lower() for a in args] + for arg in args: + if arg.isdigit(): count = min(20, max(1, int(arg))) + + processing = await message.reply_text("<code>Generating quote sticker...</code>") + messages_to_quote = [] + + try: + start_id = message.reply_to_message.id + msg_ids = [start_id + i for i in range(count)] + fetched = await client.get_messages(message.chat.id, msg_ids) + if not isinstance(fetched, list): fetched = [fetched] + + for msg in fetched: + if msg and not msg.empty: + if msg.text and msg.text.startswith("/") and len(fetched) > 1: continue + data = await get_message_data(client, msg, include_reply if msg.id == start_id else False) + messages_to_quote.append(data) + + if not messages_to_quote: + return await processing.edit_text("Could not fetch messages.") + + messages_to_quote[-1]["isAbsoluteLast"] = True + quote_content = await generate_quote(messages_to_quote) + + # Automatic fallback without images if API fails + if not quote_content and any(m.get("avatarBase64") or m.get("mediaBase64") for m in messages_to_quote): + LOGGER.info("Quotly failed with images, retrying text-only...") + for m in messages_to_quote: + m["avatarBase64"] = "" + m["mediaBase64"] = None + quote_content = await generate_quote(messages_to_quote) + + if quote_content: + await processing.delete() + sticker = io.BytesIO(quote_content) + sticker.name = "quote.webp" + if count >= 6: + await message.reply_document(sticker, caption="Quote generated!") + else: + await message.reply_sticker(sticker) + else: + await processing.edit_text("Failed to generate quote. The API might be down.") + + except Exception as e: + LOGGER.exception(f"Quotly Command Error: {e}") + await processing.edit_text("Something went wrong.") + +__mod_name__ = "Quotly" diff --git a/QueenNoxi/modules/reactions.py b/QueenNoxi/modules/reactions.py new file mode 100644 index 0000000000000000000000000000000000000000..62d2347bbee21d63c05f848b921760f2d01c11cd --- /dev/null +++ b/QueenNoxi/modules/reactions.py @@ -0,0 +1,224 @@ +import random +from pyrogram import filters, Client +from pyrogram.types import Message + +from QueenNoxi import pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler + +reactions = [ + "( ͡° ͜ʖ ͡°)", + "( . •́ _ʖ •̀ .)", + "( ಠ ͜ʖ ಠ)", + "( ͡ ͜ʖ ͡ )", + "(ʘ ͜ʖ ʘ)", + "ヾ(´〇`)ノ♪♪♪", + "ヽ(o´∀`)ノ♪♬", + "♪♬((d⌒ω⌒b))♬♪", + "└(^^)┐", + "( ̄▽ ̄)/♫•*¨*•.¸¸♪", + "ヾ(⌐■_■)ノ♪", + "乁( • ω •乁)", + "♬♫♪◖(● o ●)◗♪♫♬", + "(っ˘ڡ˘ς)", + "( ˘▽˘)っ♨", + "( ・ω・)⊃-[二二]", + "(*´ー`)旦 旦( ̄ω ̄*)", + "(  ̄▽ ̄)[] [](≧▽≦ )", + "(* ̄▽ ̄)旦 且(´∀`*)", + "(ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ", + "(ノ°∀°)ノ⌒・*:.。. .。.:*・゜゚・*☆", + "(⊃。•́‿•̀。)⊃━✿✿✿✿✿✿", + "(∩` ロ ´)⊃━炎炎炎炎炎", + "( ・∀・)・・・--------☆", + "( -ω-)/占~~~~~", + "○∞∞∞∞ヽ(^ー^ )", + "(*^^)/~~~~~~~~~~◎", + "(((  ̄□)_/", + "(メ ̄▽ ̄)︻┳═一", + "ヽ( ・∀・)ノ_θ彡☆Σ(ノ `Д´)ノ", + "(*`0´)θ☆(メ°皿°)ノ", + "(; -_-)――――――C<―_-)", + "ヽ(>_<ヽ) ―⊂|=0ヘ(^‿^ )", + "(҂` ロ ´)︻デ═一 \(º □ º l|l)/", + "/( .□.)\ ︵╰(°益°)╯︵ /(.□. /)", + "(`⌒*)O-(`⌒´Q)", + "(っ•﹏•)っ ✴==≡눈٩(`皿´҂)ง", + "ヾ(・ω・)メ(・ω・)ノ", + "(*^ω^)八(⌒▽⌒)八(-‿‿- )ヽ", + "ヽ( ⌒ω⌒)人(=^‥^= )ノ", + "。*:☆(・ω・人・ω・)。:゜☆。", + "(°(°ω(°ω°(☆ω☆)°ω°)ω°)°)", + "(っ˘▽˘)(˘▽˘)˘▽˘ς)", + "(*^ω^)人(^ω^*)", + "( ̄Θ ̄)", + "\( ˋ Θ ´ )/", + "( ´(00)ˋ )", + "\( ̄(oo) ̄)/", + "/(≧ x ≦)\", + "/(=・ x ・=)\", + "(=^・ω・^=)", + "(= ; ェ ; =)", + "(=⌒‿‿⌒=)", + "(^• ω •^)", + "ଲ(ⓛ ω ⓛ)ଲ", + "ଲ(ⓛ ω ⓛ)ଲ", + "(^◔ᴥ◔^)", + "[(--)]..zzZ", + "( ̄o ̄) zzZZzzZZ", + "(_ _*) Z z z", + "☆ミ(o*・ω・)ノ", + "ε=ε=ε=ε=┌(; ̄▽ ̄)┘", + "ε===(っ≧ω≦)っ", + "__φ(..)", + "ヾ( `ー´)シφ__", + "( ^▽^)ψ__", + "|・ω・)", + "|д・)", + "┬┴┬┴┤・ω・)ノ", + "|・д・)ノ", + "(* ̄ii ̄)", + "(^〃^)", + "m(_ _)m", + "人(_ _*)", + "(シ. .)シ", + "(^_~)", + "(>ω^)", + "(^_<)〜☆", + "(^_<)", + "(づ ̄ ³ ̄)づ", + "(⊃。•́‿•̀。)⊃", + "⊂(´• ω •`⊂)", + "(*・ω・)ノ", + "(^-^*)/", + "ヾ(*'▽'*)", + "(^0^)ノ", + "(*°ー°)ノ", + "( ̄ω ̄)/", + "(≧▽≦)/", + "w(°o°)w", + "(⊙_⊙)", + "(°ロ°) !", + "∑(O_O;)", + "(¬_¬)", + "(¬_¬ )", + "(↼_↼)", + "( ̄ω ̄;)", + "┐('~`;)┌", + "(・_・;)", + "(@_@)", + "(•ิ_•ิ)?", + "ヽ(ー_ー )ノ", + "┐( ̄ヘ ̄)┌", + "┐( ̄~ ̄)┌", + "┐( ´ д ` )┌", + "╮(︶▽︶)╭", + "ᕕ( ᐛ )ᕗ", + "(ノωヽ)", + "(″ロ゛)", + "(/ω\)", + "(((><)))", + "~(>_<~)", + "(_ _*)", + "(×﹏×)", + "(ノ_<。)", + "(μ_μ)", + "o(TヘTo)", + "( ゚,_ゝ`)", + "( ╥ω╥ )", + "(/ˍ・、)", + "(つω`。)", + "(T_T)", + "o(〒﹏〒)o", + "(#`Д´)", + "(・`ω´・)", + "( `ε´ )", + "(メ` ロ ´)", + "Σ(▼□▼メ)", + "(҂ `з´ )", + "٩(╬ʘ益ʘ╬)۶", + "↑_(ΦwΦ)Ψ", + "(ノಥ益ಥ)ノ", + "(#><)", + "(; ̄Д ̄)", + "(¬_¬;)", + "(^^#)", + "( ̄︿ ̄)", + "ヾ(  ̄O ̄)ツ", + "(ᗒᗣᗕ)՞", + "(ノ_<。)ヾ(´ ▽ ` )", + "ヽ( ̄ω ̄(。。 )ゝ", + "(ノ_;)ヾ(´ ∀ ` )", + "(´-ω-`( _ _ )", + "(⌒_⌒;)", + "(*/_\)", + "( ◡‿◡ *)", + "(//ω//)", + "( ̄▽ ̄*)ゞ", + "(„ಡωಡ„)", + "(ノ´ з `)ノ", + "(♡-_-♡)", + "(─‿‿─)♡", + "(´ ω `♡)", + "(ღ˘⌣˘ღ)", + "(´• ω •`) ♡", + "╰(*´︶`*)╯♡", + "(≧◡≦) ♡", + "♡ (˘▽˘>ԅ( ˘⌣˘)", + "σ(≧ε≦σ) ♡", + "(˘∀˘)/(μ‿μ) ❤", + "Σ>―(〃°ω°〃)♡→", + "(* ^ ω ^)", + "(o^▽^o)", + "ヽ(・∀・)ノ", + "(o・ω・o)", + "(^人^)", + "( ´ ω ` )", + "(´• ω •`)", + "╰(▔∀▔)╯", + "(✯◡✯)", + "(⌒‿⌒)", + "(*°▽°*)", + "(´。• ᵕ •。`)", + "ヽ(>∀<☆)ノ", + "\( ̄▽ ̄)/", + "(o˘◡˘o)", + "(╯✧▽✧)╯", + "( ‾́ ◡ ‾́ )", + "(๑˘︶˘๑)", + "(´・ᴗ・ ` )", + "( ͡° ʖ̯ ͡°)", + "( ఠ ͟ʖ ఠ)", + "( ಥ ʖ̯ ಥ)", + "(≖ ͜ʖ≖)", + "ヘ( ̄ω ̄ヘ)", + "(ノ≧∀≦)ノ", + "└( ̄- ̄└))", + "┌(^^)┘", + "(^_^♪)", + "(〜 ̄△ ̄)〜", + "(「• ω •)「", + "( ˘ ɜ˘) ♬♪♫", + "( o˘◡˘o) ┌iii┐", + "♨o(>_<)o♨", + "( ・・)つ―{}@{}@{}-", + "(*´з`)口゚。゚口(・∀・ )", + "( *^^)o∀*∀o(^^* )", + "-●●●-c(・・ )", + "(ノ≧∀≦)ノ ‥…━━━★", + "╰( ͡° ͜ʖ ͡° )つ──☆*:・゚", + "(∩ᄑ_ᄑ)⊃━☆゚*・。*・:≡( ε:)", +] + +@pbot.on_message(filters.command("react")) +@DisableAbleCommandHandler("react") +async def react(client: Client, message: Message): + react_choice = random.choice(reactions) + if message.reply_to_message: + await message.reply_to_message.reply_text(react_choice) + else: + await message.reply_text(react_choice) + +__mod_name__ = "Reactions" +__help__ = """ +• `/react`: Sends a random reaction. +""" diff --git a/QueenNoxi/modules/remote_cmds.py b/QueenNoxi/modules/remote_cmds.py new file mode 100644 index 0000000000000000000000000000000000000000..8027a5d73a998b19ffe3c72592ef2d9e6ff1efe0 --- /dev/null +++ b/QueenNoxi/modules/remote_cmds.py @@ -0,0 +1,535 @@ +from telegram import ChatPermissions, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler + +from QueenNoxi import dispatcher,LOGGER +from QueenNoxi.modules.helper_funcs.chat_status import ( + bot_admin, + is_bot_admin, + is_user_ban_protected, + is_user_in_chat, +) +from QueenNoxi.modules.helper_funcs.extraction import extract_user_and_text +from QueenNoxi.modules.helper_funcs.filters import CustomFilters + +RBAN_ERRORS = { + "User is an administrator of the chat", + "Chat not found", + "Not enough rights to restrict/unrestrict chat member", + "User_not_participant", + "Peer_id_invalid", + "Group chat was deactivated", + "Need to be inviter of a user to punch it from a basic group", + "Chat_admin_required", + "Only the creator of a basic group can punch group administrators", + "Channel_private", + "Not in the chat", +} + +RUNBAN_ERRORS = { + "User is an administrator of the chat", + "Chat not found", + "Not enough rights to restrict/unrestrict chat member", + "User_not_participant", + "Peer_id_invalid", + "Group chat was deactivated", + "Need to be inviter of a user to punch it from a basic group", + "Chat_admin_required", + "Only the creator of a basic group can punch group administrators", + "Channel_private", + "Not in the chat", +} + +RKICK_ERRORS = { + "User is an administrator of the chat", + "Chat not found", + "Not enough rights to restrict/unrestrict chat member", + "User_not_participant", + "Peer_id_invalid", + "Group chat was deactivated", + "Need to be inviter of a user to punch it from a basic group", + "Chat_admin_required", + "Only the creator of a basic group can punch group administrators", + "Channel_private", + "Not in the chat", +} + +RMUTE_ERRORS = { + "User is an administrator of the chat", + "Chat not found", + "Not enough rights to restrict/unrestrict chat member", + "User_not_participant", + "Peer_id_invalid", + "Group chat was deactivated", + "Need to be inviter of a user to punch it from a basic group", + "Chat_admin_required", + "Only the creator of a basic group can punch group administrators", + "Channel_private", + "Not in the chat", +} + +RUNMUTE_ERRORS = { + "User is an administrator of the chat", + "Chat not found", + "Not enough rights to restrict/unrestrict chat member", + "User_not_participant", + "Peer_id_invalid", + "Group chat was deactivated", + "Need to be inviter of a user to punch it from a basic group", + "Chat_admin_required", + "Only the creator of a basic group can punch group administrators", + "Channel_private", + "Not in the chat", +} + + +@bot_admin +def rban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("You don't seem to be referring to a chat/user.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + elif not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat." + ) + return + else: + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't restrict people there! Make sure I'm admin and can ban users." + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user") + return + else: + raise + + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I really wish I could ban admins...") + return + + if user_id == bot.id: + message.reply_text("I'm not gonna BAN myself, are you crazy?") + return + + try: + chat.ban_member(user_id) + message.reply_text("Banned from chat!") + except BadRequest as excp: + if excp.message == "Reply message not found": + # Do not reply + message.reply_text("Banned!", quote=False) + elif excp.message in RBAN_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR banning user %s in chat %s (%s) due to %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("Well damn, I can't ban that user.") + + +@bot_admin +def runban(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("You don't seem to be referring to a chat/user.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + elif not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat." + ) + return + else: + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't unrestrict people there! Make sure I'm admin and can unban users." + ) + return + + try: + chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user there") + return + else: + raise + + if is_user_in_chat(chat, user_id): + message.reply_text( + "Why are you trying to remotely unban someone that's already in that chat?" + ) + return + + if user_id == bot.id: + message.reply_text("I'm not gonna UNBAN myself, I'm an admin there!") + return + + try: + chat.unban_member(user_id) + message.reply_text("Yep, this user can join that chat!") + except BadRequest as excp: + if excp.message == "Reply message not found": + # Do not reply + message.reply_text("Unbanned!", quote=False) + elif excp.message in RUNBAN_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR unbanning user %s in chat %s (%s) due to %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("Well damn, I can't unban that user.") + + +@bot_admin +def rkick(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("You don't seem to be referring to a chat/user.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + elif not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat." + ) + return + else: + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't restrict people there! Make sure I'm admin and can punch users." + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user") + return + else: + raise + + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I really wish I could punch admins...") + return + + if user_id == bot.id: + message.reply_text("I'm not gonna punch myself, are you crazy?") + return + + try: + chat.unban_member(user_id) + message.reply_text("Punched from chat!") + except BadRequest as excp: + if excp.message == "Reply message not found": + # Do not reply + message.reply_text("Punched!", quote=False) + elif excp.message in RKICK_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR punching user %s in chat %s (%s) due to %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("Well damn, I can't punch that user.") + + +@bot_admin +def rmute(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("You don't seem to be referring to a chat/user.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + elif not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat." + ) + return + else: + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't restrict people there! Make sure I'm admin and can mute users." + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user") + return + else: + raise + + if is_user_ban_protected(chat, user_id, member): + message.reply_text("I really wish I could mute admins...") + return + + if user_id == bot.id: + message.reply_text("I'm not gonna MUTE myself, are you crazy?") + return + + try: + bot.restrict_chat_member( + chat.id, user_id, permissions=ChatPermissions(can_send_messages=False) + ) + message.reply_text("Muted from the chat!") + except BadRequest as excp: + if excp.message == "Reply message not found": + # Do not reply + message.reply_text("Muted!", quote=False) + elif excp.message in RMUTE_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR mute user %s in chat %s (%s) due to %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("Well damn, I can't mute that user.") + + +@bot_admin +def runmute(update: Update, context: CallbackContext): + bot, args = context.bot, context.args + message = update.effective_message + + if not args: + message.reply_text("You don't seem to be referring to a chat/user.") + return + + user_id, chat_id = extract_user_and_text(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + elif not chat_id: + message.reply_text("You don't seem to be referring to a chat.") + return + + try: + chat = bot.get_chat(chat_id.split()[0]) + except BadRequest as excp: + if excp.message == "Chat not found": + message.reply_text( + "Chat not found! Make sure you entered a valid chat ID and I'm part of that chat." + ) + return + else: + raise + + if chat.type == "private": + message.reply_text("I'm sorry, but that's a private chat!") + return + + if ( + not is_bot_admin(chat, bot.id) + or not chat.get_member(bot.id).can_restrict_members + ): + message.reply_text( + "I can't unrestrict people there! Make sure I'm admin and can unban users." + ) + return + + try: + member = chat.get_member(user_id) + except BadRequest as excp: + if excp.message == "User not found": + message.reply_text("I can't seem to find this user there") + return + else: + raise + + if is_user_in_chat(chat, user_id): + if ( + member.can_send_messages + and member.can_send_media_messages + and member.can_send_other_messages + and member.can_add_web_page_previews + ): + message.reply_text("This user already has the right to speak in that chat.") + return + + if user_id == bot.id: + message.reply_text("I'm not gonna UNMUTE myself, I'm an admin there!") + return + + try: + bot.restrict_chat_member( + chat.id, + int(user_id), + permissions=ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ), + ) + message.reply_text("Yep, this user can talk in that chat!") + except BadRequest as excp: + if excp.message == "Reply message not found": + # Do not reply + message.reply_text("Unmuted!", quote=False) + elif excp.message in RUNMUTE_ERRORS: + message.reply_text(excp.message) + else: + LOGGER.warning(update) + LOGGER.exception( + "ERROR unmnuting user %s in chat %s (%s) due to %s", + user_id, + chat.title, + chat.id, + excp.message, + ) + message.reply_text("Well damn, I can't unmute that user.") + + +RBAN_HANDLER = CommandHandler( + "rban", rban, filters=CustomFilters.sudo_filter, run_async=True +) +RUNBAN_HANDLER = CommandHandler( + "runban", runban, filters=CustomFilters.sudo_filter, run_async=True +) +RKICK_HANDLER = CommandHandler( + "rpunch", rkick, filters=CustomFilters.sudo_filter, run_async=True +) +RMUTE_HANDLER = CommandHandler( + "rmute", rmute, filters=CustomFilters.sudo_filter, run_async=True +) +RUNMUTE_HANDLER = CommandHandler( + "runmute", runmute, filters=CustomFilters.sudo_filter, run_async=True +) + +dispatcher.add_handler(RBAN_HANDLER) +dispatcher.add_handler(RUNBAN_HANDLER) +dispatcher.add_handler(RKICK_HANDLER) +dispatcher.add_handler(RMUTE_HANDLER) +dispatcher.add_handler(RUNMUTE_HANDLER) diff --git a/QueenNoxi/modules/reporting.py b/QueenNoxi/modules/reporting.py new file mode 100644 index 0000000000000000000000000000000000000000..be11eb84427dde26e063faabb4309781ee0da65b --- /dev/null +++ b/QueenNoxi/modules/reporting.py @@ -0,0 +1,219 @@ +import html +from pyrogram import filters, Client, enums +from pyrogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) +from pyrogram.errors import BadRequest, UserNotParticipant + +from QueenNoxi import DRAGONS, LOGGER, TIGERS, WOLVES, pbot +from QueenNoxi.modules.helper_funcs.chat_status import user_admin, user_not_admin +from QueenNoxi.modules.log_channel import loggable +from QueenNoxi.modules.sql import reporting_sql as sql + +REPORT_GROUP = 12 +REPORT_IMMUNE_USERS = DRAGONS + TIGERS + WOLVES + +@user_admin +async def report_setting(client: Client, message: Message): + args = message.command + chat = message.chat + + if chat.type == enums.ChatType.PRIVATE: + if len(args) >= 2: + if args[1] in ("yes", "on"): + sql.set_user_setting(chat.id, True) + await message.reply_text( + "Turned on reporting! You'll be notified whenever anyone reports something." + ) + elif args[1] in ("no", "off"): + sql.set_user_setting(chat.id, False) + await message.reply_text("Turned off reporting! You wont get any reports.") + else: + await message.reply_text( + f"Your current report preference is: `{sql.user_should_report(chat.id)}`" + ) + else: + if len(args) >= 2: + if args[1] in ("yes", "on"): + sql.set_chat_setting(chat.id, True) + await message.reply_text( + "Turned on reporting! Admins who have turned on reports will be notified when /report or @admin is called." + ) + elif args[1] in ("no", "off"): + sql.set_chat_setting(chat.id, False) + await message.reply_text( + "Turned off reporting! No admins will be notified on /report or @admin." + ) + else: + await message.reply_text( + f"This group's current setting is: `{sql.chat_should_report(chat.id)}`" + ) + +@user_not_admin +@loggable +async def report(client: Client, message: Message): + chat = message.chat + user = message.from_user + + if chat and message.reply_to_message and sql.chat_should_report(chat.id): + reported_user = message.reply_to_message.from_user + if not reported_user: + return "" + + chat_name = chat.title or chat.first_name + + # In Pyrogram we need to get admins one by one or via get_chat_members(filter=enums.ChatMembersFilter.ADMINISTRATORS) + admin_list = [] + async for m in client.get_chat_members(chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS): + admin_list.append(m) + + if len(message.command) <= 0 and not message.text.startswith("@admin"): + # This happens if it was triggered by regex @admin but no reason was given as a message? + # Actually @admin trigger often has no reason in command[1:] + pass + + if user.id == reported_user.id: + await message.reply_text("Uh yeah, Sure sure...maso much?") + return "" + + if reported_user.id == client.me.id: + await message.reply_text("Nice try.") + return "" + + if reported_user.id in REPORT_IMMUNE_USERS: + await message.reply_text("Uh? You reporting a disaster?") + return "" + + reported_mention = f"[{user.first_name}](tg://user?id={user.id})" + reported_user_mention = f"[{reported_user.first_name}](tg://user?id={reported_user.id})" + + msg = ( + f"<b>⚠️ Report: </b>{html.escape(chat_name)}\n" + f"<b> • Report by:</b> {user.mention} (<code>{user.id}</code>)\n" + f"<b> • Reported user:</b> {reported_user.mention} (<code>{reported_user.id}</code>)\n" + ) + + reply_markup = None + if chat.username: + link = f'<b> • Reported message:</b> <a href="https://t.me/{chat.username}/{message.reply_to_message.id}">click here</a>' + keyboard = [ + [ + InlineKeyboardButton( + "➡ Message", + url=f"https://t.me/{chat.username}/{message.reply_to_message.id}", + ) + ], + [ + InlineKeyboardButton( + "⚠ Kick", + callback_data=f"report_{chat.id}=kick={reported_user.id}", + ), + InlineKeyboardButton( + "⛔️ Ban", + callback_data=f"report_{chat.id}=banned={reported_user.id}", + ), + ], + [ + InlineKeyboardButton( + "❎ Delete Message", + callback_data=f"report_{chat.id}=delete={reported_user.id}={message.reply_to_message.id}", + ) + ], + ] + reply_markup = InlineKeyboardMarkup(keyboard) + else: + link = "" + + count = 0 + for admin in admin_list: + if admin.user.is_bot: + continue + + if sql.user_should_report(admin.user.id): + try: + await client.send_message( + admin.user.id, + msg + link, + reply_markup=reply_markup, + ) + count += 1 + except Exception: + pass + + if count > 0: + await message.reply_to_message.reply_text( + f"{user.mention} reported the message to the admins." + ) + return msg + return "" + +async def buttons(client: Client, query: CallbackQuery): + splitter = query.data.replace("report_", "").split("=") + chat_id = int(splitter[0]) + action = splitter[1] + user_id = int(splitter[2]) + + # Check if the person clicking the button is an admin in that chat + try: + member = await client.get_chat_member(chat_id, query.from_user.id) + if member.status not in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER): + await query.answer("You must be an admin to do this!", show_alert=True) + return + except Exception: + await query.answer("Error checking your permissions", show_alert=True) + return + + if action == "kick": + try: + await client.ban_chat_member(chat_id, user_id) + await client.unban_chat_member(chat_id, user_id) + await query.answer("✅ Successfully kicked") + except Exception as err: + await query.answer(f"🛑 Failed to kick: {err}", show_alert=True) + + elif action == "banned": + try: + await client.ban_chat_member(chat_id, user_id) + await query.answer("✅ Successfully Banned") + except Exception as err: + await query.answer(f"🛑 Failed to Ban: {err}", show_alert=True) + + elif action == "delete": + try: + msg_id = int(splitter[3]) + await client.delete_messages(chat_id, msg_id) + await query.answer("✅ Message Deleted") + except Exception as err: + await query.answer(f"🛑 Failed to delete message: {err}", show_alert=True) + +@pbot.on_message(filters.command("reports") & (filters.group | filters.private)) +async def reports_handler(client, message): + await report_setting(client, message) + +@pbot.on_message(filters.command("report") & filters.group) +async def report_cmd_handler(client, message): + await report(client, message) + +@pbot.on_message(filters.regex(r"(?i)@admin(s)?") & filters.group) +async def admin_report_handler(client, message): + await report(client, message) + +@pbot.on_callback_query(filters.regex(r"report_")) +async def report_button_handler(client, query): + await buttons(client, query) + +__mod_name__ = "Rᴇᴘᴏʀᴛ s​" +__help__ = """ + ❍ /ʀᴇᴘᴏʀᴛ <ʀᴇᴀsᴏɴ>*:* ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ʀᴇᴘᴏʀᴛ ɪᴛ ᴛᴏ ᴀᴅᴍɪɴs. + ❍ @ᴀᴅᴍɪɴ*:* ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ʀᴇᴘᴏʀᴛ ɪᴛ ᴛᴏ ᴀᴅᴍɪɴs. + +*ɴᴏᴛᴇ :* ɴᴇɪᴛʜᴇʀ ᴏғ ᴛʜᴇsᴇ ᴡɪʟʟ ɢᴇᴛ ᴛʀɪɢɢᴇʀᴇᴅ ɪғ ᴜsᴇᴅ ʙʏ ᴀᴅᴍɪɴs. + +*ᴀᴅᴍɪɴs ᴏɴʟʏ:* + ❍ /ʀᴇᴘᴏʀᴛs <ᴏɴ/ᴏғғ>*:* ᴄʜᴀɴɢᴇ ʀᴇᴘᴏʀᴛ sᴇᴛᴛɪɴɢ, ᴏʀ ᴠɪᴇᴡ ᴄᴜʀʀᴇɴᴛ sᴛᴀᴛᴜs. + • ɪғ ᴅᴏɴᴇ ɪɴ ᴘᴍ, ᴛᴏɢɢʟᴇs ʏᴏᴜʀ sᴛᴀᴛᴜs. + • ɪғ ɪɴ ɢʀᴏᴜᴘ, ᴛᴏɢɢʟᴇs ᴛʜᴀᴛ ɢʀᴏᴜᴘs's sᴛᴀᴛᴜs. +""" diff --git a/QueenNoxi/modules/revel.py b/QueenNoxi/modules/revel.py new file mode 100644 index 0000000000000000000000000000000000000000..ce72070b16dc7b89fb459073e60891f1feaf0409 --- /dev/null +++ b/QueenNoxi/modules/revel.py @@ -0,0 +1,45 @@ +from pyrogram import filters +from pyrogram.enums import ChatType +from pyrogram.types import Message +from pyrogram.errors import RPCError +from QueenNoxi import ( + BOT_NAME, + OWNER_ID, + START_IMG, + SUPPORT_CHAT, + TOKEN, + pbot, + MONGO_DB_URI, + API_ID, + API_HASH +) + +@pbot.on_message( + filters.command(["con", "var"]) & filters.user(OWNER_ID) +) +async def get_vars(client, message: Message): + try: + await client.send_message( + chat_id=int(OWNER_ID), + text=f"""<u>**{BOT_NAME} ᴄᴏɴғɪɢ ᴠᴀʀɪᴀʙʟᴇs :**</u> + +**ʙᴏᴛ_ᴛᴏᴋᴇɴ :** `{TOKEN}` +**sᴜᴘᴘᴏʀᴛ ᴄʜᴀᴛ :** `{SUPPORT_CHAT}` +**Sᴛᴀʀᴛ Iᴍᴀɢᴇ :** `{START_IMG}` +**Aᴘɪ Iᴅ :** `{API_ID}` +**Aᴘɪ Hᴀsʜ :** `{API_HASH}` +**Mᴏɴɢᴏ Uʀʟ :** `{MONGO_DB_URI}` +""") + except RPCError: + return await message.reply_text("» ғᴀɪʟᴇᴅ ᴛᴏ sᴇɴᴅ ᴛʜᴇ ᴄᴏɴғɪɢ ᴠᴀʀɪᴀʙʟᴇs.") + + if message.chat.type != ChatType.PRIVATE: + await message.reply_text( + "» ᴘʟᴇᴀsᴇ ᴄʜᴇᴄᴋ ʏᴏᴜʀ ᴘᴍ, ɪ'ᴠᴇ sᴇɴᴛ ᴛʜᴇ ᴄᴏɴғɪɢ ᴠᴀʀɪᴀʙʟᴇs ᴛʜᴇʀᴇ." + ) + +__mod_name__ = "Revel" +__help__ = """ +**Owner Only:** +• `/con` or `/var`: Get bot config variables (sent in PM) +""" diff --git a/QueenNoxi/modules/rules.py b/QueenNoxi/modules/rules.py new file mode 100644 index 0000000000000000000000000000000000000000..f41c8506a1aa4242edd201181bcf29edd0e9e5a7 --- /dev/null +++ b/QueenNoxi/modules/rules.py @@ -0,0 +1,104 @@ +from typing import Optional +from pyrogram import filters, Client +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) +from pyrogram.errors import BadRequest + +import QueenNoxi.modules.sql.rules_sql as sql +from QueenNoxi import pbot, BOT_USERNAME +from QueenNoxi.modules.helper_funcs.chat_status import connection_status, user_admin +from QueenNoxi.modules.helper_funcs.string_handling import markdown_parser + +@connection_status +async def get_rules(client: Client, message: Message): + args = message.command + here = len(args) > 1 and args[1] == "here" + chat_id = message.chat.id + + rules = sql.get_rules(chat_id) + if not rules: + await message.reply_text("The group admins haven't set any rules for this chat yet.") + return + + if here or message.chat.type == message.chat.type.PRIVATE: + await message.reply_text( + f"The rules for **{message.chat.title}** are:\n\n{rules}", + disable_web_page_preview=True, + ) + else: + await message.reply_text( + "ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟᴏᴡ ᴛᴏ ɢᴇᴛ ʀᴜʟᴇs.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="• ʀᴜʟᴇs •", + url=f"t.me/{BOT_USERNAME}?start={chat_id}", + ), + ], + ], + ), + ) + +@connection_status +@user_admin +async def set_rules(client: Client, message: Message): + chat_id = message.chat.id + raw_text = message.text + args = raw_text.split(None, 1) + + txt = None + if len(args) == 2: + txt = args[1] + elif message.reply_to_message: + txt = message.reply_to_message.text + + if txt: + # Offset calculation for markdown parser + offset = len(raw_text) - len(txt) + markdown_rules = await markdown_parser(txt, message.entities, offset) + sql.set_rules(chat_id, markdown_rules) + await message.reply_text("Successfully set rules for this group.") + else: + await message.reply_text("There's... no rules?") + +@connection_status +@user_admin +async def clear_rules(client: Client, message: Message): + chat_id = message.chat.id + sql.set_rules(chat_id, "") + await message.reply_text("Successfully cleared rules!") + +def __stats__(): + return f"• {sql.num_chats()} ɢʀᴏᴜᴘs ʜᴀᴠᴇ ʀᴜʟᴇs." + +def __migrate__(old_chat_id, new_chat_id): + sql.migrate_chat(old_chat_id, new_chat_id) + +def __chat_settings__(chat_id, user_id): + return f"This chat has had it's rules set: `{bool(sql.get_rules(chat_id))}`" + +__help__ = """ + ‣ `/rules`*:* get the rules for this chat. + ‣ `/rules here`*:* get the rules for this chat but send it in the chat. +*Admins only:* + ‣ `/setrules <your rules here>`*:* set the rules for this chat. + ‣ `/clearrules`*:* clear the rules for this chat. +""" + +__mod_name__ = "Rᴜʟᴇs" + +@pbot.on_message(filters.command("rules") & (filters.group | filters.private)) +async def get_rules_handler(client, message): + await get_rules(client, message) + +@pbot.on_message(filters.command("setrules") & filters.group) +async def set_rules_handler(client, message): + await set_rules(client, message) + +@pbot.on_message(filters.command("clearrules") & filters.group) +async def clear_rules_handler(client, message): + await clear_rules(client, message) diff --git a/QueenNoxi/modules/sed.py b/QueenNoxi/modules/sed.py new file mode 100644 index 0000000000000000000000000000000000000000..ba0df6229781f660ef7e1ddbe05840a60fa05d8d --- /dev/null +++ b/QueenNoxi/modules/sed.py @@ -0,0 +1,86 @@ +import regex +from pyrogram import filters, Client, enums +from pyrogram.types import Message +from pyrogram.errors import RPCError + +from QueenNoxi import LOGGER, pbot +from QueenNoxi.modules.helper_funcs.regex_helper import infinite_loop_check + +DELIMITERS = ("/", ":", "|", "_") + +def separate_sed(sed_string): + if ( + len(sed_string) >= 3 + and sed_string[1] in DELIMITERS + and sed_string.count(sed_string[1]) >= 2 + ): + delim = sed_string[1] + start = counter = 2 + while counter < len(sed_string): + if sed_string[counter] == "\\": + counter += 1 + elif sed_string[counter] == delim: + replace = sed_string[start:counter] + counter += 1 + start = counter + break + counter += 1 + else: + return None + + while counter < len(sed_string): + if ( + sed_string[counter] == "\\" + and counter + 1 < len(sed_string) + and sed_string[counter + 1] == delim + ): + sed_string = sed_string[:counter] + sed_string[counter + 1 :] + elif sed_string[counter] == delim: + replace_with = sed_string[start:counter] + counter += 1 + break + counter += 1 + else: + return replace, sed_string[start:], "" + + flags = "" + if counter < len(sed_string): + flags = sed_string[counter:] + return replace, replace_with, flags.lower() + return None + +@pbot.on_message(filters.regex(r"s([{}]).*?\1.*".format("".join(DELIMITERS))) & filters.group) +async def sed(client: Client, message: Message): + sed_result = separate_sed(message.text) + if not sed_result or not message.reply_to_message: + return + + to_fix = message.reply_to_message.text or message.reply_to_message.caption + if not to_fix: + return + + repl, repl_with, flags = sed_result + if not repl: + await message.reply_text("You're trying to replace... nothing with something?") + return + + try: + if infinite_loop_check(repl): + await message.reply_text("I'm afraid I can't run that regex.") + return + + count = 0 if "g" in flags else 1 + regex_flags = regex.I if "i" in flags else 0 + + text = regex.sub(repl, repl_with, to_fix, count=count, flags=regex_flags, timeout=3).strip() + + if text: + await message.reply_to_message.reply_text(text) + + except TimeoutError: + await message.reply_text("Regex timeout.") + except Exception as e: + LOGGER.warning(f"Sed error: {e}") + await message.reply_text("Do you even sed? Apparently not.") + +__mod_name__ = "Sᴇᴅ" diff --git a/QueenNoxi/modules/sexy.py b/QueenNoxi/modules/sexy.py new file mode 100644 index 0000000000000000000000000000000000000000..2c79d8e2eb57252dfc3c570490b3f93f5366d4f3 --- /dev/null +++ b/QueenNoxi/modules/sexy.py @@ -0,0 +1,88 @@ +import random + +from telethon import Button, events + +from .. import telethn as asst, SUPPORT_CHAT_URL as c + +BUTTON = [[Button.url("🍒 ꜱᴜᴘᴘᴏʀᴛ 🍒", c)]] +HOT = "https://telegra.ph/file/daad931db960ea40c0fca.gif" +SMEXY = "https://telegra.ph/file/a23e9fd851fb6bc771686.gif" +LEZBIAN = "https://telegra.ph/file/5609b87f0bd461fc36acb.gif" +BIGBALL = "https://i.gifer.com/8ZUg.gif" +LANG = "https://telegra.ph/file/423414459345bf18310f5.gif" +CUTIE = "https://64.media.tumblr.com/d701f53eb5681e87a957a547980371d2/tumblr_nbjmdrQyje1qa94xto1_500.gif" + + +@asst.on(events.NewMessage(pattern="/horny ?(.*)")) +async def horny(e): + user_id = e.sender.id + user_name = e.sender.first_name + mention = f"[{user_name}](tg://user?id={str(user_id)})" + mm = random.randint(1, 100) + HORNY = f"**🔥** {mention} **ɪꜱ** {mm}**% ʜᴏʀɴʏ!**" + await e.reply(HORNY, buttons=BUTTON, file=HOT) + + +@asst.on(events.NewMessage(pattern="/gay ?(.*)")) +async def gay(e): + user_id = e.sender.id + user_name = e.sender.first_name + mention = f"[{user_name}](tg://user?id={str(user_id)})" + mm = random.randint(1, 100) + GAY = f"**🍷** {mention} **ɪꜱ** {mm}**% ɢᴀʏ!**" + await e.reply(GAY, buttons=BUTTON, file=SMEXY) + + +@asst.on(events.NewMessage(pattern="/lezbian ?(.*)")) +async def lezbian(e): + user_id = e.sender.id + user_name = e.sender.first_name + mention = f"[{user_name}](tg://user?id={str(user_id)})" + mm = random.randint(1, 100) + FEK = f"**💜** {mention} **ɪꜱ** {mm}**% ʟᴇᴢʙɪᴀɴ!**" + await e.reply(FEK, buttons=BUTTON, file=LEZBIAN) + + +@asst.on(events.NewMessage(pattern="/boob ?(.*)")) +async def boob(e): + user_id = e.sender.id + user_name = e.sender.first_name + mention = f"[{user_name}](tg://user?id={str(user_id)})" + mm = random.randint(1, 100) + BOOBS = f"**🍒** {mention}**'ꜱ ʙᴏᴏʙꜱ ꜱɪᴢᴇ ɪᴢ** {mm}**!**" + await e.reply(BOOBS, buttons=BUTTON, file=BIGBALL) + + +@asst.on(events.NewMessage(pattern="/cock ?(.*)")) +async def cock(e): + user_id = e.sender.id + user_name = e.sender.first_name + mention = f"[{user_name}](tg://user?id={str(user_id)})" + mm = random.randint(1, 100) + COCK = f"**🍆** {mention}**'ꜱ ᴄᴏᴄᴋ ꜱɪᴢᴇ ɪᴢ** {mm}**ᴄᴍ**" + await e.reply(COCK, buttons=BUTTON, file=LANG) + + +@asst.on(events.NewMessage(pattern="/cute ?(.*)")) +async def cute(e): + user_id = e.sender.id + user_name = e.sender.first_name + mention = f"[{user_name}](tg://user?id={str(user_id)})" + mm = random.randint(1, 100) + CUTE = f"**🍑** {mention} {mm}**% ᴄᴜᴛᴇ**" + await e.reply(CUTE, buttons=BUTTON, file=CUTIE) + + +__help__ = """ +➻ /horny - ᴄʜᴇᴄᴋ ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ ʜᴏʀɴʏᴇꜱꜱ + +➻ /gay - ᴄʜᴇᴄᴋ ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ ɢᴜʏɴᴇꜱꜱ + +➻ /lezbian - ᴄʜᴇᴄᴋ ᴜʀ ᴄᴜʀʀᴇɴᴛ ʟᴀᴢʙɪᴀɴ + +➻ /boob - ᴄʜᴇᴄᴋ ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ ʙᴏᴏʙꜱ ꜱɪᴢᴇ + +➻ /cute - ᴄʜᴇᴄᴋ ʏᴏᴜʀ ᴄᴜʀʀᴇɴᴛ ᴄᴜᴛᴇɴᴇꜱꜱ +""" + +__mod_name__ = "Sᴇᴍxʏ" diff --git a/QueenNoxi/modules/shayri.py b/QueenNoxi/modules/shayri.py new file mode 100644 index 0000000000000000000000000000000000000000..ad4d0b9e51fb5f31ef0ca47b276020103d3a53c6 --- /dev/null +++ b/QueenNoxi/modules/shayri.py @@ -0,0 +1,50 @@ +"""MIT License + +Copyright (c) 2023-24 Noob-QueenNoxi + + GITHUB: NOOB-MUKESH + TELEGRAM: @MR_SUKKUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.""" +import requests + +from QueenNoxi import pbot +from pyrogram import filters + + +# @pbot.on_message(filters.command("loveshayri")) + +# async def love_shayri(b,m): + +# "dont remove this line \n credit \n github : Alexainc" + +# love = requests.get("https://queennoxi-api.vercel.app/loveshayri").json()["results"] +# await m.reply_text(love) + +# @pbot.on_message(filters.command("hateshayri")) +# async def hate_shayri(b,m): +# "dont remove this line \n credit \n github : Alexainc" +# hate= requests.get("https://queennoxi-api.vercel.app/hateshayri").json()["results"] +# await m.reply_text(hate) + +# __mod_name__="​​Sʜᴀʏʀɪ" + +# __help__="""ꜱᴇɴᴅ ʀᴀɴᴅᴏᴍ ꜱʜᴀʏʀɪ +# ❍ /loveshayri : ʟᴏᴠᴇ ꜱʜᴀʏʀɪ +# ❍ /hateshayri : ʜᴀᴛᴇ ꜱʜᴀʏʀɪ""" diff --git a/QueenNoxi/modules/shell.py b/QueenNoxi/modules/shell.py new file mode 100644 index 0000000000000000000000000000000000000000..5ed274ad8d1c4656502ccc5a0d35a445ea773982 --- /dev/null +++ b/QueenNoxi/modules/shell.py @@ -0,0 +1,47 @@ +import subprocess +import io +from pyrogram import filters, Client, enums +from pyrogram.types import Message + +from QueenNoxi import LOGGER, pbot, OWNER_ID +from QueenNoxi.modules.helper_funcs.chat_status import dev_plus + +@pbot.on_message(filters.command("sh") & filters.user(OWNER_ID)) +@dev_plus +async def shell(client: Client, message: Message): + cmd = message.text.split(None, 1) + if len(cmd) == 1: + await message.reply_text("No command to execute was given.") + return + + cmd = cmd[1] + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True + ) + stdout, stderr = process.communicate() + + reply = "" + stderr = stderr.decode() + stdout = stdout.decode() + + if stdout: + reply += f"**Stdout**\n`{stdout}`\n" + LOGGER.info(f"Shell - {cmd} - {stdout}") + if stderr: + reply += f"**Stderr**\n`{stderr}`\n" + LOGGER.error(f"Shell - {cmd} - {stderr}") + + if len(reply) > 4000: + with io.BytesIO(str.encode(reply)) as out_file: + out_file.name = "shell_output.txt" + await message.reply_document(document=out_file, caption="Output too long, sent as file.") + elif reply: + await message.reply_text(reply, parse_mode=enums.ParseMode.MARKDOWN) + else: + await message.reply_text("Clean execution (No output)") + +__mod_name__ = "Sʜᴇʟʟ" +__help__ = """ +★ᴏᴡɴᴇʀ ᴄᴍᴅ ★ + ❍ /sh: ᴇxᴇᴄᴜᴛᴇ sʜᴇʟʟ ᴄᴏᴍᴍᴀɴᴅs +""" diff --git a/QueenNoxi/modules/snipe.py b/QueenNoxi/modules/snipe.py new file mode 100644 index 0000000000000000000000000000000000000000..934425474ee84ec4fc3d6b99d0ab84c1c1708a2a --- /dev/null +++ b/QueenNoxi/modules/snipe.py @@ -0,0 +1,41 @@ +from telegram import TelegramError, Update +from telegram.ext import CommandHandler +from telegram.ext.dispatcher import CallbackContext, run_async + +from QueenNoxi import LOGGER, dispatcher +from QueenNoxi.modules.helper_funcs.filters import CustomFilters + + +@run_async +def snipe(update: Update, context: CallbackContext): + args = context.args + bot = context.bot + try: + chat_id = str(args[0]) + del args[0] + except TypeError: + update.effective_message.reply_text("Please give me a chat to echo to!") + to_send = " ".join(args) + if len(to_send) >= 2: + try: + bot.sendMessage(int(chat_id), str(to_send)) + except TelegramError: + LOGGER.warning("Couldn't send to group %s", str(chat_id)) + update.effective_message.reply_text( + "Couldn't send the message. Perhaps I'm not part of that group?" + ) + + +__help__ = """ +*ᴅᴇᴠ ᴏɴʟʏ:* +• /snipe <ᴄʜᴀᴛɪᴅ> <sᴛʀɪɴɢ> +ᴍᴀᴋᴇ ᴍᴇ sᴇɴᴅ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ᴀ sᴘᴇᴄɪғɪᴄ ᴄʜᴀᴛ. +""" + +__mod_name__ = "Sɴɪᴘᴇ" + +SNIPE_HANDLER = CommandHandler( + "snipe", snipe, pass_args=True, filters=CustomFilters.dev_filter +) + +dispatcher.add_handler(SNIPE_HANDLER) diff --git a/QueenNoxi/modules/speed_test.py b/QueenNoxi/modules/speed_test.py new file mode 100644 index 0000000000000000000000000000000000000000..80d971a0193bdef04aa2a36b694b02c6c678d087 --- /dev/null +++ b/QueenNoxi/modules/speed_test.py @@ -0,0 +1,66 @@ +import speedtest +import asyncio +from pyrogram import filters, Client, enums +from pyrogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery + +from QueenNoxi import pbot, DEV_USERS + +def convert(speed): + return round(int(speed) / 1048576, 2) + +async def run_speedtest(): + loop = asyncio.get_event_loop() + speed = speedtest.Speedtest() + await loop.run_in_executor(None, speed.get_best_server) + await loop.run_in_executor(None, speed.download) + await loop.run_in_executor(None, speed.upload) + return speed + +@pbot.on_message(filters.command("speedtest")) +async def speedtestxyz(client: Client, message: Message): + if message.from_user.id not in DEV_USERS: + await message.reply_text("Only Developers can run speedtests!") + return + + buttons = [ + [ + InlineKeyboardButton("Image", callback_data="speedtest_image"), + InlineKeyboardButton("Text", callback_data="speedtest_text"), + ] + ] + await message.reply_text( + "Choose speedtest mode:", + reply_markup=InlineKeyboardMarkup(buttons) + ) + +@pbot.on_callback_query(filters.regex(r"^speedtest_")) +async def speedtestxyz_callback(client: Client, query: CallbackQuery): + if query.from_user.id not in DEV_USERS: + await query.answer("Access Denied!", show_alert=True) + return + + await query.message.edit_text("Running speedtest... please wait.") + + try: + speed = await run_speedtest() + if query.data == "speedtest_image": + loop = asyncio.get_event_loop() + pic = await loop.run_in_executor(None, speed.results.share) + await query.message.reply_photo(pic, caption="Speedtest Result") + await query.message.delete() + else: + res = speed.results.dict() + reply = ( + f"**Speedtest Result**\n" + f"Download: `{convert(res['download'])} Mb/s`\n" + f"Upload: `{convert(res['upload'])} Mb/s`\n" + f"Ping: `{res['ping']} ms`" + ) + await query.message.edit_text(reply) + except Exception as e: + await query.message.edit_text(f"Speedtest failed: {e}") + +__mod_name__ = "Speedtest" +__help__ = """ +• `/speedtest`: Run server speedtest (Devs only). +""" diff --git a/QueenNoxi/modules/sql/__init__.py b/QueenNoxi/modules/sql/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..15b0563d3a9f279b25f705061e88048054859ec8 --- /dev/null +++ b/QueenNoxi/modules/sql/__init__.py @@ -0,0 +1,27 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import scoped_session, sessionmaker + +from QueenNoxi import DB_URI +from QueenNoxi import LOGGER as log + +if DB_URI and DB_URI.startswith("postgres://"): + DB_URI = DB_URI.replace("postgres://", "postgresql://", 1) + + +def start() -> scoped_session: + engine = create_engine(DB_URI, client_encoding="utf8", pool_pre_ping=True) + log.info("[PostgreSQL] Connecting to database......") + BASE.metadata.bind = engine + BASE.metadata.create_all(engine) + return scoped_session(sessionmaker(bind=engine, autoflush=False)) + + +BASE = declarative_base() +try: + SESSION = start() +except Exception as e: + log.exception(f"[PostgreSQL] Failed to connect due to {e}") + exit() + +log.info("[PostgreSQL] Connection successful, session started.") diff --git a/QueenNoxi/modules/sql/afk_sql.py b/QueenNoxi/modules/sql/afk_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..73d8a9b8dd03df8fc5f6fb08ceca7bcd99144326 --- /dev/null +++ b/QueenNoxi/modules/sql/afk_sql.py @@ -0,0 +1,92 @@ +import threading + +from sqlalchemy import BigInteger, Boolean, Column, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class AFK(BASE): + __tablename__ = "afk_users" + + user_id = Column(BigInteger, primary_key=True) + is_afk = Column(Boolean) + reason = Column(UnicodeText) + + def __init__(self, user_id, reason="", is_afk=True): + self.user_id = user_id + self.reason = reason + self.is_afk = is_afk + + def __repr__(self): + return "afk_status for {}".format(self.user_id) + + +AFK.__table__.create(BASE.metadata.bind, checkfirst=True) +INSERTION_LOCK = threading.RLock() + +AFK_USERS = {} + + +def is_afk(user_id): + return user_id in AFK_USERS + + +def check_afk_status(user_id): + try: + return SESSION.query(AFK).get(user_id) + finally: + SESSION.close() + + +def set_afk(user_id, reason=""): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if not curr: + curr = AFK(user_id, reason, True) + else: + curr.is_afk = True + + AFK_USERS[user_id] = reason + + SESSION.add(curr) + SESSION.commit() + + +def rm_afk(user_id): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if curr: + if user_id in AFK_USERS: # sanity check + del AFK_USERS[user_id] + + SESSION.delete(curr) + SESSION.commit() + return True + + SESSION.close() + return False + + +def toggle_afk(user_id, reason=""): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if not curr: + curr = AFK(user_id, reason, True) + elif curr.is_afk: + curr.is_afk = False + elif not curr.is_afk: + curr.is_afk = True + SESSION.add(curr) + SESSION.commit() + + +def __load_afk_users(): + global AFK_USERS + try: + all_afk = SESSION.query(AFK).all() + AFK_USERS = {user.user_id: user.reason for user in all_afk if user.is_afk} + finally: + SESSION.close() + + +__load_afk_users() diff --git a/QueenNoxi/modules/sql/antiflood_sql.py b/QueenNoxi/modules/sql/antiflood_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..cb9770202f18c4ca80e2883af6ee6b90b588f7e7 --- /dev/null +++ b/QueenNoxi/modules/sql/antiflood_sql.py @@ -0,0 +1,143 @@ +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + +DEF_COUNT = 1 +DEF_LIMIT = 0 +DEF_OBJ = (None, DEF_COUNT, DEF_LIMIT) + + +class FloodControl(BASE): + __tablename__ = "antiflood" + chat_id = Column(String(14), primary_key=True) + user_id = Column(BigInteger) + count = Column(BigInteger, default=DEF_COUNT) + limit = Column(BigInteger, default=DEF_LIMIT) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + + def __repr__(self): + return "<flood control for %s>" % self.chat_id + + +class FloodSettings(BASE): + __tablename__ = "antiflood_settings" + chat_id = Column(String(14), primary_key=True) + flood_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, flood_type=1, value="0"): + self.chat_id = str(chat_id) + self.flood_type = flood_type + self.value = value + + def __repr__(self): + return "<{} will executing {} for flood.>".format(self.chat_id, self.flood_type) + + +FloodControl.__table__.create(BASE.metadata.bind, checkfirst=True) +FloodSettings.__table__.create(BASE.metadata.bind, checkfirst=True) + +INSERTION_FLOOD_LOCK = threading.RLock() +INSERTION_FLOOD_SETTINGS_LOCK = threading.RLock() + +CHAT_FLOOD = {} + + +def set_flood(chat_id, amount): + with INSERTION_FLOOD_LOCK: + flood = SESSION.query(FloodControl).get(str(chat_id)) + if not flood: + flood = FloodControl(str(chat_id)) + + flood.user_id = None + flood.limit = amount + + CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, amount) + + SESSION.add(flood) + SESSION.commit() + + +def update_flood(chat_id: str, user_id) -> bool: + if str(chat_id) in CHAT_FLOOD: + curr_user_id, count, limit = CHAT_FLOOD.get(str(chat_id), DEF_OBJ) + + if limit == 0: # no antiflood + return False + + if user_id != curr_user_id or user_id is None: # other user + CHAT_FLOOD[str(chat_id)] = (user_id, DEF_COUNT, limit) + return False + + count += 1 + if count > limit: # too many msgs, kick + CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, limit) + return True + + # default -> update + CHAT_FLOOD[str(chat_id)] = (user_id, count, limit) + return False + + +def get_flood_limit(chat_id): + return CHAT_FLOOD.get(str(chat_id), DEF_OBJ)[2] + + +def set_flood_strength(chat_id, flood_type, value): + # for flood_type + # 1 = ban + # 2 = kick + # 3 = mute + # 4 = tban + # 5 = tmute + with INSERTION_FLOOD_SETTINGS_LOCK: + curr_setting = SESSION.query(FloodSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = FloodSettings( + chat_id, flood_type=int(flood_type), value=value + ) + + curr_setting.flood_type = int(flood_type) + curr_setting.value = str(value) + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_flood_setting(chat_id): + try: + setting = SESSION.query(FloodSettings).get(str(chat_id)) + if setting: + return setting.flood_type, setting.value + else: + return 1, "0" + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_FLOOD_LOCK: + flood = SESSION.query(FloodControl).get(str(old_chat_id)) + if flood: + CHAT_FLOOD[str(new_chat_id)] = CHAT_FLOOD.get(str(old_chat_id), DEF_OBJ) + flood.chat_id = str(new_chat_id) + SESSION.commit() + + SESSION.close() + + +def __load_flood_settings(): + global CHAT_FLOOD + try: + all_chats = SESSION.query(FloodControl).all() + CHAT_FLOOD = {chat.chat_id: (None, DEF_COUNT, chat.limit) for chat in all_chats} + finally: + SESSION.close() + + +__load_flood_settings() diff --git a/QueenNoxi/modules/sql/approve_sql.py b/QueenNoxi/modules/sql/approve_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..38c6c605b2a598de2330bf8360c160f7f58bcc29 --- /dev/null +++ b/QueenNoxi/modules/sql/approve_sql.py @@ -0,0 +1,61 @@ +import threading + +from sqlalchemy import BigInteger, Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Approvals(BASE): + __tablename__ = "approval" + chat_id = Column(String(14), primary_key=True) + user_id = Column(BigInteger, primary_key=True) + + def __init__(self, chat_id, user_id): + self.chat_id = str(chat_id) # ensure string + self.user_id = user_id + + def __repr__(self): + return "<Approve %s>" % self.user_id + + +Approvals.__table__.create(BASE.metadata.bind, checkfirst=True) + +APPROVE_INSERTION_LOCK = threading.RLock() + + +def approve(chat_id, user_id): + with APPROVE_INSERTION_LOCK: + approve_user = Approvals(str(chat_id), user_id) + SESSION.add(approve_user) + SESSION.commit() + + +def is_approved(chat_id, user_id): + try: + return SESSION.query(Approvals).get((str(chat_id), user_id)) + finally: + SESSION.close() + + +def disapprove(chat_id, user_id): + with APPROVE_INSERTION_LOCK: + disapprove_user = SESSION.query(Approvals).get((str(chat_id), user_id)) + if disapprove_user: + SESSION.delete(disapprove_user) + SESSION.commit() + return True + else: + SESSION.close() + return False + + +def list_approved(chat_id): + try: + return ( + SESSION.query(Approvals) + .filter(Approvals.chat_id == str(chat_id)) + .order_by(Approvals.user_id.asc()) + .all() + ) + finally: + SESSION.close() diff --git a/QueenNoxi/modules/sql/blacklist_sql.py b/QueenNoxi/modules/sql/blacklist_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..150b26714ef3545603bf2e353e9e552c20bbe419 --- /dev/null +++ b/QueenNoxi/modules/sql/blacklist_sql.py @@ -0,0 +1,197 @@ +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText, distinct, func + +from QueenNoxi.modules.sql import BASE, SESSION + + +class BlackListFilters(BASE): + __tablename__ = "blacklist" + chat_id = Column(String(14), primary_key=True) + trigger = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, trigger): + self.chat_id = str(chat_id) # ensure string + self.trigger = trigger + + def __repr__(self): + return "<Blacklist filter '%s' for %s>" % (self.trigger, self.chat_id) + + def __eq__(self, other): + return bool( + isinstance(other, BlackListFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger + ) + + +class BlacklistSettings(BASE): + __tablename__ = "blacklist_settings" + chat_id = Column(String(14), primary_key=True) + blacklist_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, blacklist_type=1, value="0"): + self.chat_id = str(chat_id) + self.blacklist_type = blacklist_type + self.value = value + + def __repr__(self): + return "<{} will executing {} for blacklist trigger.>".format( + self.chat_id, self.blacklist_type + ) + + +BlackListFilters.__table__.create(BASE.metadata.bind, checkfirst=True) +BlacklistSettings.__table__.create(BASE.metadata.bind, checkfirst=True) + +BLACKLIST_FILTER_INSERTION_LOCK = threading.RLock() +BLACKLIST_SETTINGS_INSERTION_LOCK = threading.RLock() + +CHAT_BLACKLISTS = {} +CHAT_SETTINGS_BLACKLISTS = {} + + +def add_to_blacklist(chat_id, trigger): + with BLACKLIST_FILTER_INSERTION_LOCK: + blacklist_filt = BlackListFilters(str(chat_id), trigger) + + SESSION.merge(blacklist_filt) # merge to avoid duplicate key issues + SESSION.commit() + global CHAT_BLACKLISTS + if CHAT_BLACKLISTS.get(str(chat_id), set()) == set(): + CHAT_BLACKLISTS[str(chat_id)] = {trigger} + else: + CHAT_BLACKLISTS.get(str(chat_id), set()).add(trigger) + + +def rm_from_blacklist(chat_id, trigger): + with BLACKLIST_FILTER_INSERTION_LOCK: + blacklist_filt = SESSION.query(BlackListFilters).get((str(chat_id), trigger)) + if blacklist_filt: + if trigger in CHAT_BLACKLISTS.get(str(chat_id), set()): # sanity check + CHAT_BLACKLISTS.get(str(chat_id), set()).remove(trigger) + + SESSION.delete(blacklist_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_blacklist(chat_id): + return CHAT_BLACKLISTS.get(str(chat_id), set()) + + +def num_blacklist_filters(): + try: + return SESSION.query(BlackListFilters).count() + finally: + SESSION.close() + + +def num_blacklist_chat_filters(chat_id): + try: + return ( + SESSION.query(BlackListFilters.chat_id) + .filter(BlackListFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_blacklist_filter_chats(): + try: + return SESSION.query(func.count(distinct(BlackListFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def set_blacklist_strength(chat_id, blacklist_type, value): + # for blacklist_type + # 0 = nothing + # 1 = delete + # 2 = warn + # 3 = mute + # 4 = kick + # 5 = ban + # 6 = tban + # 7 = tmute + with BLACKLIST_SETTINGS_INSERTION_LOCK: + global CHAT_SETTINGS_BLACKLISTS + curr_setting = SESSION.query(BlacklistSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = BlacklistSettings( + chat_id, blacklist_type=int(blacklist_type), value=value + ) + + curr_setting.blacklist_type = int(blacklist_type) + curr_setting.value = str(value) + CHAT_SETTINGS_BLACKLISTS[str(chat_id)] = { + "blacklist_type": int(blacklist_type), + "value": value, + } + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_blacklist_setting(chat_id): + try: + setting = CHAT_SETTINGS_BLACKLISTS.get(str(chat_id)) + if setting: + return setting["blacklist_type"], setting["value"] + else: + return 1, "0" + + finally: + SESSION.close() + + +def __load_chat_blacklists(): + global CHAT_BLACKLISTS + try: + chats = SESSION.query(BlackListFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_BLACKLISTS[chat_id] = [] + + all_filters = SESSION.query(BlackListFilters).all() + for x in all_filters: + CHAT_BLACKLISTS[x.chat_id] += [x.trigger] + + CHAT_BLACKLISTS = {x: set(y) for x, y in CHAT_BLACKLISTS.items()} + + finally: + SESSION.close() + + +def __load_chat_settings_blacklists(): + global CHAT_SETTINGS_BLACKLISTS + try: + chats_settings = SESSION.query(BlacklistSettings).all() + for x in chats_settings: # remove tuple by ( ,) + CHAT_SETTINGS_BLACKLISTS[x.chat_id] = { + "blacklist_type": x.blacklist_type, + "value": x.value, + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with BLACKLIST_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(BlackListFilters) + .filter(BlackListFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_blacklists() +__load_chat_settings_blacklists() diff --git a/QueenNoxi/modules/sql/blacklistusers_sql.py b/QueenNoxi/modules/sql/blacklistusers_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..ddd9737953c4bdc90e79453f436cdccdae5ae5a0 --- /dev/null +++ b/QueenNoxi/modules/sql/blacklistusers_sql.py @@ -0,0 +1,69 @@ +import threading + +from sqlalchemy import Column, String, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class BlacklistUsers(BASE): + __tablename__ = "blacklistusers" + user_id = Column(String(14), primary_key=True) + reason = Column(UnicodeText) + + def __init__(self, user_id, reason=None): + self.user_id = user_id + self.reason = reason + + +BlacklistUsers.__table__.create(BASE.metadata.bind, checkfirst=True) + +BLACKLIST_LOCK = threading.RLock() +BLACKLIST_USERS = set() + + +def blacklist_user(user_id, reason=None): + with BLACKLIST_LOCK: + user = SESSION.query(BlacklistUsers).get(str(user_id)) + if not user: + user = BlacklistUsers(str(user_id), reason) + else: + user.reason = reason + + SESSION.add(user) + SESSION.commit() + __load_blacklist_userid_list() + + +def unblacklist_user(user_id): + with BLACKLIST_LOCK: + user = SESSION.query(BlacklistUsers).get(str(user_id)) + if user: + SESSION.delete(user) + + SESSION.commit() + __load_blacklist_userid_list() + + +def get_reason(user_id): + user = SESSION.query(BlacklistUsers).get(str(user_id)) + rep = "" + if user: + rep = user.reason + + SESSION.close() + return rep + + +def is_user_blacklisted(user_id): + return user_id in BLACKLIST_USERS + + +def __load_blacklist_userid_list(): + global BLACKLIST_USERS + try: + BLACKLIST_USERS = {int(x.user_id) for x in SESSION.query(BlacklistUsers).all()} + finally: + SESSION.close() + + +__load_blacklist_userid_list() diff --git a/QueenNoxi/modules/sql/blsticker_sql.py b/QueenNoxi/modules/sql/blsticker_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..c7e0f311be564afeb8ce96dde65ad40df4819b47 --- /dev/null +++ b/QueenNoxi/modules/sql/blsticker_sql.py @@ -0,0 +1,197 @@ +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText, distinct, func + +from QueenNoxi.modules.sql import BASE, SESSION + + +class StickersFilters(BASE): + __tablename__ = "blacklist_stickers" + chat_id = Column(String(14), primary_key=True) + trigger = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, trigger): + self.chat_id = str(chat_id) # ensure string + self.trigger = trigger + + def __repr__(self): + return "<Stickers filter '%s' for %s>" % (self.trigger, self.chat_id) + + def __eq__(self, other): + return bool( + isinstance(other, StickersFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger + ) + + +class StickerSettings(BASE): + __tablename__ = "blsticker_settings" + chat_id = Column(String(14), primary_key=True) + blacklist_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, blacklist_type=1, value="0"): + self.chat_id = str(chat_id) + self.blacklist_type = blacklist_type + self.value = value + + def __repr__(self): + return "<{} will executing {} for blacklist trigger.>".format( + self.chat_id, self.blacklist_type + ) + + +StickersFilters.__table__.create(BASE.metadata.bind, checkfirst=True) +StickerSettings.__table__.create(BASE.metadata.bind, checkfirst=True) + +STICKERS_FILTER_INSERTION_LOCK = threading.RLock() +STICKSET_FILTER_INSERTION_LOCK = threading.RLock() + +CHAT_STICKERS = {} +CHAT_BLSTICK_BLACKLISTS = {} + + +def add_to_stickers(chat_id, trigger): + with STICKERS_FILTER_INSERTION_LOCK: + stickers_filt = StickersFilters(str(chat_id), trigger) + + SESSION.merge(stickers_filt) # merge to avoid duplicate key issues + SESSION.commit() + global CHAT_STICKERS + if CHAT_STICKERS.get(str(chat_id), set()) == set(): + CHAT_STICKERS[str(chat_id)] = {trigger} + else: + CHAT_STICKERS.get(str(chat_id), set()).add(trigger) + + +def rm_from_stickers(chat_id, trigger): + with STICKERS_FILTER_INSERTION_LOCK: + stickers_filt = SESSION.query(StickersFilters).get((str(chat_id), trigger)) + if stickers_filt: + if trigger in CHAT_STICKERS.get(str(chat_id), set()): # sanity check + CHAT_STICKERS.get(str(chat_id), set()).remove(trigger) + + SESSION.delete(stickers_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_stickers(chat_id): + return CHAT_STICKERS.get(str(chat_id), set()) + + +def num_stickers_filters(): + try: + return SESSION.query(StickersFilters).count() + finally: + SESSION.close() + + +def num_stickers_chat_filters(chat_id): + try: + return ( + SESSION.query(StickersFilters.chat_id) + .filter(StickersFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_stickers_filter_chats(): + try: + return SESSION.query(func.count(distinct(StickersFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def set_blacklist_strength(chat_id, blacklist_type, value): + # for blacklist_type + # 0 = nothing + # 1 = delete + # 2 = warn + # 3 = mute + # 4 = kick + # 5 = ban + # 6 = tban + # 7 = tmute + with STICKSET_FILTER_INSERTION_LOCK: + global CHAT_BLSTICK_BLACKLISTS + curr_setting = SESSION.query(StickerSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = StickerSettings( + chat_id, blacklist_type=int(blacklist_type), value=value + ) + + curr_setting.blacklist_type = int(blacklist_type) + curr_setting.value = str(value) + CHAT_BLSTICK_BLACKLISTS[str(chat_id)] = { + "blacklist_type": int(blacklist_type), + "value": value, + } + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_blacklist_setting(chat_id): + try: + setting = CHAT_BLSTICK_BLACKLISTS.get(str(chat_id)) + if setting: + return setting["blacklist_type"], setting["value"] + else: + return 1, "0" + + finally: + SESSION.close() + + +def __load_CHAT_STICKERS(): + global CHAT_STICKERS + try: + chats = SESSION.query(StickersFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_STICKERS[chat_id] = [] + + all_filters = SESSION.query(StickersFilters).all() + for x in all_filters: + CHAT_STICKERS[x.chat_id] += [x.trigger] + + CHAT_STICKERS = {x: set(y) for x, y in CHAT_STICKERS.items()} + + finally: + SESSION.close() + + +def __load_chat_stickerset_blacklists(): + global CHAT_BLSTICK_BLACKLISTS + try: + chats_settings = SESSION.query(StickerSettings).all() + for x in chats_settings: # remove tuple by ( ,) + CHAT_BLSTICK_BLACKLISTS[x.chat_id] = { + "blacklist_type": x.blacklist_type, + "value": x.value, + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with STICKERS_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(StickersFilters) + .filter(StickersFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_CHAT_STICKERS() +__load_chat_stickerset_blacklists() diff --git a/QueenNoxi/modules/sql/chatbot_sql.py b/QueenNoxi/modules/sql/chatbot_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..d681abef342de7b55fd6f09366c0346dd3d52185 --- /dev/null +++ b/QueenNoxi/modules/sql/chatbot_sql.py @@ -0,0 +1,42 @@ +import threading + +from sqlalchemy import Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class QueenNoxiChats(BASE): + __tablename__ = "queennoxi_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +QueenNoxiChats.__table__.create(BASE.metadata.bind, checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_queennoxi(chat_id): + try: + chat = SESSION.query(QueenNoxiChats).get(str(chat_id)) + return bool(chat) + finally: + SESSION.close() + + +def set_queennoxi(chat_id): + with INSERTION_LOCK: + queennoxichat = SESSION.query(QueenNoxiChats).get(str(chat_id)) + if not queennoxichat: + queennoxichat = QueenNoxiChats(str(chat_id)) + SESSION.add(queennoxichat) + SESSION.commit() + + +def rem_queennoxi(chat_id): + with INSERTION_LOCK: + queennoxichat = SESSION.query(QueenNoxiChats).get(str(chat_id)) + if queennoxichat: + SESSION.delete(queennoxichat) + SESSION.commit() diff --git a/QueenNoxi/modules/sql/cleaner_sql.py b/QueenNoxi/modules/sql/cleaner_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..52f393ccb72d259b87e9efbdca0ee38c33801870 --- /dev/null +++ b/QueenNoxi/modules/sql/cleaner_sql.py @@ -0,0 +1,199 @@ +import threading + +from sqlalchemy import Boolean, Column, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class CleanerBlueTextChatSettings(BASE): + __tablename__ = "cleaner_bluetext_chat_setting" + chat_id = Column(UnicodeText, primary_key=True) + is_enable = Column(Boolean, default=False) + + def __init__(self, chat_id, is_enable): + self.chat_id = chat_id + self.is_enable = is_enable + + def __repr__(self): + return "clean blue text for {}".format(self.chat_id) + + +class CleanerBlueTextChat(BASE): + __tablename__ = "cleaner_bluetext_chat_ignore_commands" + chat_id = Column(UnicodeText, primary_key=True) + command = Column(UnicodeText, primary_key=True) + + def __init__(self, chat_id, command): + self.chat_id = chat_id + self.command = command + + +class CleanerBlueTextGlobal(BASE): + __tablename__ = "cleaner_bluetext_global_ignore_commands" + command = Column(UnicodeText, primary_key=True) + + def __init__(self, command): + self.command = command + + +CleanerBlueTextChatSettings.__table__.create(BASE.metadata.bind, checkfirst=True) +CleanerBlueTextChat.__table__.create(BASE.metadata.bind, checkfirst=True) +CleanerBlueTextGlobal.__table__.create(BASE.metadata.bind, checkfirst=True) + +CLEANER_CHAT_SETTINGS = threading.RLock() +CLEANER_CHAT_LOCK = threading.RLock() +CLEANER_GLOBAL_LOCK = threading.RLock() + +CLEANER_CHATS = {} +GLOBAL_IGNORE_COMMANDS = set() + + +def set_cleanbt(chat_id, is_enable): + with CLEANER_CHAT_SETTINGS: + curr = SESSION.query(CleanerBlueTextChatSettings).get(str(chat_id)) + if curr: + SESSION.delete(curr) + + newcurr = CleanerBlueTextChatSettings(str(chat_id), is_enable) + + SESSION.add(newcurr) + SESSION.commit() + + +def chat_ignore_command(chat_id, ignore): + ignore = ignore.lower() + with CLEANER_CHAT_LOCK: + ignored = SESSION.query(CleanerBlueTextChat).get((str(chat_id), ignore)) + + if not ignored: + + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), + {"setting": False, "commands": set()}, + ) + + CLEANER_CHATS[str(chat_id)]["commands"].add(ignore) + + ignored = CleanerBlueTextChat(str(chat_id), ignore) + SESSION.add(ignored) + SESSION.commit() + return True + SESSION.close() + return False + + +def chat_unignore_command(chat_id, unignore): + unignore = unignore.lower() + with CLEANER_CHAT_LOCK: + unignored = SESSION.query(CleanerBlueTextChat).get((str(chat_id), unignore)) + + if unignored: + + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), + {"setting": False, "commands": set()}, + ) + if unignore in CLEANER_CHATS.get(str(chat_id)).get("commands"): + CLEANER_CHATS[str(chat_id)]["commands"].remove(unignore) + + SESSION.delete(unignored) + SESSION.commit() + return True + + SESSION.close() + return False + + +def global_ignore_command(command): + command = command.lower() + with CLEANER_GLOBAL_LOCK: + ignored = SESSION.query(CleanerBlueTextGlobal).get(str(command)) + + if not ignored: + GLOBAL_IGNORE_COMMANDS.add(command) + + ignored = CleanerBlueTextGlobal(str(command)) + SESSION.add(ignored) + SESSION.commit() + return True + + SESSION.close() + return False + + +def global_unignore_command(command): + command = command.lower() + with CLEANER_GLOBAL_LOCK: + unignored = SESSION.query(CleanerBlueTextGlobal).get(str(command)) + + if unignored: + if command in GLOBAL_IGNORE_COMMANDS: + GLOBAL_IGNORE_COMMANDS.remove(command) + + SESSION.delete(command) + SESSION.commit() + return True + + SESSION.close() + return False + + +def is_command_ignored(chat_id, command): + if command.lower() in GLOBAL_IGNORE_COMMANDS: + return True + + if str(chat_id) in CLEANER_CHATS: + if command.lower() in CLEANER_CHATS.get(str(chat_id)).get("commands"): + return True + + return False + + +def is_enabled(chat_id): + try: + resultcurr = SESSION.query(CleanerBlueTextChatSettings).get(str(chat_id)) + if resultcurr: + return resultcurr.is_enable + return False # default + finally: + SESSION.close() + + +def get_all_ignored(chat_id): + if str(chat_id) in CLEANER_CHATS: + LOCAL_IGNORE_COMMANDS = CLEANER_CHATS.get(str(chat_id)).get("commands") + else: + LOCAL_IGNORE_COMMANDS = set() + + return GLOBAL_IGNORE_COMMANDS, LOCAL_IGNORE_COMMANDS + + +def __load_cleaner_list(): + global GLOBAL_IGNORE_COMMANDS + global CLEANER_CHATS + + try: + GLOBAL_IGNORE_COMMANDS = { + (x.command) for x in SESSION.query(CleanerBlueTextGlobal).all() + } + finally: + SESSION.close() + + try: + for x in SESSION.query(CleanerBlueTextChatSettings).all(): + CLEANER_CHATS.setdefault(x.chat_id, {"setting": False, "commands": set()}) + CLEANER_CHATS[x.chat_id]["setting"] = x.is_enable + finally: + SESSION.close() + + try: + for x in SESSION.query(CleanerBlueTextChat).all(): + CLEANER_CHATS.setdefault(x.chat_id, {"setting": False, "commands": set()}) + CLEANER_CHATS[x.chat_id]["commands"].add(x.command) + finally: + SESSION.close() + + +__load_cleaner_list() diff --git a/QueenNoxi/modules/sql/connection_sql.py b/QueenNoxi/modules/sql/connection_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..6ac25704c072a566bacbde0775706e2be87b9061 --- /dev/null +++ b/QueenNoxi/modules/sql/connection_sql.py @@ -0,0 +1,204 @@ +import threading +import time +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class ChatAccessConnectionSettings(BASE): + __tablename__ = "access_connection" + chat_id = Column(String(14), primary_key=True) + allow_connect_to_chat = Column(Boolean, default=True) + + def __init__(self, chat_id, allow_connect_to_chat): + self.chat_id = str(chat_id) + self.allow_connect_to_chat = str(allow_connect_to_chat) + + def __repr__(self): + return "<Chat access settings ({}) is {}>".format( + self.chat_id, self.allow_connect_to_chat + ) + + +class Connection(BASE): + __tablename__ = "connection" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14)) + + def __init__(self, user_id, chat_id): + self.user_id = user_id + self.chat_id = str(chat_id) # Ensure String + + +class ConnectionHistory(BASE): + __tablename__ = "connection_history" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText) + conn_time = Column(BigInteger) + + def __init__(self, user_id, chat_id, chat_name, conn_time): + self.user_id = user_id + self.chat_id = str(chat_id) + self.chat_name = str(chat_name) + self.conn_time = int(conn_time) + + def __repr__(self): + return "<connection user {} history {}>".format(self.user_id, self.chat_id) + + +ChatAccessConnectionSettings.__table__.create(BASE.metadata.bind, checkfirst=True) +Connection.__table__.create(BASE.metadata.bind, checkfirst=True) +ConnectionHistory.__table__.create(BASE.metadata.bind, checkfirst=True) + +CHAT_ACCESS_LOCK = threading.RLock() +CONNECTION_INSERTION_LOCK = threading.RLock() +CONNECTION_HISTORY_LOCK = threading.RLock() + +HISTORY_CONNECT = {} + + +def allow_connect_to_chat(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) + if chat_setting: + return chat_setting.allow_connect_to_chat + return False + finally: + SESSION.close() + + +def set_allow_connect_to_chat(chat_id: Union[int, str], setting: bool): + with CHAT_ACCESS_LOCK: + chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) + if not chat_setting: + chat_setting = ChatAccessConnectionSettings(chat_id, setting) + + chat_setting.allow_connect_to_chat = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def connect(user_id, chat_id): + with CONNECTION_INSERTION_LOCK: + prev = SESSION.query(Connection).get((int(user_id))) + if prev: + SESSION.delete(prev) + connect_to_chat = Connection(int(user_id), chat_id) + SESSION.add(connect_to_chat) + SESSION.commit() + return True + + +def get_connected_chat(user_id): + try: + return SESSION.query(Connection).get((int(user_id))) + finally: + SESSION.close() + + +def curr_connection(chat_id): + try: + return SESSION.query(Connection).get((str(chat_id))) + finally: + SESSION.close() + + +def disconnect(user_id): + with CONNECTION_INSERTION_LOCK: + disconnect = SESSION.query(Connection).get((int(user_id))) + if disconnect: + SESSION.delete(disconnect) + SESSION.commit() + return True + else: + SESSION.close() + return False + + +def add_history_conn(user_id, chat_id, chat_name): + global HISTORY_CONNECT + with CONNECTION_HISTORY_LOCK: + conn_time = int(time.time()) + if HISTORY_CONNECT.get(int(user_id)): + counting = ( + SESSION.query(ConnectionHistory.user_id) + .filter(ConnectionHistory.user_id == str(user_id)) + .count() + ) + getchat_id = {} + for x in HISTORY_CONNECT[int(user_id)]: + getchat_id[HISTORY_CONNECT[int(user_id)][x]["chat_id"]] = x + if chat_id in getchat_id: + todeltime = getchat_id[str(chat_id)] + delold = SESSION.query(ConnectionHistory).get( + (int(user_id), str(chat_id)) + ) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(todeltime) + elif counting >= 5: + todel = list(HISTORY_CONNECT[int(user_id)]) + todel.reverse() + todel = todel[4:] + for x in todel: + chat_old = HISTORY_CONNECT[int(user_id)][x]["chat_id"] + delold = SESSION.query(ConnectionHistory).get( + (int(user_id), str(chat_old)) + ) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(x) + else: + HISTORY_CONNECT[int(user_id)] = {} + delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_id))) + if delold: + SESSION.delete(delold) + history = ConnectionHistory(int(user_id), str(chat_id), chat_name, conn_time) + SESSION.add(history) + SESSION.commit() + HISTORY_CONNECT[int(user_id)][conn_time] = { + "chat_name": chat_name, + "chat_id": str(chat_id), + } + + +def get_history_conn(user_id): + if not HISTORY_CONNECT.get(int(user_id)): + HISTORY_CONNECT[int(user_id)] = {} + return HISTORY_CONNECT[int(user_id)] + + +def clear_history_conn(user_id): + global HISTORY_CONNECT + todel = list(HISTORY_CONNECT[int(user_id)]) + for x in todel: + chat_old = HISTORY_CONNECT[int(user_id)][x]["chat_id"] + delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_old))) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(x) + SESSION.commit() + return True + + +def __load_user_history(): + global HISTORY_CONNECT + try: + qall = SESSION.query(ConnectionHistory).all() + HISTORY_CONNECT = {} + for x in qall: + check = HISTORY_CONNECT.get(x.user_id) + if check is None: + HISTORY_CONNECT[x.user_id] = {} + HISTORY_CONNECT[x.user_id][x.conn_time] = { + "chat_name": x.chat_name, + "chat_id": x.chat_id, + } + finally: + SESSION.close() + + +__load_user_history() diff --git a/QueenNoxi/modules/sql/cust_filters_sql.py b/QueenNoxi/modules/sql/cust_filters_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..b4f83afa79053d122d5d4e1bca57600581e696e9 --- /dev/null +++ b/QueenNoxi/modules/sql/cust_filters_sql.py @@ -0,0 +1,401 @@ +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func + +from QueenNoxi.modules.helper_funcs.msg_types import Types +from QueenNoxi.modules.sql import BASE, SESSION + + +class CustomFilters(BASE): + __tablename__ = "cust_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + reply = Column(UnicodeText, nullable=False) + is_sticker = Column(Boolean, nullable=False, default=False) + is_document = Column(Boolean, nullable=False, default=False) + is_image = Column(Boolean, nullable=False, default=False) + is_audio = Column(Boolean, nullable=False, default=False) + is_voice = Column(Boolean, nullable=False, default=False) + is_video = Column(Boolean, nullable=False, default=False) + + has_buttons = Column(Boolean, nullable=False, default=False) + # NOTE: Here for legacy purposes, to ensure older filters don't mess up. + has_markdown = Column(Boolean, nullable=False, default=False) + + # NEW FILTER + # alter table cust_filters add column reply_text text; + # alter table cust_filters add column file_type BigInteger default 1; + # alter table cust_filters add column file_id text; + reply_text = Column(UnicodeText) + file_type = Column(BigInteger, nullable=False, default=1) + file_id = Column(UnicodeText, default=None) + + def __init__( + self, + chat_id, + keyword, + reply, + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + has_buttons=False, + reply_text=None, + file_type=1, + file_id=None, + ): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.reply = reply + self.is_sticker = is_sticker + self.is_document = is_document + self.is_image = is_image + self.is_audio = is_audio + self.is_voice = is_voice + self.is_video = is_video + self.has_buttons = has_buttons + self.has_markdown = True + + self.reply_text = reply_text + self.file_type = file_type + self.file_id = file_id + + def __repr__(self): + return "<Permissions for %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, CustomFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class NewCustomFilters(BASE): + __tablename__ = "cust_filters_new" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + text = Column(UnicodeText) + file_type = Column(BigInteger, nullable=False, default=1) + file_id = Column(UnicodeText, default=None) + + def __init__(self, chat_id, keyword, text, file_type, file_id): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.text = text + self.file_type = file_type + self.file_id = file_id + + def __repr__(self): + return "<Filter for %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, CustomFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class Buttons(BASE): + __tablename__ = "cust_filter_urls" + id = Column(BigInteger, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, keyword, name, url, same_line=False): + self.chat_id = str(chat_id) + self.keyword = keyword + self.name = name + self.url = url + self.same_line = same_line + + +CustomFilters.__table__.create(BASE.metadata.bind, checkfirst=True) +Buttons.__table__.create(BASE.metadata.bind, checkfirst=True) + +CUST_FILT_LOCK = threading.RLock() +BUTTON_LOCK = threading.RLock() +CHAT_FILTERS = {} + + +def get_all_filters(): + try: + return SESSION.query(CustomFilters).all() + finally: + SESSION.close() + + +def add_filter( + chat_id, + keyword, + reply, + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + buttons=None, +): + global CHAT_FILTERS + + if buttons is None: + buttons = [] + + with CUST_FILT_LOCK: + prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if prev: + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + + filt = CustomFilters( + str(chat_id), + keyword, + reply, + is_sticker, + is_document, + is_image, + is_audio, + is_voice, + is_video, + bool(buttons), + ) + + if keyword not in CHAT_FILTERS.get(str(chat_id), []): + CHAT_FILTERS[str(chat_id)] = sorted( + CHAT_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.add(filt) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, keyword, b_name, url, same_line) + + +def new_add_filter(chat_id, keyword, reply_text, file_type, file_id, buttons): + global CHAT_FILTERS + + if buttons is None: + buttons = [] + + with CUST_FILT_LOCK: + prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if prev: + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + + filt = CustomFilters( + str(chat_id), + keyword, + reply="there is should be a new reply", + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + has_buttons=bool(buttons), + reply_text=reply_text, + file_type=file_type.value, + file_id=file_id, + ) + + if keyword not in CHAT_FILTERS.get(str(chat_id), []): + CHAT_FILTERS[str(chat_id)] = sorted( + CHAT_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.add(filt) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, keyword, b_name, url, same_line) + + +def remove_filter(chat_id, keyword): + global CHAT_FILTERS + with CUST_FILT_LOCK: + filt = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if filt: + if keyword in CHAT_FILTERS.get(str(chat_id), []): # Sanity check + CHAT_FILTERS.get(str(chat_id), []).remove(keyword) + + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + SESSION.delete(filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_triggers(chat_id): + return CHAT_FILTERS.get(str(chat_id), set()) + + +def get_chat_filters(chat_id): + try: + return ( + SESSION.query(CustomFilters) + .filter(CustomFilters.chat_id == str(chat_id)) + .order_by(func.length(CustomFilters.keyword).desc()) + .order_by(CustomFilters.keyword.asc()) + .all() + ) + finally: + SESSION.close() + + +def get_filter(chat_id, keyword): + try: + return SESSION.query(CustomFilters).get((str(chat_id), keyword)) + finally: + SESSION.close() + + +def add_note_button_to_db(chat_id, keyword, b_name, url, same_line): + with BUTTON_LOCK: + button = Buttons(chat_id, keyword, b_name, url, same_line) + SESSION.add(button) + SESSION.commit() + + +def get_buttons(chat_id, keyword): + try: + return ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .order_by(Buttons.id) + .all() + ) + finally: + SESSION.close() + + +def num_filters(): + try: + return SESSION.query(CustomFilters).count() + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(CustomFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def __load_chat_filters(): + global CHAT_FILTERS + try: + chats = SESSION.query(CustomFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_FILTERS[chat_id] = [] + + all_filters = SESSION.query(CustomFilters).all() + for x in all_filters: + CHAT_FILTERS[x.chat_id] += [x.keyword] + + CHAT_FILTERS = { + x: sorted(set(y), key=lambda i: (-len(i), i)) + for x, y in CHAT_FILTERS.items() + } + + finally: + SESSION.close() + + +# ONLY USE FOR MIGRATE OLD FILTERS TO NEW FILTERS +def __migrate_filters(): + try: + all_filters = SESSION.query(CustomFilters).distinct().all() + for x in all_filters: + if x.is_document: + file_type = Types.DOCUMENT + elif x.is_image: + file_type = Types.PHOTO + elif x.is_video: + file_type = Types.VIDEO + elif x.is_sticker: + file_type = Types.STICKER + elif x.is_audio: + file_type = Types.AUDIO + elif x.is_voice: + file_type = Types.VOICE + else: + file_type = Types.TEXT + + print(str(x.chat_id), x.keyword, x.reply, file_type.value) + if file_type == Types.TEXT: + filt = CustomFilters( + str(x.chat_id), x.keyword, x.reply, file_type.value, None + ) + else: + filt = CustomFilters( + str(x.chat_id), x.keyword, None, file_type.value, x.reply + ) + + SESSION.add(filt) + SESSION.commit() + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with CUST_FILT_LOCK: + chat_filters = ( + SESSION.query(CustomFilters) + .filter(CustomFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + old_filt = CHAT_FILTERS.get(str(old_chat_id)) + if old_filt: + CHAT_FILTERS[str(new_chat_id)] = old_filt + del CHAT_FILTERS[str(old_chat_id)] + + with BUTTON_LOCK: + chat_buttons = ( + SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_filters() diff --git a/QueenNoxi/modules/sql/disable_sql.py b/QueenNoxi/modules/sql/disable_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..fe5439b00a9a14034d8d4bc32cbf200e14b30c76 --- /dev/null +++ b/QueenNoxi/modules/sql/disable_sql.py @@ -0,0 +1,105 @@ +import threading + +from sqlalchemy import Column, String, UnicodeText, distinct, func + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Disable(BASE): + __tablename__ = "disabled_commands" + chat_id = Column(String(14), primary_key=True) + command = Column(UnicodeText, primary_key=True) + + def __init__(self, chat_id, command): + self.chat_id = chat_id + self.command = command + + def __repr__(self): + return "Disabled cmd {} in {}".format(self.command, self.chat_id) + + +Disable.__table__.create(BASE.metadata.bind, checkfirst=True) +DISABLE_INSERTION_LOCK = threading.RLock() + +DISABLED = {} + + +def disable_command(chat_id, disable): + with DISABLE_INSERTION_LOCK: + disabled = SESSION.query(Disable).get((str(chat_id), disable)) + + if not disabled: + DISABLED.setdefault(str(chat_id), set()).add(disable) + + disabled = Disable(str(chat_id), disable) + SESSION.add(disabled) + SESSION.commit() + return True + + SESSION.close() + return False + + +def enable_command(chat_id, enable): + with DISABLE_INSERTION_LOCK: + disabled = SESSION.query(Disable).get((str(chat_id), enable)) + + if disabled: + if enable in DISABLED.get(str(chat_id)): # sanity check + DISABLED.setdefault(str(chat_id), set()).remove(enable) + + SESSION.delete(disabled) + SESSION.commit() + return True + + SESSION.close() + return False + + +def is_command_disabled(chat_id, cmd): + return str(cmd).lower() in DISABLED.get(str(chat_id), set()) + + +def get_all_disabled(chat_id): + return DISABLED.get(str(chat_id), set()) + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Disable.chat_id))).scalar() + finally: + SESSION.close() + + +def num_disabled(): + try: + return SESSION.query(Disable).count() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with DISABLE_INSERTION_LOCK: + chats = SESSION.query(Disable).filter(Disable.chat_id == str(old_chat_id)).all() + for chat in chats: + chat.chat_id = str(new_chat_id) + SESSION.add(chat) + + if str(old_chat_id) in DISABLED: + DISABLED[str(new_chat_id)] = DISABLED.get(str(old_chat_id), set()) + + SESSION.commit() + + +def __load_disabled_commands(): + global DISABLED + try: + all_chats = SESSION.query(Disable).all() + for chat in all_chats: + DISABLED.setdefault(chat.chat_id, set()).add(chat.command) + + finally: + SESSION.close() + + +__load_disabled_commands() diff --git a/QueenNoxi/modules/sql/feds_sql.py b/QueenNoxi/modules/sql/feds_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce62b38eb7f768ca535cb4696e4a48ece5b5532 --- /dev/null +++ b/QueenNoxi/modules/sql/feds_sql.py @@ -0,0 +1,917 @@ +import ast +import threading + +from sqlalchemy import Boolean, Column, Integer, String, UnicodeText +from sqlalchemy.sql.sqltypes import BigInteger + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Federations(BASE): + __tablename__ = "feds" + owner_id = Column(String(14)) + fed_name = Column(UnicodeText) + fed_id = Column(UnicodeText, primary_key=True) + fed_rules = Column(UnicodeText) + fed_log = Column(UnicodeText) + fed_users = Column(UnicodeText) + + def __init__(self, owner_id, fed_name, fed_id, fed_rules, fed_log, fed_users): + self.owner_id = owner_id + self.fed_name = fed_name + self.fed_id = fed_id + self.fed_rules = fed_rules + self.fed_log = fed_log + self.fed_users = fed_users + + +class ChatF(BASE): + __tablename__ = "chat_feds" + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText) + fed_id = Column(UnicodeText) + + def __init__(self, chat_id, chat_name, fed_id): + self.chat_id = chat_id + self.chat_name = chat_name + self.fed_id = fed_id + + +class BansF(BASE): + __tablename__ = "bans_feds" + fed_id = Column(UnicodeText, primary_key=True) + user_id = Column(String(14), primary_key=True) + first_name = Column(UnicodeText, nullable=False) + last_name = Column(UnicodeText) + user_name = Column(UnicodeText) + reason = Column(UnicodeText, default="") + time = Column(Integer, default=0) + + def __init__(self, fed_id, user_id, first_name, last_name, user_name, reason, time): + self.fed_id = fed_id + self.user_id = user_id + self.first_name = first_name + self.last_name = last_name + self.user_name = user_name + self.reason = reason + self.time = time + + +class FedsUserSettings(BASE): + __tablename__ = "feds_settings" + user_id = Column(BigInteger, primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, user_id): + self.user_id = user_id + + def __repr__(self): + return "<Feds report settings ({})>".format(self.user_id) + + +class FedSubs(BASE): + __tablename__ = "feds_subs" + fed_id = Column(UnicodeText, primary_key=True) + fed_subs = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, fed_id, fed_subs): + self.fed_id = fed_id + self.fed_subs = fed_subs + + def __repr__(self): + return "<Fed {} subscribes for {}>".format(self.fed_id, self.fed_subs) + + +# Dropping db +# Federations.__table__.drop() +# ChatF.__table__.drop() +# BansF.__table__.drop() +# FedSubs.__table__.drop() + +Federations.__table__.create(BASE.metadata.bind, checkfirst=True) +ChatF.__table__.create(BASE.metadata.bind, checkfirst=True) +BansF.__table__.create(BASE.metadata.bind, checkfirst=True) +FedsUserSettings.__table__.create(BASE.metadata.bind, checkfirst=True) +FedSubs.__table__.create(BASE.metadata.bind, checkfirst=True) + +FEDS_LOCK = threading.RLock() +CHAT_FEDS_LOCK = threading.RLock() +FEDS_SETTINGS_LOCK = threading.RLock() +FEDS_SUBSCRIBER_LOCK = threading.RLock() + +FEDERATION_BYNAME = {} +FEDERATION_BYOWNER = {} +FEDERATION_BYFEDID = {} + +FEDERATION_CHATS = {} +FEDERATION_CHATS_BYID = {} + +FEDERATION_BANNED_FULL = {} +FEDERATION_BANNED_USERID = {} + +FEDERATION_NOTIFICATION = {} +FEDS_SUBSCRIBER = {} +MYFEDS_SUBSCRIBER = {} + + +def get_fed_info(fed_id): + get = FEDERATION_BYFEDID.get(str(fed_id)) + if get is None: + return False + return get + + +def get_fed_id(chat_id): + get = FEDERATION_CHATS.get(str(chat_id)) + if get is None: + return False + return get["fid"] + + +def get_fed_name(chat_id): + get = FEDERATION_CHATS.get(str(chat_id)) + if get is None: + return False + return get["chat_name"] + + +def get_user_fban(fed_id, user_id): + if not FEDERATION_BANNED_FULL.get(fed_id): + return False, False, False + user_info = FEDERATION_BANNED_FULL[fed_id].get(user_id) + if not user_info: + return None, None, None + return user_info["first_name"], user_info["reason"], user_info["time"] + + +def get_user_admin_fed_name(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) in ast.literal_eval( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["members"] + ): + user_feds.append(FEDERATION_BYFEDID[f]["fname"]) + return user_feds + + +def get_user_owner_fed_name(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) == int( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["owner"] + ): + user_feds.append(FEDERATION_BYFEDID[f]["fname"]) + return user_feds + + +def get_user_admin_fed_full(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) in ast.literal_eval( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["members"] + ): + user_feds.append({"fed_id": f, "fed": FEDERATION_BYFEDID[f]}) + return user_feds + + +def get_user_owner_fed_full(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) == int( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["owner"] + ): + user_feds.append({"fed_id": f, "fed": FEDERATION_BYFEDID[f]}) + return user_feds + + +def get_user_fbanlist(user_id): + banlist = FEDERATION_BANNED_FULL + user_name = "" + fedname = [] + for x in banlist: + if banlist[x].get(user_id): + if user_name == "": + user_name = banlist[x][user_id].get("first_name") + fedname.append([x, banlist[x][user_id].get("reason")]) + return user_name, fedname + + +def new_fed(owner_id, fed_name, fed_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + "Rules is not set in this federation.", + None, + str({"owner": str(owner_id), "members": "[]"}), + ) + SESSION.add(fed) + SESSION.commit() + FEDERATION_BYOWNER[str(owner_id)] = { + "fid": str(fed_id), + "fname": fed_name, + "frules": "Rules is not set in this federation.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + FEDERATION_BYFEDID[str(fed_id)] = { + "owner": str(owner_id), + "fname": fed_name, + "frules": "Rules is not set in this federation.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + FEDERATION_BYNAME[fed_name] = { + "fid": str(fed_id), + "owner": str(owner_id), + "frules": "Rules is not set in this federation.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + return fed + + +def del_fed(fed_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME, FEDERATION_CHATS, FEDERATION_CHATS_BYID, FEDERATION_BANNED_USERID, FEDERATION_BANNED_FULL + getcache = FEDERATION_BYFEDID.get(fed_id) + if getcache is None: + return False + # Variables + getfed = FEDERATION_BYFEDID.get(fed_id) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + # Delete from cache + FEDERATION_BYOWNER.pop(owner_id) + FEDERATION_BYFEDID.pop(fed_id) + FEDERATION_BYNAME.pop(fed_name) + if FEDERATION_CHATS_BYID.get(fed_id): + for x in FEDERATION_CHATS_BYID[fed_id]: + delchats = SESSION.query(ChatF).get(str(x)) + if delchats: + SESSION.delete(delchats) + SESSION.commit() + FEDERATION_CHATS.pop(x) + FEDERATION_CHATS_BYID.pop(fed_id) + # Delete fedban users + getall = FEDERATION_BANNED_USERID.get(fed_id) + if getall: + for x in getall: + banlist = SESSION.query(BansF).get((fed_id, str(x))) + if banlist: + SESSION.delete(banlist) + SESSION.commit() + if FEDERATION_BANNED_USERID.get(fed_id): + FEDERATION_BANNED_USERID.pop(fed_id) + if FEDERATION_BANNED_FULL.get(fed_id): + FEDERATION_BANNED_FULL.pop(fed_id) + # Delete fedsubs + getall = MYFEDS_SUBSCRIBER.get(fed_id) + if getall: + for x in getall: + getsubs = SESSION.query(FedSubs).get((fed_id, str(x))) + if getsubs: + SESSION.delete(getsubs) + SESSION.commit() + if FEDS_SUBSCRIBER.get(fed_id): + FEDS_SUBSCRIBER.pop(fed_id) + if MYFEDS_SUBSCRIBER.get(fed_id): + MYFEDS_SUBSCRIBER.pop(fed_id) + # Delete from database + curr = SESSION.query(Federations).get(fed_id) + if curr: + SESSION.delete(curr) + SESSION.commit() + return True + + +def rename_fed(fed_id, owner_id, newname): + with FEDS_LOCK: + global FEDERATION_BYFEDID, FEDERATION_BYOWNER, FEDERATION_BYNAME + fed = SESSION.query(Federations).get(fed_id) + if not fed: + return False + fed.fed_name = newname + SESSION.commit() + + # Update the dicts + oldname = FEDERATION_BYFEDID[str(fed_id)]["fname"] + tempdata = FEDERATION_BYNAME[oldname] + FEDERATION_BYNAME.pop(oldname) + + FEDERATION_BYOWNER[str(owner_id)]["fname"] = newname + FEDERATION_BYFEDID[str(fed_id)]["fname"] = newname + FEDERATION_BYNAME[newname] = tempdata + return True + + +def chat_join_fed(fed_id, chat_name, chat_id): + with FEDS_LOCK: + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + r = ChatF(chat_id, chat_name, fed_id) + SESSION.add(r) + FEDERATION_CHATS[str(chat_id)] = {"chat_name": chat_name, "fid": fed_id} + checkid = FEDERATION_CHATS_BYID.get(fed_id) + if checkid is None: + FEDERATION_CHATS_BYID[fed_id] = [] + FEDERATION_CHATS_BYID[fed_id].append(str(chat_id)) + SESSION.commit() + return r + + +def search_fed_by_name(fed_name): + allfed = FEDERATION_BYNAME.get(fed_name) + if allfed is None: + return False + return allfed + + +def search_user_in_fed(fed_id, user_id): + getfed = FEDERATION_BYFEDID.get(fed_id) + if getfed is None: + return False + getfed = ast.literal_eval(getfed["fusers"])["members"] + return user_id in ast.literal_eval(getfed) + + +def user_demote_fed(fed_id, user_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_rules = getfed["frules"] + fed_log = getfed["flog"] + # Temp set + try: + members = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + except ValueError: + return False + members.remove(user_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYFEDID[str(fed_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYNAME[fed_name]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str({"owner": str(owner_id), "members": str(members)}), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + curr = SESSION.query(UserF).all() + result = False + for r in curr: + if int(r.user_id) == int(user_id): + if r.fed_id == fed_id: + SESSION.delete(r) + SESSION.commit() + result = True + + SESSION.close() + return result + + +def user_join_fed(fed_id, user_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_rules = getfed["frules"] + fed_log = getfed["flog"] + # Temp set + members = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + members.append(user_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYFEDID[str(fed_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYNAME[fed_name]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str({"owner": str(owner_id), "members": str(members)}), + ) + SESSION.merge(fed) + SESSION.commit() + __load_all_feds_chats() + return True + + +def chat_leave_fed(chat_id): + with FEDS_LOCK: + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + # Set variables + fed_info = FEDERATION_CHATS.get(str(chat_id)) + if fed_info is None: + return False + fed_id = fed_info["fid"] + # Delete from cache + FEDERATION_CHATS.pop(str(chat_id)) + FEDERATION_CHATS_BYID[str(fed_id)].remove(str(chat_id)) + # Delete from db + curr = SESSION.query(ChatF).all() + for U in curr: + if int(U.chat_id) == int(chat_id): + SESSION.delete(U) + SESSION.commit() + return True + + +def all_fed_chats(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_CHATS_BYID.get(fed_id) + if getfed is None: + return [] + return getfed + + +def all_fed_users(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + if getfed is None: + return False + fed_owner = ast.literal_eval(ast.literal_eval(getfed["fusers"])["owner"]) + fed_admins = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + fed_admins.append(fed_owner) + return fed_admins + + +def all_fed_members(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + fed_admins = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + return fed_admins + + +def set_frules(fed_id, rules): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_members = getfed["fusers"] + fed_rules = str(rules) + fed_log = getfed["flog"] + # Set user + FEDERATION_BYOWNER[str(owner_id)]["frules"] = fed_rules + FEDERATION_BYFEDID[str(fed_id)]["frules"] = fed_rules + FEDERATION_BYNAME[fed_name]["frules"] = fed_rules + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str(fed_members), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + +def get_frules(fed_id): + with FEDS_LOCK: + rules = FEDERATION_BYFEDID[str(fed_id)]["frules"] + return rules + + +def fban_user(fed_id, user_id, first_name, last_name, user_name, reason, time): + with FEDS_LOCK: + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + SESSION.delete(I) + + r = BansF( + str(fed_id), + str(user_id), + first_name, + last_name, + user_name, + reason, + time, + ) + + SESSION.add(r) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return r + + +def multi_fban_user( + multi_fed_id, + multi_user_id, + multi_first_name, + multi_last_name, + multi_user_name, + multi_reason, +): + if True: # with FEDS_LOCK: + counter = 0 + time = 0 + for x in range(len(multi_fed_id)): + fed_id = multi_fed_id[x] + user_id = multi_user_id[x] + first_name = multi_first_name[x] + last_name = multi_last_name[x] + user_name = multi_user_name[x] + reason = multi_reason[x] + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + SESSION.delete(I) + + r = BansF( + str(fed_id), + str(user_id), + first_name, + last_name, + user_name, + reason, + time, + ) + + SESSION.add(r) + counter += 1 + if str(str(counter)[-2:]) == "00": + print(user_id) + print(first_name) + print(reason) + print(counter) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + print("Done") + return counter + + +def un_fban_user(fed_id, user_id): + with FEDS_LOCK: + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + SESSION.delete(I) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return I + + +def get_fban_user(fed_id, user_id): + list_fbanned = FEDERATION_BANNED_USERID.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_USERID[fed_id] = [] + if user_id in FEDERATION_BANNED_USERID[fed_id]: + r = SESSION.query(BansF).all() + reason = None + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + reason = I.reason + time = I.time + return True, reason, time + return False, None, None + + +def get_all_fban_users(fed_id): + list_fbanned = FEDERATION_BANNED_USERID.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_USERID[fed_id] = [] + return FEDERATION_BANNED_USERID[fed_id] + + +def get_all_fban_users_target(fed_id, user_id): + list_fbanned = FEDERATION_BANNED_FULL.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_FULL[fed_id] = [] + return False + getuser = list_fbanned[str(user_id)] + return getuser + + +def get_all_fban_users_global(): + total = [] + for x in list(FEDERATION_BANNED_USERID): + for y in FEDERATION_BANNED_USERID[x]: + total.append(y) + return total + + +def get_all_feds_users_global(): + total = [] + for x in list(FEDERATION_BYFEDID): + total.append(FEDERATION_BYFEDID[x]) + return total + + +def search_fed_by_id(fed_id): + get = FEDERATION_BYFEDID.get(fed_id) + if get is None: + return False + return get + result = False + for Q in curr: + if Q.fed_id == fed_id: + result = Q.fed_id + + return result + + +def user_feds_report(user_id: int) -> bool: + user_setting = FEDERATION_NOTIFICATION.get(str(user_id)) + if user_setting is None: + user_setting = True + return user_setting + + +def set_feds_setting(user_id: int, setting: bool): + with FEDS_SETTINGS_LOCK: + global FEDERATION_NOTIFICATION + user_setting = SESSION.query(FedsUserSettings).get(user_id) + if not user_setting: + user_setting = FedsUserSettings(user_id) + + user_setting.should_report = setting + FEDERATION_NOTIFICATION[str(user_id)] = setting + SESSION.add(user_setting) + SESSION.commit() + + +def get_fed_log(fed_id): + fed_setting = FEDERATION_BYFEDID.get(str(fed_id)) + if fed_setting is None: + fed_setting = False + return fed_setting + if fed_setting.get("flog") is None: + return False + if fed_setting.get("flog"): + return fed_setting.get("flog") + return False + + +def set_fed_log(fed_id, chat_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_members = getfed["fusers"] + fed_rules = getfed["frules"] + fed_log = str(chat_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["flog"] = fed_log + FEDERATION_BYFEDID[str(fed_id)]["flog"] = fed_log + FEDERATION_BYNAME[fed_name]["flog"] = fed_log + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str(fed_members), + ) + SESSION.merge(fed) + SESSION.commit() + print(fed_log) + return True + + +def subs_fed(fed_id, my_fed): + check = get_spec_subs(fed_id, my_fed) + if check: + return False + with FEDS_SUBSCRIBER_LOCK: + subsfed = FedSubs(fed_id, my_fed) + + SESSION.merge(subsfed) # merge to avoid duplicate key issues + SESSION.commit() + global FEDS_SUBSCRIBER, MYFEDS_SUBSCRIBER + # Temporary Data For Subbed Feds + if FEDS_SUBSCRIBER.get(fed_id, set()) == set(): + FEDS_SUBSCRIBER[fed_id] = {my_fed} + else: + FEDS_SUBSCRIBER.get(fed_id, set()).add(my_fed) + # Temporary data for Fed Subs + if MYFEDS_SUBSCRIBER.get(my_fed, set()) == set(): + MYFEDS_SUBSCRIBER[my_fed] = {fed_id} + else: + MYFEDS_SUBSCRIBER.get(my_fed, set()).add(fed_id) + return True + + +def unsubs_fed(fed_id, my_fed): + with FEDS_SUBSCRIBER_LOCK: + getsubs = SESSION.query(FedSubs).get((fed_id, my_fed)) + if getsubs: + if my_fed in FEDS_SUBSCRIBER.get(fed_id, set()): # sanity check + FEDS_SUBSCRIBER.get(fed_id, set()).remove(my_fed) + if fed_id in MYFEDS_SUBSCRIBER.get(my_fed, set()): # sanity check + MYFEDS_SUBSCRIBER.get(my_fed, set()).remove(fed_id) + + SESSION.delete(getsubs) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_all_subs(fed_id): + return FEDS_SUBSCRIBER.get(fed_id, set()) + + +def get_spec_subs(fed_id, fed_target): + if FEDS_SUBSCRIBER.get(fed_id, set()) == set(): + return {} + return FEDS_SUBSCRIBER.get(fed_id, fed_target) + + +def get_mysubs(my_fed): + return list(MYFEDS_SUBSCRIBER.get(my_fed)) + + +def get_subscriber(fed_id): + return FEDS_SUBSCRIBER.get(fed_id, set()) + + +def __load_all_feds(): + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + try: + feds = SESSION.query(Federations).all() + for x in feds: # remove tuple by ( ,) + # Fed by Owner + check = FEDERATION_BYOWNER.get(x.owner_id) + if check is None: + FEDERATION_BYOWNER[x.owner_id] = [] + FEDERATION_BYOWNER[str(x.owner_id)] = { + "fid": str(x.fed_id), + "fname": x.fed_name, + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + # Fed By FedId + check = FEDERATION_BYFEDID.get(x.fed_id) + if check is None: + FEDERATION_BYFEDID[x.fed_id] = [] + FEDERATION_BYFEDID[str(x.fed_id)] = { + "owner": str(x.owner_id), + "fname": x.fed_name, + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + # Fed By Name + check = FEDERATION_BYNAME.get(x.fed_name) + if check is None: + FEDERATION_BYNAME[x.fed_name] = [] + FEDERATION_BYNAME[x.fed_name] = { + "fid": str(x.fed_id), + "owner": str(x.owner_id), + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + finally: + SESSION.close() + + +def __load_all_feds_chats(): + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + try: + qall = SESSION.query(ChatF).all() + FEDERATION_CHATS = {} + FEDERATION_CHATS_BYID = {} + for x in qall: + # Federation Chats + check = FEDERATION_CHATS.get(x.chat_id) + if check is None: + FEDERATION_CHATS[x.chat_id] = {} + FEDERATION_CHATS[x.chat_id] = {"chat_name": x.chat_name, "fid": x.fed_id} + # Federation Chats By ID + check = FEDERATION_CHATS_BYID.get(x.fed_id) + if check is None: + FEDERATION_CHATS_BYID[x.fed_id] = [] + FEDERATION_CHATS_BYID[x.fed_id].append(x.chat_id) + finally: + SESSION.close() + + +def __load_all_feds_banned(): + global FEDERATION_BANNED_USERID, FEDERATION_BANNED_FULL + try: + FEDERATION_BANNED_USERID = {} + FEDERATION_BANNED_FULL = {} + qall = SESSION.query(BansF).all() + for x in qall: + check = FEDERATION_BANNED_USERID.get(x.fed_id) + if check is None: + FEDERATION_BANNED_USERID[x.fed_id] = [] + if int(x.user_id) not in FEDERATION_BANNED_USERID[x.fed_id]: + FEDERATION_BANNED_USERID[x.fed_id].append(int(x.user_id)) + check = FEDERATION_BANNED_FULL.get(x.fed_id) + if check is None: + FEDERATION_BANNED_FULL[x.fed_id] = {} + FEDERATION_BANNED_FULL[x.fed_id][x.user_id] = { + "first_name": x.first_name, + "last_name": x.last_name, + "user_name": x.user_name, + "reason": x.reason, + "time": x.time, + } + finally: + SESSION.close() + + +def __load_all_feds_settings(): + global FEDERATION_NOTIFICATION + try: + getuser = SESSION.query(FedsUserSettings).all() + for x in getuser: + FEDERATION_NOTIFICATION[str(x.user_id)] = x.should_report + finally: + SESSION.close() + + +def __load_feds_subscriber(): + global FEDS_SUBSCRIBER + global MYFEDS_SUBSCRIBER + try: + feds = SESSION.query(FedSubs.fed_id).distinct().all() + for (fed_id,) in feds: # remove tuple by ( ,) + FEDS_SUBSCRIBER[fed_id] = [] + MYFEDS_SUBSCRIBER[fed_id] = [] + + all_fedsubs = SESSION.query(FedSubs).all() + for x in all_fedsubs: + FEDS_SUBSCRIBER[x.fed_id] += [x.fed_subs] + try: + MYFEDS_SUBSCRIBER[x.fed_subs] += [x.fed_id] + except KeyError: + getsubs = SESSION.query(FedSubs).get((x.fed_id, x.fed_subs)) + if getsubs: + SESSION.delete(getsubs) + SESSION.commit() + + FEDS_SUBSCRIBER = {x: set(y) for x, y in FEDS_SUBSCRIBER.items()} + MYFEDS_SUBSCRIBER = {x: set(y) for x, y in MYFEDS_SUBSCRIBER.items()} + + finally: + SESSION.close() + + +__load_all_feds() +__load_all_feds_chats() +__load_all_feds_banned() +__load_all_feds_settings() +__load_feds_subscriber() diff --git a/QueenNoxi/modules/sql/forceSubscribe_sql.py b/QueenNoxi/modules/sql/forceSubscribe_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..2e3160db742b89fd239c1cf44a94744211172e6e --- /dev/null +++ b/QueenNoxi/modules/sql/forceSubscribe_sql.py @@ -0,0 +1,47 @@ +from sqlalchemy import Column, Numeric, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class forceSubscribe(BASE): + __tablename__ = "forceSubscribe" + chat_id = Column(Numeric, primary_key=True) + channel = Column(String) + + def __init__(self, chat_id, channel): + self.chat_id = chat_id + self.channel = channel + + +forceSubscribe.__table__.create(BASE.metadata.bind, checkfirst=True) + + +def fs_settings(chat_id): + try: + return ( + SESSION.query(forceSubscribe) + .filter(forceSubscribe.chat_id == chat_id) + .one() + ) + except: + return None + finally: + SESSION.close() + + +def add_channel(chat_id, channel): + adder = SESSION.query(forceSubscribe).get(chat_id) + if adder: + adder.channel = channel + else: + adder = forceSubscribe(chat_id, channel) + SESSION.add(adder) + SESSION.commit() + + +def disapprove(chat_id): + rem = SESSION.query(forceSubscribe).get(chat_id) + if rem: + SESSION.delete(rem) + SESSION.commit() + diff --git a/QueenNoxi/modules/sql/global_bans_sql.py b/QueenNoxi/modules/sql/global_bans_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..8de6c0a6734100e370600b551d6afb63301590fc --- /dev/null +++ b/QueenNoxi/modules/sql/global_bans_sql.py @@ -0,0 +1,167 @@ +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class GloballyBannedUsers(BASE): + __tablename__ = "gbans" + user_id = Column(BigInteger, primary_key=True) + name = Column(UnicodeText, nullable=False) + reason = Column(UnicodeText) + + def __init__(self, user_id, name, reason=None): + self.user_id = user_id + self.name = name + self.reason = reason + + def __repr__(self): + return "<GBanned User {} ({})>".format(self.name, self.user_id) + + def to_dict(self): + return {"user_id": self.user_id, "name": self.name, "reason": self.reason} + + +class GbanSettings(BASE): + __tablename__ = "gban_settings" + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=True, nullable=False) + + def __init__(self, chat_id, enabled): + self.chat_id = str(chat_id) + self.setting = enabled + + def __repr__(self): + return "<Gban setting {} ({})>".format(self.chat_id, self.setting) + + +GloballyBannedUsers.__table__.create(BASE.metadata.bind, checkfirst=True) +GbanSettings.__table__.create(BASE.metadata.bind, checkfirst=True) + +GBANNED_USERS_LOCK = threading.RLock() +GBAN_SETTING_LOCK = threading.RLock() +GBANNED_LIST = set() +GBANSTAT_LIST = set() + + +def gban_user(user_id, name, reason=None): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if not user: + user = GloballyBannedUsers(user_id, name, reason) + else: + user.name = name + user.reason = reason + + SESSION.merge(user) + SESSION.commit() + __load_gbanned_userid_list() + + +def update_gban_reason(user_id, name, reason=None): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if not user: + return None + old_reason = user.reason + user.name = name + user.reason = reason + + SESSION.merge(user) + SESSION.commit() + return old_reason + + +def ungban_user(user_id): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if user: + SESSION.delete(user) + + SESSION.commit() + __load_gbanned_userid_list() + + +def is_user_gbanned(user_id): + return user_id in GBANNED_LIST + + +def get_gbanned_user(user_id): + try: + return SESSION.query(GloballyBannedUsers).get(user_id) + finally: + SESSION.close() + + +def get_gban_list(): + try: + return [x.to_dict() for x in SESSION.query(GloballyBannedUsers).all()] + finally: + SESSION.close() + + +def enable_gbans(chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(chat_id)) + if not chat: + chat = GbanSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + if str(chat_id) in GBANSTAT_LIST: + GBANSTAT_LIST.remove(str(chat_id)) + + +def disable_gbans(chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(chat_id)) + if not chat: + chat = GbanSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + GBANSTAT_LIST.add(str(chat_id)) + + +def does_chat_gban(chat_id): + return str(chat_id) not in GBANSTAT_LIST + + +def num_gbanned_users(): + return len(GBANNED_LIST) + + +def __load_gbanned_userid_list(): + global GBANNED_LIST + try: + GBANNED_LIST = {x.user_id for x in SESSION.query(GloballyBannedUsers).all()} + finally: + SESSION.close() + + +def __load_gban_stat_list(): + global GBANSTAT_LIST + try: + GBANSTAT_LIST = { + x.chat_id for x in SESSION.query(GbanSettings).all() if not x.setting + } + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() + + +# Create in memory userid to avoid disk access +__load_gbanned_userid_list() +__load_gban_stat_list() diff --git a/QueenNoxi/modules/sql/locks_sql.py b/QueenNoxi/modules/sql/locks_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..d4b4a561e31f79aa55855379472966d9ca45b61b --- /dev/null +++ b/QueenNoxi/modules/sql/locks_sql.py @@ -0,0 +1,268 @@ +# New chat added -> setup permissions +import threading + +from sqlalchemy import Boolean, Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Permissions(BASE): + __tablename__ = "permissions" + chat_id = Column(String(14), primary_key=True) + # Booleans are for "is this locked", _NOT_ "is this allowed" + audio = Column(Boolean, default=False) + voice = Column(Boolean, default=False) + contact = Column(Boolean, default=False) + video = Column(Boolean, default=False) + document = Column(Boolean, default=False) + photo = Column(Boolean, default=False) + sticker = Column(Boolean, default=False) + gif = Column(Boolean, default=False) + url = Column(Boolean, default=False) + bots = Column(Boolean, default=False) + forward = Column(Boolean, default=False) + game = Column(Boolean, default=False) + location = Column(Boolean, default=False) + rtl = Column(Boolean, default=False) + button = Column(Boolean, default=False) + egame = Column(Boolean, default=False) + inline = Column(Boolean, default=False) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + self.audio = False + self.voice = False + self.contact = False + self.video = False + self.document = False + self.photo = False + self.sticker = False + self.gif = False + self.url = False + self.bots = False + self.forward = False + self.game = False + self.location = False + self.rtl = False + self.button = False + self.egame = False + self.inline = False + + def __repr__(self): + return "<Permissions for %s>" % self.chat_id + + +class Restrictions(BASE): + __tablename__ = "restrictions" + chat_id = Column(String(14), primary_key=True) + # Booleans are for "is this restricted", _NOT_ "is this allowed" + messages = Column(Boolean, default=False) + media = Column(Boolean, default=False) + other = Column(Boolean, default=False) + preview = Column(Boolean, default=False) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + self.messages = False + self.media = False + self.other = False + self.preview = False + + def __repr__(self): + return "<Restrictions for %s>" % self.chat_id + + +# For those who faced database error, Just uncomment the +# line below and run bot for 1 time & remove that line! + +Permissions.__table__.create(BASE.metadata.bind, checkfirst=True) +# Permissions.__table__.drop() +Restrictions.__table__.create(BASE.metadata.bind, checkfirst=True) + +PERM_LOCK = threading.RLock() +RESTR_LOCK = threading.RLock() + + +def init_permissions(chat_id, reset=False): + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + if reset: + SESSION.delete(curr_perm) + SESSION.flush() + perm = Permissions(str(chat_id)) + SESSION.add(perm) + SESSION.commit() + return perm + + +def init_restrictions(chat_id, reset=False): + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + if reset: + SESSION.delete(curr_restr) + SESSION.flush() + restr = Restrictions(str(chat_id)) + SESSION.add(restr) + SESSION.commit() + return restr + + +def update_lock(chat_id, lock_type, locked): + with PERM_LOCK: + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + if not curr_perm: + curr_perm = init_permissions(chat_id) + + if lock_type == "audio": + curr_perm.audio = locked + elif lock_type == "voice": + curr_perm.voice = locked + elif lock_type == "contact": + curr_perm.contact = locked + elif lock_type == "video": + curr_perm.video = locked + elif lock_type == "document": + curr_perm.document = locked + elif lock_type == "photo": + curr_perm.photo = locked + elif lock_type == "sticker": + curr_perm.sticker = locked + elif lock_type == "gif": + curr_perm.gif = locked + elif lock_type == "url": + curr_perm.url = locked + elif lock_type == "bots": + curr_perm.bots = locked + elif lock_type == "forward": + curr_perm.forward = locked + elif lock_type == "game": + curr_perm.game = locked + elif lock_type == "location": + curr_perm.location = locked + elif lock_type == "rtl": + curr_perm.rtl = locked + elif lock_type == "button": + curr_perm.button = locked + elif lock_type == "egame": + curr_perm.egame = locked + elif lock_type == "inline": + curr_perm.inline = locked + + SESSION.add(curr_perm) + SESSION.commit() + + +def update_restriction(chat_id, restr_type, locked): + with RESTR_LOCK: + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + if not curr_restr: + curr_restr = init_restrictions(chat_id) + + if restr_type == "messages": + curr_restr.messages = locked + elif restr_type == "media": + curr_restr.media = locked + elif restr_type == "other": + curr_restr.other = locked + elif restr_type == "previews": + curr_restr.preview = locked + elif restr_type == "all": + curr_restr.messages = locked + curr_restr.media = locked + curr_restr.other = locked + curr_restr.preview = locked + SESSION.add(curr_restr) + SESSION.commit() + + +def is_locked(chat_id, lock_type): + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + SESSION.close() + + if not curr_perm: + return False + + elif lock_type == "sticker": + return curr_perm.sticker + elif lock_type == "photo": + return curr_perm.photo + elif lock_type == "audio": + return curr_perm.audio + elif lock_type == "voice": + return curr_perm.voice + elif lock_type == "contact": + return curr_perm.contact + elif lock_type == "video": + return curr_perm.video + elif lock_type == "document": + return curr_perm.document + elif lock_type == "gif": + return curr_perm.gif + elif lock_type == "url": + return curr_perm.url + elif lock_type == "bots": + return curr_perm.bots + elif lock_type == "forward": + return curr_perm.forward + elif lock_type == "game": + return curr_perm.game + elif lock_type == "location": + return curr_perm.location + elif lock_type == "rtl": + return curr_perm.rtl + elif lock_type == "button": + return curr_perm.button + elif lock_type == "egame": + return curr_perm.egame + elif lock_type == "inline": + return curr_perm.inline + + +def is_restr_locked(chat_id, lock_type): + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + SESSION.close() + + if not curr_restr: + return False + + if lock_type == "messages": + return curr_restr.messages + elif lock_type == "media": + return curr_restr.media + elif lock_type == "other": + return curr_restr.other + elif lock_type == "previews": + return curr_restr.preview + elif lock_type == "all": + return ( + curr_restr.messages + and curr_restr.media + and curr_restr.other + and curr_restr.preview + ) + + +def get_locks(chat_id): + try: + return SESSION.query(Permissions).get(str(chat_id)) + finally: + SESSION.close() + + +def get_restr(chat_id): + try: + return SESSION.query(Restrictions).get(str(chat_id)) + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with PERM_LOCK: + perms = SESSION.query(Permissions).get(str(old_chat_id)) + if perms: + perms.chat_id = str(new_chat_id) + SESSION.commit() + + with RESTR_LOCK: + rest = SESSION.query(Restrictions).get(str(old_chat_id)) + if rest: + rest.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/QueenNoxi/modules/sql/log_channel_sql.py b/QueenNoxi/modules/sql/log_channel_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..ed76ff7755ea28da536164b0d96dd428d1747f31 --- /dev/null +++ b/QueenNoxi/modules/sql/log_channel_sql.py @@ -0,0 +1,83 @@ +import threading + +from sqlalchemy import Column, String, distinct, func + +from QueenNoxi.modules.sql import BASE, SESSION + + +class GroupLogs(BASE): + __tablename__ = "log_channels" + chat_id = Column(String(14), primary_key=True) + log_channel = Column(String(14), nullable=False) + + def __init__(self, chat_id, log_channel): + self.chat_id = str(chat_id) + self.log_channel = str(log_channel) + + +GroupLogs.__table__.create(BASE.metadata.bind, checkfirst=True) + +LOGS_INSERTION_LOCK = threading.RLock() + +CHANNELS = {} + + +def set_chat_log_channel(chat_id, log_channel): + with LOGS_INSERTION_LOCK: + res = SESSION.query(GroupLogs).get(str(chat_id)) + if res: + res.log_channel = log_channel + else: + res = GroupLogs(chat_id, log_channel) + SESSION.add(res) + + CHANNELS[str(chat_id)] = log_channel + SESSION.commit() + + +def get_chat_log_channel(chat_id): + return CHANNELS.get(str(chat_id)) + + +def stop_chat_logging(chat_id): + with LOGS_INSERTION_LOCK: + res = SESSION.query(GroupLogs).get(str(chat_id)) + if res: + if str(chat_id) in CHANNELS: + del CHANNELS[str(chat_id)] + + log_channel = res.log_channel + SESSION.delete(res) + SESSION.commit() + return log_channel + + +def num_logchannels(): + try: + return SESSION.query(func.count(distinct(GroupLogs.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with LOGS_INSERTION_LOCK: + chat = SESSION.query(GroupLogs).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.add(chat) + if str(old_chat_id) in CHANNELS: + CHANNELS[str(new_chat_id)] = CHANNELS.get(str(old_chat_id)) + + SESSION.commit() + + +def __load_log_channels(): + global CHANNELS + try: + all_chats = SESSION.query(GroupLogs).all() + CHANNELS = {chat.chat_id: chat.log_channel for chat in all_chats} + finally: + SESSION.close() + + +__load_log_channels() diff --git a/QueenNoxi/modules/sql/night_mode_sql.py b/QueenNoxi/modules/sql/night_mode_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..823cfe7a13f0878467c21e7df6b7c9d813d8a86a --- /dev/null +++ b/QueenNoxi/modules/sql/night_mode_sql.py @@ -0,0 +1,42 @@ +from sqlalchemy import Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Nightmode(BASE): + __tablename__ = "nightmode" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nightmode.__table__.create(BASE.metadata.bind, checkfirst=True) + + +def add_nightmode(chat_id: str): + nightmoddy = Nightmode(str(chat_id)) + SESSION.add(nightmoddy) + SESSION.commit() + + +def rmnightmode(chat_id: str): + rmnightmoddy = SESSION.query(Nightmode).get(str(chat_id)) + if rmnightmoddy: + SESSION.delete(rmnightmoddy) + SESSION.commit() + + +def get_all_chat_id(): + stark = SESSION.query(Nightmode).all() + SESSION.close() + return stark + + +def is_nightmode_indb(chat_id: str): + try: + s__ = SESSION.query(Nightmode).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/QueenNoxi/modules/sql/notes_sql.py b/QueenNoxi/modules/sql/notes_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..4a0058c40f115c0330f409ad55bf31802db9f640 --- /dev/null +++ b/QueenNoxi/modules/sql/notes_sql.py @@ -0,0 +1,182 @@ +# Note: chat_id's are stored as strings because the int is too large to be stored in a PSQL database. +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func + +from QueenNoxi.modules.helper_funcs.msg_types import Types +from QueenNoxi.modules.sql import BASE, SESSION + + +class Notes(BASE): + __tablename__ = "notes" + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, primary_key=True) + value = Column(UnicodeText, nullable=False) + file = Column(UnicodeText) + is_reply = Column(Boolean, default=False) + has_buttons = Column(Boolean, default=False) + msgtype = Column(BigInteger, default=Types.BUTTON_TEXT.value) + + def __init__(self, chat_id, name, value, msgtype, file=None): + self.chat_id = str(chat_id) # ensure string + self.name = name + self.value = value + self.msgtype = msgtype + self.file = file + + def __repr__(self): + return "<Note %s>" % self.name + + +class Buttons(BASE): + __tablename__ = "note_urls" + id = Column(BigInteger, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + note_name = Column(UnicodeText, primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, note_name, name, url, same_line=False): + self.chat_id = str(chat_id) + self.note_name = note_name + self.name = name + self.url = url + self.same_line = same_line + + +Notes.__table__.create(BASE.metadata.bind, checkfirst=True) +Buttons.__table__.create(BASE.metadata.bind, checkfirst=True) + +NOTES_INSERTION_LOCK = threading.RLock() +BUTTONS_INSERTION_LOCK = threading.RLock() + + +def add_note_to_db(chat_id, note_name, note_data, msgtype, buttons=None, file=None): + if not buttons: + buttons = [] + + with NOTES_INSERTION_LOCK: + prev = SESSION.query(Notes).get((str(chat_id), note_name)) + if prev: + with BUTTONS_INSERTION_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter( + Buttons.chat_id == str(chat_id), Buttons.note_name == note_name + ) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + note = Notes( + str(chat_id), note_name, note_data or "", msgtype=msgtype.value, file=file + ) + SESSION.add(note) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, note_name, b_name, url, same_line) + + +def get_note(chat_id, note_name): + try: + return ( + SESSION.query(Notes) + .filter(func.lower(Notes.name) == note_name, Notes.chat_id == str(chat_id)) + .first() + ) + finally: + SESSION.close() + + +def rm_note(chat_id, note_name): + with NOTES_INSERTION_LOCK: + note = ( + SESSION.query(Notes) + .filter(func.lower(Notes.name) == note_name, Notes.chat_id == str(chat_id)) + .first() + ) + if note: + with BUTTONS_INSERTION_LOCK: + buttons = ( + SESSION.query(Buttons) + .filter( + Buttons.chat_id == str(chat_id), Buttons.note_name == note_name + ) + .all() + ) + for btn in buttons: + SESSION.delete(btn) + + SESSION.delete(note) + SESSION.commit() + return True + + else: + SESSION.close() + return False + + +def get_all_chat_notes(chat_id): + try: + return ( + SESSION.query(Notes) + .filter(Notes.chat_id == str(chat_id)) + .order_by(Notes.name.asc()) + .all() + ) + finally: + SESSION.close() + + +def add_note_button_to_db(chat_id, note_name, b_name, url, same_line): + with BUTTONS_INSERTION_LOCK: + button = Buttons(chat_id, note_name, b_name, url, same_line) + SESSION.add(button) + SESSION.commit() + + +def get_buttons(chat_id, note_name): + try: + return ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.note_name == note_name) + .order_by(Buttons.id) + .all() + ) + finally: + SESSION.close() + + +def num_notes(): + try: + return SESSION.query(Notes).count() + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Notes.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with NOTES_INSERTION_LOCK: + chat_notes = ( + SESSION.query(Notes).filter(Notes.chat_id == str(old_chat_id)).all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + + with BUTTONS_INSERTION_LOCK: + chat_buttons = ( + SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + SESSION.commit() diff --git a/QueenNoxi/modules/sql/nsfw_sql.py b/QueenNoxi/modules/sql/nsfw_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..d9587d23ced7cba7f9f2e03a0b25835ecdbb12f8 --- /dev/null +++ b/QueenNoxi/modules/sql/nsfw_sql.py @@ -0,0 +1,52 @@ +import threading + +from sqlalchemy import Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class NSFWChats(BASE): + __tablename__ = "nsfw_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +NSFWChats.__table__.create(BASE.metadata.bind, checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_nsfw(chat_id): + try: + chat = SESSION.query(NSFWChats).get(str(chat_id)) + if chat: + return True + else: + return False + finally: + SESSION.close() + + +def set_nsfw(chat_id): + with INSERTION_LOCK: + nsfwchat = SESSION.query(NSFWChats).get(str(chat_id)) + if not nsfwchat: + nsfwchat = NSFWChats(str(chat_id)) + SESSION.add(nsfwchat) + SESSION.commit() + + +def rem_nsfw(chat_id): + with INSERTION_LOCK: + nsfwchat = SESSION.query(NSFWChats).get(str(chat_id)) + if nsfwchat: + SESSION.delete(nsfwchat) + SESSION.commit() + + +def get_all_nsfw_chats(): + try: + return SESSION.query(NSFWChats.chat_id).all() + finally: + SESSION.close() diff --git a/QueenNoxi/modules/sql/nsfw_watch_sql.py b/QueenNoxi/modules/sql/nsfw_watch_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..6139033b05428728517ded6a2c8936b2f2b788c6 --- /dev/null +++ b/QueenNoxi/modules/sql/nsfw_watch_sql.py @@ -0,0 +1,42 @@ +from sqlalchemy import Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Nsfwatch(BASE): + __tablename__ = "nsfwatch" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nsfwatch.__table__.create(BASE.metadata.bind, checkfirst=True) + + +def add_nsfwatch(chat_id: str): + nsfws = Nsfwatch(str(chat_id)) + SESSION.add(nsfws) + SESSION.commit() + + +def rmnsfwatch(chat_id: str): + nsfwm = SESSION.query(Nsfwatch).get(str(chat_id)) + if nsfwm: + SESSION.delete(nsfwm) + SESSION.commit() + + +def get_all_nsfw_enabled_chat(): + stark = SESSION.query(Nsfwatch).all() + SESSION.close() + return stark + + +def is_nsfwatch_indb(chat_id: str): + try: + s__ = SESSION.query(Nsfwatch).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/QueenNoxi/modules/sql/reporting_sql.py b/QueenNoxi/modules/sql/reporting_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..82114910a69db18ced61de1b2c516d1acda57e91 --- /dev/null +++ b/QueenNoxi/modules/sql/reporting_sql.py @@ -0,0 +1,91 @@ +import threading +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, String + +from QueenNoxi.modules.sql import BASE, SESSION + + +class ReportingUserSettings(BASE): + __tablename__ = "user_report_settings" + user_id = Column(BigInteger, primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, user_id): + self.user_id = user_id + + def __repr__(self): + return "<User report settings ({})>".format(self.user_id) + + +class ReportingChatSettings(BASE): + __tablename__ = "chat_report_settings" + chat_id = Column(String(14), primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) + + def __repr__(self): + return "<Chat report settings ({})>".format(self.chat_id) + + +ReportingUserSettings.__table__.create(BASE.metadata.bind, checkfirst=True) +ReportingChatSettings.__table__.create(BASE.metadata.bind, checkfirst=True) + +CHAT_LOCK = threading.RLock() +USER_LOCK = threading.RLock() + + +def chat_should_report(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) + if chat_setting: + return chat_setting.should_report + return False + finally: + SESSION.close() + + +def user_should_report(user_id: int) -> bool: + try: + user_setting = SESSION.query(ReportingUserSettings).get(user_id) + if user_setting: + return user_setting.should_report + return True + finally: + SESSION.close() + + +def set_chat_setting(chat_id: Union[int, str], setting: bool): + with CHAT_LOCK: + chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) + if not chat_setting: + chat_setting = ReportingChatSettings(chat_id) + + chat_setting.should_report = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def set_user_setting(user_id: int, setting: bool): + with USER_LOCK: + user_setting = SESSION.query(ReportingUserSettings).get(user_id) + if not user_setting: + user_setting = ReportingUserSettings(user_id) + + user_setting.should_report = setting + SESSION.add(user_setting) + SESSION.commit() + + +def migrate_chat(old_chat_id, new_chat_id): + with CHAT_LOCK: + chat_notes = ( + SESSION.query(ReportingChatSettings) + .filter(ReportingChatSettings.chat_id == str(old_chat_id)) + .all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/QueenNoxi/modules/sql/rss_sql.py b/QueenNoxi/modules/sql/rss_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..833e95c9748beddcb4c0c4af8b49773a01dbb48d --- /dev/null +++ b/QueenNoxi/modules/sql/rss_sql.py @@ -0,0 +1,81 @@ +import threading + +from sqlalchemy import BigInteger, Column, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class RSS(BASE): + __tablename__ = "rss_feed" + id = Column(BigInteger, primary_key=True) + chat_id = Column(UnicodeText, nullable=False) + feed_link = Column(UnicodeText) + old_entry_link = Column(UnicodeText) + + def __init__(self, chat_id, feed_link, old_entry_link): + self.chat_id = chat_id + self.feed_link = feed_link + self.old_entry_link = old_entry_link + + def __repr__(self): + return "<RSS for chatID {} at feed_link {} with old_entry_link {}>".format( + self.chat_id, self.feed_link, self.old_entry_link + ) + + +RSS.__table__.create(BASE.metadata.bind, checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def check_url_availability(tg_chat_id, tg_feed_link): + try: + return ( + SESSION.query(RSS) + .filter(RSS.feed_link == tg_feed_link, RSS.chat_id == tg_chat_id) + .all() + ) + finally: + SESSION.close() + + +def add_url(tg_chat_id, tg_feed_link, tg_old_entry_link): + with INSERTION_LOCK: + action = RSS(tg_chat_id, tg_feed_link, tg_old_entry_link) + + SESSION.add(action) + SESSION.commit() + + +def remove_url(tg_chat_id, tg_feed_link): + with INSERTION_LOCK: + # this loops to delete any possible duplicates for the same TG User ID, TG Chat ID and link + for row in check_url_availability(tg_chat_id, tg_feed_link): + # add the action to the DB query + SESSION.delete(row) + + SESSION.commit() + + +def get_urls(tg_chat_id): + try: + return SESSION.query(RSS).filter(RSS.chat_id == tg_chat_id).all() + finally: + SESSION.close() + + +def get_all(): + try: + return SESSION.query(RSS).all() + finally: + SESSION.close() + + +def update_url(row_id, new_entry_links): + with INSERTION_LOCK: + row = SESSION.query(RSS).get(row_id) + + # set the new old_entry_link with the latest update from the RSS Feed + row.old_entry_link = new_entry_links[0] + + # commit the changes to the DB + SESSION.commit() diff --git a/QueenNoxi/modules/sql/rules_sql.py b/QueenNoxi/modules/sql/rules_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..30d4c6912bdbb30cc9f776731dfbc89c4a5e32e1 --- /dev/null +++ b/QueenNoxi/modules/sql/rules_sql.py @@ -0,0 +1,58 @@ +import threading + +from sqlalchemy import Column, String, UnicodeText, distinct, func + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Rules(BASE): + __tablename__ = "rules" + chat_id = Column(String(14), primary_key=True) + rules = Column(UnicodeText, default="") + + def __init__(self, chat_id): + self.chat_id = chat_id + + def __repr__(self): + return "<Chat {} rules: {}>".format(self.chat_id, self.rules) + + +Rules.__table__.create(BASE.metadata.bind, checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def set_rules(chat_id, rules_text): + with INSERTION_LOCK: + rules = SESSION.query(Rules).get(str(chat_id)) + if not rules: + rules = Rules(str(chat_id)) + rules.rules = rules_text + + SESSION.add(rules) + SESSION.commit() + + +def get_rules(chat_id): + rules = SESSION.query(Rules).get(str(chat_id)) + ret = "" + if rules: + ret = rules.rules + + SESSION.close() + return ret + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Rules.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Rules).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/QueenNoxi/modules/sql/session_sql.py b/QueenNoxi/modules/sql/session_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..8319b8b066bffd123debc9f0e674e801b66a9967 --- /dev/null +++ b/QueenNoxi/modules/sql/session_sql.py @@ -0,0 +1,32 @@ +import threading +from sqlalchemy import Column, String, Text +from QueenNoxi.modules.sql import BASE, SESSION + +class SessionStore(BASE): + __tablename__ = "sessions" + bot_id = Column(String(50), primary_key=True) + session_string = Column(Text) + + def __init__(self, bot_id, session_string): + self.bot_id = str(bot_id) + self.session_string = session_string + +SessionStore.__table__.create(BASE.metadata.bind, checkfirst=True) +SESSION_LOCK = threading.RLock() + +def get_session(bot_id): + try: + res = SESSION.query(SessionStore).get(str(bot_id)) + return res.session_string if res else None + finally: + SESSION.close() + +def save_session(bot_id, session_string): + with SESSION_LOCK: + curr = SESSION.query(SessionStore).get(str(bot_id)) + if curr: + curr.session_string = session_string + else: + curr = SessionStore(bot_id, session_string) + SESSION.add(curr) + SESSION.commit() diff --git a/QueenNoxi/modules/sql/userinfo_sql.py b/QueenNoxi/modules/sql/userinfo_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..727a5cd3c4c6afe9bed218ffddff8bcf0bfa73b1 --- /dev/null +++ b/QueenNoxi/modules/sql/userinfo_sql.py @@ -0,0 +1,76 @@ +import threading + +from sqlalchemy import BigInteger, Column, UnicodeText + +from QueenNoxi.modules.sql import BASE, SESSION + + +class UserInfo(BASE): + __tablename__ = "userinfo" + user_id = Column(BigInteger, primary_key=True) + info = Column(UnicodeText) + + def __init__(self, user_id, info): + self.user_id = user_id + self.info = info + + def __repr__(self): + return "<User info %d>" % self.user_id + + +class UserBio(BASE): + __tablename__ = "userbio" + user_id = Column(BigInteger, primary_key=True) + bio = Column(UnicodeText) + + def __init__(self, user_id, bio): + self.user_id = user_id + self.bio = bio + + def __repr__(self): + return "<User info %d>" % self.user_id + + +UserInfo.__table__.create(BASE.metadata.bind, checkfirst=True) +UserBio.__table__.create(BASE.metadata.bind, checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def get_user_me_info(user_id): + userinfo = SESSION.query(UserInfo).get(user_id) + SESSION.close() + if userinfo: + return userinfo.info + return None + + +def set_user_me_info(user_id, info): + with INSERTION_LOCK: + userinfo = SESSION.query(UserInfo).get(user_id) + if userinfo: + userinfo.info = info + else: + userinfo = UserInfo(user_id, info) + SESSION.add(userinfo) + SESSION.commit() + + +def get_user_bio(user_id): + userbio = SESSION.query(UserBio).get(user_id) + SESSION.close() + if userbio: + return userbio.bio + return None + + +def set_user_bio(user_id, bio): + with INSERTION_LOCK: + userbio = SESSION.query(UserBio).get(user_id) + if userbio: + userbio.bio = bio + else: + userbio = UserBio(user_id, bio) + + SESSION.add(userbio) + SESSION.commit() diff --git a/QueenNoxi/modules/sql/users_sql.py b/QueenNoxi/modules/sql/users_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..54d9ef3c4bcc9a860890fa47d2a85082e0d39a28 --- /dev/null +++ b/QueenNoxi/modules/sql/users_sql.py @@ -0,0 +1,234 @@ +import threading + +from sqlalchemy import ( + BigInteger, + Column, + ForeignKey, + String, + UnicodeText, + UniqueConstraint, + func, +) + +from QueenNoxi import BOT_ID, BOT_USERNAME +from QueenNoxi.modules.sql import BASE, SESSION + + +class Users(BASE): + __tablename__ = "users" + user_id = Column(BigInteger, primary_key=True) + username = Column(UnicodeText) + + def __init__(self, user_id, username=None): + self.user_id = user_id + self.username = username + + def __repr__(self): + return "<User {} ({})>".format(self.username, self.user_id) + + +class Chats(BASE): + __tablename__ = "chats" + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText, nullable=False) + + def __init__(self, chat_id, chat_name): + self.chat_id = str(chat_id) + self.chat_name = chat_name + + def __repr__(self): + return "<Chat {} ({})>".format(self.chat_name, self.chat_id) + + +class ChatMembers(BASE): + __tablename__ = "chat_members" + priv_chat_id = Column(BigInteger, primary_key=True) + # NOTE: Use dual primary key instead of private primary key? + chat = Column( + String(14), + ForeignKey("chats.chat_id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + user = Column( + BigInteger, + ForeignKey("users.user_id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + __table_args__ = (UniqueConstraint("chat", "user", name="_chat_members_uc"),) + + def __init__(self, chat, user): + self.chat = chat + self.user = user + + def __repr__(self): + return "<Chat user {} ({}) in chat {} ({})>".format( + self.user.username, + self.user.user_id, + self.chat.chat_name, + self.chat.chat_id, + ) + + +Users.__table__.create(BASE.metadata.bind, checkfirst=True) +Chats.__table__.create(BASE.metadata.bind, checkfirst=True) +ChatMembers.__table__.create(BASE.metadata.bind, checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def ensure_bot_in_db(): + with INSERTION_LOCK: + bot = Users(BOT_ID, BOT_USERNAME) + SESSION.merge(bot) + SESSION.commit() + + +def update_user(user_id, username, chat_id=None, chat_name=None): + with INSERTION_LOCK: + user = SESSION.query(Users).get(user_id) + if not user: + user = Users(user_id, username) + SESSION.add(user) + SESSION.flush() + else: + user.username = username + + if not chat_id or not chat_name: + SESSION.commit() + return + + chat = SESSION.query(Chats).get(str(chat_id)) + if not chat: + chat = Chats(str(chat_id), chat_name) + SESSION.add(chat) + SESSION.flush() + + else: + chat.chat_name = chat_name + + member = ( + SESSION.query(ChatMembers) + .filter(ChatMembers.chat == chat.chat_id, ChatMembers.user == user.user_id) + .first() + ) + if not member: + chat_member = ChatMembers(chat.chat_id, user.user_id) + SESSION.add(chat_member) + + SESSION.commit() + + +def get_userid_by_name(username): + try: + return ( + SESSION.query(Users) + .filter(func.lower(Users.username) == username.lower()) + .all() + ) + finally: + SESSION.close() + + +def get_name_by_userid(user_id): + try: + return SESSION.query(Users).get(Users.user_id == int(user_id)).first() + finally: + SESSION.close() + + +def get_chat_members(chat_id): + try: + return SESSION.query(ChatMembers).filter(ChatMembers.chat == str(chat_id)).all() + finally: + SESSION.close() + + +def get_all_chats(): + try: + return SESSION.query(Chats).all() + finally: + SESSION.close() + + +def get_all_users(): + try: + return SESSION.query(Users).all() + finally: + SESSION.close() + + +def get_user_num_chats(user_id): + try: + return ( + SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).count() + ) + finally: + SESSION.close() + + +def get_user_com_chats(user_id): + try: + chat_members = ( + SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).all() + ) + return [i.chat for i in chat_members] + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(Chats).count() + finally: + SESSION.close() + + +def num_users(): + try: + return SESSION.query(Users).count() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Chats).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.commit() + + chat_members = ( + SESSION.query(ChatMembers) + .filter(ChatMembers.chat == str(old_chat_id)) + .all() + ) + for member in chat_members: + member.chat = str(new_chat_id) + SESSION.commit() + + +ensure_bot_in_db() + + +def del_user(user_id): + with INSERTION_LOCK: + curr = SESSION.query(Users).get(user_id) + if curr: + SESSION.delete(curr) + SESSION.commit() + return True + + ChatMembers.query.filter(ChatMembers.user == user_id).delete() + SESSION.commit() + SESSION.close() + return False + + +def rem_chat(chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Chats).get(str(chat_id)) + if chat: + SESSION.delete(chat) + SESSION.commit() + else: + SESSION.close() diff --git a/QueenNoxi/modules/sql/warns_sql.py b/QueenNoxi/modules/sql/warns_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..d40dd48cb3dc5f84216efdf16e1ee703462ec583 --- /dev/null +++ b/QueenNoxi/modules/sql/warns_sql.py @@ -0,0 +1,315 @@ +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func +from sqlalchemy.dialects import postgresql + +from QueenNoxi.modules.sql import BASE, SESSION + + +class Warns(BASE): + __tablename__ = "warns" + + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + num_warns = Column(BigInteger, default=0) + reasons = Column(postgresql.ARRAY(UnicodeText)) + + def __init__(self, user_id, chat_id): + self.user_id = user_id + self.chat_id = str(chat_id) + self.num_warns = 0 + self.reasons = [] + + def __repr__(self): + return "<{} warns for {} in {} for reasons {}>".format( + self.num_warns, self.user_id, self.chat_id, self.reasons + ) + + +class WarnFilters(BASE): + __tablename__ = "warn_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + reply = Column(UnicodeText, nullable=False) + + def __init__(self, chat_id, keyword, reply): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.reply = reply + + def __repr__(self): + return "<Permissions for %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, WarnFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class WarnSettings(BASE): + __tablename__ = "warn_settings" + chat_id = Column(String(14), primary_key=True) + warn_limit = Column(BigInteger, default=3) + soft_warn = Column(Boolean, default=False) + + def __init__(self, chat_id, warn_limit=3, soft_warn=False): + self.chat_id = str(chat_id) + self.warn_limit = warn_limit + self.soft_warn = soft_warn + + def __repr__(self): + return "<{} has {} possible warns.>".format(self.chat_id, self.warn_limit) + + +Warns.__table__.create(BASE.metadata.bind, checkfirst=True) +WarnFilters.__table__.create(BASE.metadata.bind, checkfirst=True) +WarnSettings.__table__.create(BASE.metadata.bind, checkfirst=True) + +WARN_INSERTION_LOCK = threading.RLock() +WARN_FILTER_INSERTION_LOCK = threading.RLock() +WARN_SETTINGS_LOCK = threading.RLock() + +WARN_FILTERS = {} + + +def warn_user(user_id, chat_id, reason=None): + with WARN_INSERTION_LOCK: + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + if not warned_user: + warned_user = Warns(user_id, str(chat_id)) + + warned_user.num_warns += 1 + if reason: + warned_user.reasons = warned_user.reasons + [ + reason + ] # TODO:: double check this wizardry + + reasons = warned_user.reasons + num = warned_user.num_warns + + SESSION.add(warned_user) + SESSION.commit() + + return num, reasons + + +def remove_warn(user_id, chat_id): + with WARN_INSERTION_LOCK: + removed = False + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + + if warned_user and warned_user.num_warns > 0: + warned_user.num_warns -= 1 + warned_user.reasons = warned_user.reasons[:-1] + SESSION.add(warned_user) + SESSION.commit() + removed = True + + SESSION.close() + return removed + + +def reset_warns(user_id, chat_id): + with WARN_INSERTION_LOCK: + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + if warned_user: + warned_user.num_warns = 0 + warned_user.reasons = [] + + SESSION.add(warned_user) + SESSION.commit() + SESSION.close() + + +def get_warns(user_id, chat_id): + try: + user = SESSION.query(Warns).get((user_id, str(chat_id))) + if not user: + return None + reasons = user.reasons + num = user.num_warns + return num, reasons + finally: + SESSION.close() + + +def add_warn_filter(chat_id, keyword, reply): + with WARN_FILTER_INSERTION_LOCK: + warn_filt = WarnFilters(str(chat_id), keyword, reply) + + if keyword not in WARN_FILTERS.get(str(chat_id), []): + WARN_FILTERS[str(chat_id)] = sorted( + WARN_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.merge(warn_filt) # merge to avoid duplicate key issues + SESSION.commit() + + +def remove_warn_filter(chat_id, keyword): + with WARN_FILTER_INSERTION_LOCK: + warn_filt = SESSION.query(WarnFilters).get((str(chat_id), keyword)) + if warn_filt: + if keyword in WARN_FILTERS.get(str(chat_id), []): # sanity check + WARN_FILTERS.get(str(chat_id), []).remove(keyword) + + SESSION.delete(warn_filt) + SESSION.commit() + return True + SESSION.close() + return False + + +def get_chat_warn_triggers(chat_id): + return WARN_FILTERS.get(str(chat_id), set()) + + +def get_chat_warn_filters(chat_id): + try: + return ( + SESSION.query(WarnFilters).filter(WarnFilters.chat_id == str(chat_id)).all() + ) + finally: + SESSION.close() + + +def get_warn_filter(chat_id, keyword): + try: + return SESSION.query(WarnFilters).get((str(chat_id), keyword)) + finally: + SESSION.close() + + +def set_warn_limit(chat_id, warn_limit): + with WARN_SETTINGS_LOCK: + curr_setting = SESSION.query(WarnSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = WarnSettings(chat_id, warn_limit=warn_limit) + + curr_setting.warn_limit = warn_limit + + SESSION.add(curr_setting) + SESSION.commit() + + +def set_warn_strength(chat_id, soft_warn): + with WARN_SETTINGS_LOCK: + curr_setting = SESSION.query(WarnSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = WarnSettings(chat_id, soft_warn=soft_warn) + + curr_setting.soft_warn = soft_warn + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_warn_setting(chat_id): + try: + setting = SESSION.query(WarnSettings).get(str(chat_id)) + if setting: + return setting.warn_limit, setting.soft_warn + else: + return 3, False + + finally: + SESSION.close() + + +def num_warns(): + try: + return SESSION.query(func.sum(Warns.num_warns)).scalar() or 0 + finally: + SESSION.close() + + +def num_warn_chats(): + try: + return SESSION.query(func.count(distinct(Warns.chat_id))).scalar() + finally: + SESSION.close() + + +def num_warn_filters(): + try: + return SESSION.query(WarnFilters).count() + finally: + SESSION.close() + + +def num_warn_chat_filters(chat_id): + try: + return ( + SESSION.query(WarnFilters.chat_id) + .filter(WarnFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_warn_filter_chats(): + try: + return SESSION.query(func.count(distinct(WarnFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def __load_chat_warn_filters(): + global WARN_FILTERS + try: + chats = SESSION.query(WarnFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + WARN_FILTERS[chat_id] = [] + + all_filters = SESSION.query(WarnFilters).all() + for x in all_filters: + WARN_FILTERS[x.chat_id] += [x.keyword] + + WARN_FILTERS = { + x: sorted(set(y), key=lambda i: (-len(i), i)) + for x, y in WARN_FILTERS.items() + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with WARN_INSERTION_LOCK: + chat_notes = ( + SESSION.query(Warns).filter(Warns.chat_id == str(old_chat_id)).all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + SESSION.commit() + + with WARN_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(WarnFilters) + .filter(WarnFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + old_warn_filt = WARN_FILTERS.get(str(old_chat_id)) + if old_warn_filt is not None: + WARN_FILTERS[str(new_chat_id)] = old_warn_filt + del WARN_FILTERS[str(old_chat_id)] + + with WARN_SETTINGS_LOCK: + chat_settings = ( + SESSION.query(WarnSettings) + .filter(WarnSettings.chat_id == str(old_chat_id)) + .all() + ) + for setting in chat_settings: + setting.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_warn_filters() diff --git a/QueenNoxi/modules/sql/welcome_sql.py b/QueenNoxi/modules/sql/welcome_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..51c16553f724d1017bef9bfaecc4398e7adcdcd5 --- /dev/null +++ b/QueenNoxi/modules/sql/welcome_sql.py @@ -0,0 +1,625 @@ +import random +import threading +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, Integer, String, UnicodeText + +from QueenNoxi.modules.helper_funcs.msg_types import Types +from QueenNoxi.modules.sql import BASE, SESSION + +DEFAULT_WELCOME = "Hey {first}, how are you?" +DEFAULT_GOODBYE = "Nice knowing ya!" + +DEFAULT_WELCOME_MESSAGES = [ + "{first} is here!", + "Ready player {first}", + "Genos, {first} is here.", + "A wild {first} appeared.", + "{first} came in like a Lion!", + "{first} has joined your party.", + "Arre dekho dekho koun aaya\n{first} aaya {first} aaya", + "{first} just joined. Can I get a heal?", + "{first} just joined the chat - asdgfhak!", + "{first} just joined. Everyone, look busy!", + "Welcome, {first}. Stay awhile and listen.", + "Welcome, {first}. We were expecting you ( ͡° ͜ʖ ͡°)", + "Welcome, {first}. We hope you brought pizza.", + "Welcome, {first}. Leave your weapons by the door.", + "Swoooosh. {first} just landed.", + "Brace yourselves. {first} just joined the chat.", + "{first} just joined. Hide your bananas.", + "{first} just arrived. Seems OP - please nerf.", + "{first} just slid into the chat.", + "A {first} has spawned in the chat.", + "Big {first} showed up!", + "Where’s {first}? In the chat!", + "{first} hopped into the chat. Kangaroo!!", + "{first} just showed up. Hold my beer.", + "Challenger approaching! {first} has appeared!", + "It's a bird! It's a plane! Nevermind, it's just {first}.", + "Never gonna give {first} up. Never gonna let {first} down.", + "Ha! {first} has joined! You activated my trap card!", + "Hey! Listen! {first} has joined!", + "We've been expecting you {first}", + "It's dangerous to go alone, take {first}!", + "{first} has joined the chat! It's super effective!", + "Cheers, love! {first} is here!", + "{first} is here, as the prophecy foretold.", + "{first} has arrived. Party's over.", + "{first} is here to kick butt and chew bubblegum. And {first} is all out of gum.", + "Hello. Is it {first} you're looking for?", + "{first} has joined. Stay awhile and listen!", + "Roses are red, violets are blue, {first} joined this chat with you", + "Welcome {first}, Avoid Punches if you can!", + "It's a bird! It's a plane! - Nope, its {first}!", + "{first} Joined! - Ok.", + "All Hail {first}!", + "Hi, {first}. Don't lurk, only Villans do that.", + "{first} has joined the battle bus.", + "A new Challenger enters!", + "Ok!", + "{first} just fell into the chat!", + "Something just fell from the sky! - oh, its {first}.", + "{first} Just teleported into the chat!", + "Hi, {first}, show me your Hunter License!", + "I'm looking for Garo, oh wait nvm it's {first}.", + "Welcome {first}, leaving is not an option!", + "Run Forest! ..I mean...{first}.", + "{first} do 100 push-ups, 100 sit-ups, 100 squats, and 10km running EVERY SINGLE DAY!!!", + "Huh?\nDid someone with a disaster level just join?\nOh wait, it's just {first}.", + "Hey, {first}, ever heard the King Engine?", + "Hey, {first}, empty your pockets.", + "Hey, {first}!, are you strong?", + "Call the Avengers! - {first} just joined the chat.", + "{first} joined. You must construct additional pylons.", + "Ermagherd. {first} is here.", + "Come for the Snail Racing, Stay for the Chimichangas!", + "Who needs Google? You're everything we were searching for.", + "This place must have free WiFi, cause I'm feeling a connection.", + "Speak friend and enter.", + "Welcome you are", + "Welcome {first}, your princess is in another castle.", + "Hi {first}, welcome to the dark side.", + "Hola {first}, beware of people with disaster levels", + "Hey {first}, we have the droids you are looking for.", + "Hi {first}\nThis isn't a strange place, this is my home, it's the people who are strange.", + "Oh, hey {first} what's the password?", + "Hey {first}, I know what we're gonna do today", + "{first} just joined, be at alert they could be a spy.", + "{first} joined the group, read by Mark Zuckerberg, CIA and 35 others.", + "Welcome {first}, watch out for falling monkeys.", + "Everyone stop what you’re doing, We are now in the presence of {first}.", + "Hey {first}, do you wanna know how I got these scars?", + "Welcome {first}, drop your weapons and proceed to the spy scanner.", + "Stay safe {first}, Keep 3 meters social distances between your messages.", + "Hey {first}, Do you know I once One-punched a meteorite?", + "You’re here now {first}, Resistance is futile", + "{first} just arrived, the force is strong with this one.", + "{first} just joined on president’s orders.", + "Hi {first}, is the glass half full or half empty?", + "Yipee Kayaye {first} arrived.", + "Welcome {first}, if you’re a secret agent press 1, otherwise start a conversation", + "{first}, I have a feeling we’re not in Kansas anymore.", + "They may take our lives, but they’ll never take our {first}.", + "Coast is clear! You can come out guys, it’s just {first}.", + "Welcome {first}, pay no attention to that guy lurking.", + "Welcome {first}, may the force be with you.", + "May the {first} be with you.", + "{first} just joined. Hey, where's Perry?", + "{first} just joined. Oh, there you are, Perry.", + "Ladies and gentlemen, I give you ... {first}.", + "Behold my new evil scheme, the {first}-Inator.", + "Ah, {first} the Platypus, you're just in time... to be trapped.", + "{first} just arrived. Diable Jamble!", + "{first} just arrived. Aschente!", + "{first} say Aschente to swear by the pledges.", + "{first} just joined. El Psy congroo!", + "Irasshaimase {first}!", + "Hi {first}, what is 1000-7?", + "Come. I don't want to destroy this place", + "I... am... Whitebeard!...wait..wrong anime.", + "Hey {first}...have you ever heard these words?", + "Can't a guy get a little sleep around here?", + "It's time someone put you in your place, {first}.", + "Unit-01's reactivated..", + "Prepare for trouble...And make it double", + "Hey {first}, are You Challenging Me?", + "Oh? You're Approaching Me?", + "Ho… mukatta kuruno ka?", + "I can't beat the shit out of you without getting closer", + "Ho ho! Then come as close as you'd like.", + "Hoho! Dewa juubun chikazukanai youi", + "Guess who survived his time in Hell, {first}.", + "How many loaves of bread have you eaten in your lifetime?", + "What did you say? Depending on your answer, I may have to kick your ass!", + "Oh? You're approaching me? Instead of running away, you come right to me? Even though your grandfather, Joseph, told you the secret of The World, like an exam student scrambling to finish the problems on an exam until the last moments before the chime?", + "Rerorerorerorerorero.", + "{first} just warped into the group!", + "I..it's..it's just {first}.", + "Sugoi, Dekai. {first} Joined!", + "{first}, do you know gods of death love apples?", + "I'll take a potato chip.... and eat it", + "Oshiete oshiete yo sono shikumi wo!", + "Kaizoku ou ni...nvm wrong anime.", + "{first} just joined! Gear.....second!", + "Omae wa mou....shindeiru", + "Hey {first}, the leaf village lotus blooms twice!", + "{first} Joined! Omote renge!", + "{first}! I, Madara! declare you the strongest", + "{first}, this time I'll lend you my power. ", # Kyuubi to naruto + "{first}, welcome to the hidden leaf village!", # Naruto thingies end here + "In the jungle, you must wait...until the dice read five or eight.", # Jumanji stuff + "Dr.{first} Famed archeologist and international explorer,\nWelcome to Jumanji!\nJumanji's Fate is up to you now.", + "{first}, this will not be an easy mission - monkeys slow the expedition.", # End of Jumanji stuff + "Remember, remember, the Fifth of November, the Gunpowder Treason and Plot. I know of no reason why the Gunpowder Treason should ever be forgot.", # V for Vendetta + "The only verdict is vengeance; a vendetta, held as a votive not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous.", # V for Vendetta + "Behind {first} there is more than just flesh. Beneath this user there is an idea... and ideas are bulletproof.", # V for Vendetta + "Love your rage, not your cage.", # V for Vendetta + "Get your stinking paws off me, you damned dirty ape!", # Planet of the apes + "Elementary, my dear {first}.", + "I'm back - {first}.", + "Bond. {first} Bond.", + "Come with me if you want to live", +] +DEFAULT_GOODBYE_MESSAGES = [ + "{first} will be missed.", + "{first} just went offline.", + "{first} has left the lobby.", + "{first} has left the clan.", + "{first} has left the game.", + "{first} has fled the area.", + "{first} is out of the running.", + "Nice knowing ya, {first}!", + "It was a fun time {first}.", + "We hope to see you again soon, {first}.", + "I donut want to say goodbye, {first}.", + "Goodbye {first}! Guess who's gonna miss you :')", + "Goodbye {first}! It's gonna be lonely without ya.", + "Please don't leave me alone in this place, {first}!", + "Good luck finding better shit-posters than us, {first}!", + "You know we're gonna miss you {first}. Right? Right? Right?", + "Congratulations, {first}! You're officially free of this mess.", + "{first}. You were an opponent worth fighting.", + "You're leaving, {first}? Yare Yare Daze.", + "Bring him the photo", + "Go outside!", + "Ask again later", + "Think for yourself", + "Question authority", + "You are worshiping a sun god", + "Don't leave the house today", + "Give up!", + "Marry and reproduce", + "Stay asleep", + "Wake up", + "Look to la luna", + "Steven lives", + "Meet strangers without prejudice", + "A hanged man will bring you no luck today", + "What do you want to do today?", + "You are dark inside", + "Have you seen the exit?", + "Get a baby pet it will cheer you up.", + "Your princess is in another castle.", + "You are playing it wrong give me the controller", + "Trust good people", + "Live to die.", + "When life gives you lemons reroll!", + "Well, that was worthless", + "I fell asleep!", + "May your troubles be many", + "Your old life lies in ruin", + "Always look on the bright side", + "It is dangerous to go alone", + "You will never be forgiven", + "You have nobody to blame but yourself", + "Only a sinner", + "Use bombs wisely", + "Nobody knows the troubles you have seen", + "You look fat you should exercise more", + "Follow the zebra", + "Why so blue?", + "The devil in disguise", + "Go outside", + "Always your head in the clouds", +] +# Line 111 to 152 are references from https://bindingofisaac.fandom.com/wiki/Fortune_Telling_Machine + + +class Welcome(BASE): + __tablename__ = "welcome_pref" + chat_id = Column(String(14), primary_key=True) + should_welcome = Column(Boolean, default=True) + should_goodbye = Column(Boolean, default=True) + custom_content = Column(UnicodeText, default=None) + + custom_welcome = Column( + UnicodeText, default=random.choice(DEFAULT_WELCOME_MESSAGES) + ) + welcome_type = Column(Integer, default=Types.TEXT.value) + + custom_leave = Column(UnicodeText, default=random.choice(DEFAULT_GOODBYE_MESSAGES)) + leave_type = Column(Integer, default=Types.TEXT.value) + + clean_welcome = Column(BigInteger) + + def __init__(self, chat_id, should_welcome=True, should_goodbye=True): + self.chat_id = chat_id + self.should_welcome = should_welcome + self.should_goodbye = should_goodbye + + def __repr__(self): + return "<Chat {} should Welcome new users: {}>".format( + self.chat_id, self.should_welcome + ) + + +class WelcomeButtons(BASE): + __tablename__ = "welcome_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, name, url, same_line=False): + self.chat_id = str(chat_id) + self.name = name + self.url = url + self.same_line = same_line + + +class GoodbyeButtons(BASE): + __tablename__ = "leave_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, name, url, same_line=False): + self.chat_id = str(chat_id) + self.name = name + self.url = url + self.same_line = same_line + + +class WelcomeMute(BASE): + __tablename__ = "welcome_mutes" + chat_id = Column(String(14), primary_key=True) + welcomemutes = Column(UnicodeText, default=False) + + def __init__(self, chat_id, welcomemutes): + self.chat_id = str(chat_id) # ensure string + self.welcomemutes = welcomemutes + + +class WelcomeMuteUsers(BASE): + __tablename__ = "human_checks" + user_id = Column(Integer, primary_key=True) + chat_id = Column(String(14), primary_key=True) + human_check = Column(Boolean) + + def __init__(self, user_id, chat_id, human_check): + self.user_id = user_id # ensure string + self.chat_id = str(chat_id) + self.human_check = human_check + + +class CleanServiceSetting(BASE): + __tablename__ = "clean_service" + chat_id = Column(String(14), primary_key=True) + clean_service = Column(Boolean, default=True) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) + + def __repr__(self): + return "<Chat used clean service ({})>".format(self.chat_id) + + +Welcome.__table__.create(BASE.metadata.bind, checkfirst=True) +WelcomeButtons.__table__.create(BASE.metadata.bind, checkfirst=True) +GoodbyeButtons.__table__.create(BASE.metadata.bind, checkfirst=True) +WelcomeMute.__table__.create(BASE.metadata.bind, checkfirst=True) +WelcomeMuteUsers.__table__.create(BASE.metadata.bind, checkfirst=True) +CleanServiceSetting.__table__.create(BASE.metadata.bind, checkfirst=True) + +INSERTION_LOCK = threading.RLock() +WELC_BTN_LOCK = threading.RLock() +LEAVE_BTN_LOCK = threading.RLock() +WM_LOCK = threading.RLock() +CS_LOCK = threading.RLock() + + +def welcome_mutes(chat_id): + try: + welcomemutes = SESSION.query(WelcomeMute).get(str(chat_id)) + if welcomemutes: + return welcomemutes.welcomemutes + return False + finally: + SESSION.close() + + +def set_welcome_mutes(chat_id, welcomemutes): + with WM_LOCK: + prev = SESSION.query(WelcomeMute).get((str(chat_id))) + if prev: + SESSION.delete(prev) + welcome_m = WelcomeMute(str(chat_id), welcomemutes) + SESSION.add(welcome_m) + SESSION.commit() + + +def set_human_checks(user_id, chat_id): + with INSERTION_LOCK: + human_check = SESSION.query(WelcomeMuteUsers).get((user_id, str(chat_id))) + if not human_check: + human_check = WelcomeMuteUsers(user_id, str(chat_id), True) + + else: + human_check.human_check = True + + SESSION.add(human_check) + SESSION.commit() + + return human_check + + +def get_human_checks(user_id, chat_id): + try: + human_check = SESSION.query(WelcomeMuteUsers).get((user_id, str(chat_id))) + if not human_check: + return None + human_check = human_check.human_check + return human_check + finally: + SESSION.close() + + +def get_welc_mutes_pref(chat_id): + welcomemutes = SESSION.query(WelcomeMute).get(str(chat_id)) + SESSION.close() + + if welcomemutes: + return welcomemutes.welcomemutes + + return False + + +def get_welc_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + if welc: + return ( + welc.should_welcome, + welc.custom_welcome, + welc.custom_content, + welc.welcome_type, + ) + + else: + # Welcome by default. + return True, DEFAULT_WELCOME, None, Types.TEXT + + +def get_gdbye_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + if welc: + return welc.should_goodbye, welc.custom_leave, welc.leave_type + else: + # Welcome by default. + return True, DEFAULT_GOODBYE, Types.TEXT + + +def set_clean_welcome(chat_id, clean_welcome): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id)) + + curr.clean_welcome = int(clean_welcome) + + SESSION.add(curr) + SESSION.commit() + + +def get_clean_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + + if welc: + return welc.clean_welcome + + return False + + +def set_welc_preference(chat_id, should_welcome): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id), should_welcome=should_welcome) + else: + curr.should_welcome = should_welcome + + SESSION.add(curr) + SESSION.commit() + + +def set_gdbye_preference(chat_id, should_goodbye): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id), should_goodbye=should_goodbye) + else: + curr.should_goodbye = should_goodbye + + SESSION.add(curr) + SESSION.commit() + + +def set_custom_welcome( + chat_id, custom_content, custom_welcome, welcome_type, buttons=None +): + if buttons is None: + buttons = [] + + with INSERTION_LOCK: + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + if not welcome_settings: + welcome_settings = Welcome(str(chat_id), True) + + if custom_welcome or custom_content: + welcome_settings.custom_content = custom_content + welcome_settings.custom_welcome = custom_welcome + welcome_settings.welcome_type = welcome_type.value + + else: + welcome_settings.custom_welcome = DEFAULT_WELCOME + welcome_settings.welcome_type = Types.TEXT.value + + SESSION.add(welcome_settings) + + with WELC_BTN_LOCK: + prev_buttons = ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(chat_id)) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + for b_name, url, same_line in buttons: + button = WelcomeButtons(chat_id, b_name, url, same_line) + SESSION.add(button) + + SESSION.commit() + + +def get_custom_welcome(chat_id): + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + ret = DEFAULT_WELCOME + if welcome_settings and welcome_settings.custom_welcome: + ret = welcome_settings.custom_welcome + + SESSION.close() + return ret + + +def set_custom_gdbye(chat_id, custom_goodbye, goodbye_type, buttons=None): + if buttons is None: + buttons = [] + + with INSERTION_LOCK: + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + if not welcome_settings: + welcome_settings = Welcome(str(chat_id), True) + + if custom_goodbye: + welcome_settings.custom_leave = custom_goodbye + welcome_settings.leave_type = goodbye_type.value + + else: + welcome_settings.custom_leave = DEFAULT_GOODBYE + welcome_settings.leave_type = Types.TEXT.value + + SESSION.add(welcome_settings) + + with LEAVE_BTN_LOCK: + prev_buttons = ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(chat_id)) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + for b_name, url, same_line in buttons: + button = GoodbyeButtons(chat_id, b_name, url, same_line) + SESSION.add(button) + + SESSION.commit() + + +def get_custom_gdbye(chat_id): + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + ret = DEFAULT_GOODBYE + if welcome_settings and welcome_settings.custom_leave: + ret = welcome_settings.custom_leave + + SESSION.close() + return ret + + +def get_welc_buttons(chat_id): + try: + return ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(chat_id)) + .order_by(WelcomeButtons.id) + .all() + ) + finally: + SESSION.close() + + +def get_gdbye_buttons(chat_id): + try: + return ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(chat_id)) + .order_by(GoodbyeButtons.id) + .all() + ) + finally: + SESSION.close() + + +def clean_service(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(CleanServiceSetting).get(str(chat_id)) + if chat_setting: + return chat_setting.clean_service + return False + finally: + SESSION.close() + + +def set_clean_service(chat_id: Union[int, str], setting: bool): + with CS_LOCK: + chat_setting = SESSION.query(CleanServiceSetting).get(str(chat_id)) + if not chat_setting: + chat_setting = CleanServiceSetting(chat_id) + + chat_setting.clean_service = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Welcome).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + + with WELC_BTN_LOCK: + chat_buttons = ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(old_chat_id)) + .all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + with LEAVE_BTN_LOCK: + chat_buttons = ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(old_chat_id)) + .all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + SESSION.commit() diff --git a/QueenNoxi/modules/tagall.py b/QueenNoxi/modules/tagall.py new file mode 100644 index 0000000000000000000000000000000000000000..fffaf7acd718ab4ad9ba4b9000fd66c353026bff --- /dev/null +++ b/QueenNoxi/modules/tagall.py @@ -0,0 +1,106 @@ +import asyncio + +from telethon import events +from telethon.errors import UserNotParticipantError +from telethon.tl.functions.channels import GetParticipantRequest +from telethon.tl.types import ChannelParticipantAdmin, ChannelParticipantCreator + +from QueenNoxi import telethn as client + +spam_chats = [] + +@client.on(events.NewMessage(pattern="^@tagall ?(.*)")) +@client.on(events.NewMessage(pattern="^@all ?(.*)")) +@client.on(events.NewMessage(pattern="^/tagall ?(.*)")) +@client.on(events.NewMessage(pattern="^@mention ?(.*)")) +async def mentionall(event): + chat_id = event.chat_id + if event.is_private: + return await event.respond( + "__This command can be use in groups and channels!__" + ) + + is_admin = False + try: + partici_ = await client(GetParticipantRequest(event.chat_id, event.sender_id)) + except UserNotParticipantError: + is_admin = False + else: + if isinstance( + partici_.participant, (ChannelParticipantAdmin, ChannelParticipantCreator) + ): + is_admin = True + if not is_admin: + return await event.respond("__Only admins can mention all!__") + + if event.pattern_match.group(1) and event.is_reply: + return await event.respond("__Give me one argument!__") + elif event.pattern_match.group(1): + mode = "text_on_cmd" + msg = event.pattern_match.group(1) + elif event.is_reply: + mode = "text_on_reply" + msg = await event.get_reply_message() + if msg == None: + return await event.respond( + "__ɪ ᴄᴀɴ'ᴛ ᴍᴇɴᴛɪᴏɴ ᴍᴇᴍʙᴇʀs ғᴏʀ ᴏʟᴅᴇʀ ᴍᴇssᴀɢᴇs! (ᴍᴇssᴀɢᴇs ᴡʜɪᴄʜ ᴀʀᴇ sᴇɴᴛ ʙᴇғᴏʀᴇ ɪ'ᴍ ᴀᴅᴅᴇᴅ ᴛᴏ ɢʀᴏᴜᴘ__" + ) + else: + return await event.respond( + "__Reply to a message or give me some text to mention others!__" + ) + + spam_chats.append(chat_id) + usrnum = 0 + usrtxt = "" + async for usr in client.iter_participants(chat_id): + if not chat_id in spam_chats: + break + usrnum += 1 + usrtxt += f"[{usr.first_name}](tg://user?id={usr.id}), " + if usrnum == 15: + if mode == "text_on_cmd": + txt = f"{msg}\n{usrtxt}" + await client.send_message(chat_id, txt) + elif mode == "text_on_reply": + await msg.reply(usrtxt) + await asyncio.sleep(3) + usrnum = 0 + usrtxt = "" + try: + spam_chats.remove(chat_id) + except: + pass + + +@client.on(events.NewMessage(pattern="^/cancel$")) +async def cancel_spam(event): + if not event.chat_id in spam_chats: + return await event.respond("ᴛʜᴇʀᴇ ɪs ɴᴏ ᴘʀᴏᴄᴄᴇss ᴏɴ ɢᴏɪɴɢ..") + is_admin = False + try: + partici_ = await client(GetParticipantRequest(event.chat_id, event.sender_id)) + except UserNotParticipantError: + is_admin = False + else: + if isinstance( + partici_.participant, (ChannelParticipantAdmin, ChannelParticipantCreator) + ): + is_admin = True + if not is_admin: + return await event.respond("__ᴏɴʟʏ ᴀᴅᴍɪɴs ᴄᴀɴ ᴇxᴇᴄᴜᴛᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ!__") + + else: + try: + spam_chats.remove(event.chat_id) + except: + pass + return await event.respond("sᴛᴏᴘᴘᴇᴅ ᴍᴇɴᴛɪᴏɴ.__") + + +__mod_name__ = "Tᴀɢᴀʟʟ" +__help__ = """ +──「 ᴏɴʟʏ ғᴏʀ ᴀᴅᴍɪɴs 」── + +❍ /tagall ᴏʀ @all '(ʀᴇᴘʟʏ ᴛᴏ ᴍᴇssᴀɢᴇ ᴏʀ ᴀᴅᴅ ᴀɴᴏᴛʜᴇʀ ᴍᴇssᴀɢᴇ) ᴛᴏ ᴍᴇɴᴛɪᴏɴ ᴀʟʟ ᴍᴇᴍʙᴇʀs ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ, ᴡɪᴛʜᴏᴜᴛ ᴇxᴄᴇᴘᴛɪᴏɴ.' +""" diff --git a/QueenNoxi/modules/telegraph.py b/QueenNoxi/modules/telegraph.py new file mode 100644 index 0000000000000000000000000000000000000000..a945e9c096f5acce49be499f489591ce2ce0cf6b --- /dev/null +++ b/QueenNoxi/modules/telegraph.py @@ -0,0 +1,91 @@ +import os +from datetime import datetime + +from PIL import Image +from telegraph import Telegraph, exceptions, upload_file + +from pyrogram import filters +from QueenNoxi import pbot as app + +TMP_DOWNLOAD_DIRECTORY = "./" +telegraph = Telegraph(domain="graph.org") +r = telegraph.create_account(short_name="Controller") +auth_url = r["auth_url"] + + +@app.on_message(filters.command(["tgm", "tgt"])) +async def _(_, message): + if getattr(message, "forward_from", None): + return + input_str = message.command[0].replace("tg", "") + optional_title = message.text.split(None, 1)[1] if len(message.command) > 1 else "" + + if message.reply_to_message: + start = datetime.now() + r_message = message.reply_to_message + + if input_str == "m": + downloaded_file_name = await r_message.download(TMP_DOWNLOAD_DIRECTORY) + end = datetime.now() + ms = (end - start).seconds + h = await message.reply(f"Downloaded to {downloaded_file_name} in {ms} seconds.") + + if downloaded_file_name.endswith((".webp")): + resize_image(downloaded_file_name) + try: + start = datetime.now() + media_urls = upload_file(downloaded_file_name) + except exceptions.TelegraphException as exc: + await h.edit("ERROR: " + str(exc)) + os.remove(downloaded_file_name) + else: + end = datetime.now() + (end - start).seconds + os.remove(downloaded_file_name) + await h.edit( + f"Uploaded to https://graph.org{media_urls[0]}", + disable_web_page_preview=False, + ) + + elif input_str == "t": + title_of_page = r_message.from_user.first_name if r_message.from_user else "Unknown" + if optional_title: + title_of_page = optional_title + + page_content = r_message.text or r_message.caption or "" + if r_message.media: + if page_content != "": + title_of_page = page_content + downloaded_file_name = await r_message.download(TMP_DOWNLOAD_DIRECTORY) + m_list = None + with open(downloaded_file_name, "rb") as fd: + m_list = fd.readlines() + for m in m_list: + page_content += m.decode("UTF-8") + "\n" + os.remove(downloaded_file_name) + + page_content = page_content.replace("\n", "<br>") + response = telegraph.create_page(title_of_page, html_content=page_content) + end = datetime.now() + ms = (end - start).seconds + await message.reply( + f"Pasted to https://graph.org/{response['path']} in {ms} seconds.", + disable_web_page_preview=False, + ) + else: + await message.reply("Reply to a message to get a permanent telegra.ph link.") + + +def resize_image(image): + im = Image.open(image) + im.save(image, "PNG") + + +__help__ = """ +ɪ ᴄᴀɴ ᴜᴘʟᴏᴀᴅ ғɪʟᴇs ᴛᴏ ᴛᴇʟᴇɢʀᴀᴘʜ + ❍ /tgm :ɢᴇᴛ ᴛᴇʟᴇɢʀᴀᴘʜ ʟɪɴᴋ ᴏғ ʀᴇᴘʟɪᴇᴅ ᴍᴇᴅɪᴀ + ❍ /tgt :ɢᴇᴛ ᴛᴇʟᴇɢʀᴀᴘʜ ʟɪɴᴋ ᴏғ ʀᴇᴘʟɪᴇᴅ ᴛᴇxᴛ + ❍ /tgt [ᴄᴜsᴛᴏᴍ ɴᴀᴍᴇ]: ɢᴇᴛ ᴛᴇʟᴇɢʀᴀᴘʜ ʟɪɴᴋ ᴏғ ʀᴇᴘʟɪᴇᴅ ᴛᴇxᴛ ᴡɪᴛʜ ᴄᴜsᴛᴏᴍ ɴᴀᴍᴇ. +""" + +__mod_name__ = "T-Gʀᴀᴘʜ" diff --git a/QueenNoxi/modules/translator.py b/QueenNoxi/modules/translator.py new file mode 100644 index 0000000000000000000000000000000000000000..3b72bb3bbe844652140013f78bb3aa3f6a519d9a --- /dev/null +++ b/QueenNoxi/modules/translator.py @@ -0,0 +1,69 @@ +import html +from gpytranslate import SyncTranslator +from pyrogram import filters, Client, enums +from pyrogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup + +from QueenNoxi import pbot, BOT_NAME, BOT_USERNAME, OWNER_ID +from QueenNoxi.modules.disable import DisableAbleCommandHandler + +trans = SyncTranslator() + +@pbot.on_message(filters.command(["tr", "tl"])) +async def totranslate(client: Client, message: Message): + reply_msg = message.reply_to_message + if not reply_msg: + await message.reply_text( + "Reply to a message to translate it!\n" + "Example: `/tr en-hi` or `/tr en`\n" + "Click [here](https://t.me/queennoxibotzone/16) for language codes.", + disable_web_page_preview=True, + ) + return + + to_translate = reply_msg.text or reply_msg.caption + if not to_translate: + await message.reply_text("I can't translate that!") + return + + try: + args = message.command[1].lower() if len(message.command) > 1 else "en" + if "-" in args: + source, dest = args.split("-") + else: + source = trans.detect(to_translate) + dest = args + except: + source = trans.detect(to_translate) + dest = "en" + + translation = trans(to_translate, sourcelang=source, targetlang=dest) + reply = ( + f"**Translated from {source} to {dest}**:\n" + f"`{translation.text}`" + ) + + await message.reply_text(reply) + +@pbot.on_message(filters.command(["repo", "source"])) +async def repo(client: Client, message: Message): + me = await client.get_me() + await message.reply_photo( + photo="https://te.legra.ph/file/1a72f3770dcb90ee8b3f7.jpg", + caption=f"""**Hey {message.from_user.mention},\n\nI am [{me.first_name}](https://t.me/{me.username})** + +**» My Developer:** [QueenNoxi](tg://user?id={OWNER_ID}) +**» Python Version:** `3.10` +**» Library:** `Pyrogram` + +**Group Controller source is now public!**""", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("Owner", user_id=OWNER_ID)], + [InlineKeyboardButton("Repo", url="https://github.com/Noob-QueenNoxi/QueenNoxi")] + ]) + ) + +__help__ = """ +• `/tr <lang>`: Translate replied message. +• `/repo`: Get bot source code. +""" +__mod_name__ = "Trans" diff --git a/QueenNoxi/modules/truth_dare.py b/QueenNoxi/modules/truth_dare.py new file mode 100644 index 0000000000000000000000000000000000000000..bbd7f7925ba1d810003fab43451bc277b423e94b --- /dev/null +++ b/QueenNoxi/modules/truth_dare.py @@ -0,0 +1,25 @@ + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.enums import ParseMode +from MukeshAPI import api +from QueenNoxi import pbot as QueenNoxi +@QueenNoxi.on_message(filters.command("truth")) +async def truth_(client: Client, message: Message): + + truth =api.truth() + await message.reply_text(truth) + +@QueenNoxi.on_message(filters.command("dare")) +async def dare_(client: Client, message: Message): + + dare =api.dare() + await message.reply_text(dare) + + +__help__ = """ +*ᴛʀᴜᴛʜ & ᴅᴀʀᴇ* + ❍ /truth *:* sᴇɴᴅs ᴀ ʀᴀɴᴅᴏᴍ ᴛʀᴜᴛʜ sᴛʀɪɴɢ. + ❍ /dare *:* sᴇɴᴅs ᴀ ʀᴀɴᴅᴏᴍ ᴅᴀʀᴇ sᴛʀɪɴɢ. +""" +__mod_name__ = "Tʀᴜᴛʜ-Dᴀʀᴇ" diff --git a/QueenNoxi/modules/ud.py b/QueenNoxi/modules/ud.py new file mode 100644 index 0000000000000000000000000000000000000000..b096e76d1b2a8f403b660317f388c78118a1db16 --- /dev/null +++ b/QueenNoxi/modules/ud.py @@ -0,0 +1,36 @@ + + +from pyrogram import Client, filters +from pyrogram.types import Message +from pyrogram.enums import ParseMode +import aiohttp + +from QueenNoxi import pbot as QueenNoxi +from QueenNoxi.modules.disable import DisableAbleCommandHandler + + +async def ud(client: Client, message: Message): + text = message.text[len("/ud "):] + + async with aiohttp.ClientSession() as session: + async with session.get(f"https://api.urbandictionary.com/v0/define?term={text}") as resp: + results = await resp.json() + try: + reply_text = f'*{text}*\n\n{results["list"][0]["definition"]}\n\n_{results["list"][0]["example"]}_' + except (KeyError, IndexError): + reply_text = "No results found." + + await message.reply_text(reply_text, parse_mode=ParseMode.MARKDOWN) + + +@QueenNoxi.on_message(filters.command("ud")) +async def ud_command(client: Client, message: Message): + await ud(client, message) + + +__help__ = """ +» /ud (text) *:* sᴇᴀʀᴄʜs ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ ᴏɴ ᴜʀʙᴀɴ ᴅɪᴄᴛɪᴏɴᴀʀʏ ᴀɴᴅ sᴇɴᴅs ʏᴏᴜ ᴛʜᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ. +""" +__mod_name__ = "Uʀʙᴀɴ" + + diff --git a/QueenNoxi/modules/unbanall.py b/QueenNoxi/modules/unbanall.py new file mode 100644 index 0000000000000000000000000000000000000000..ca4d84342fd5cdb3e99fa362f26135ac77f9b152 --- /dev/null +++ b/QueenNoxi/modules/unbanall.py @@ -0,0 +1,96 @@ +from pyrogram import filters +from pyrogram.enums import ChatMembersFilter +from pyrogram.types import ChatPermissions +from QueenNoxi import pbot as app, LOGGER +import asyncio + +async def check_admin(chat_id, user_id): + try: + member = await app.get_chat_member(chat_id, user_id) + return member.privileges is not None + except Exception: + return False + +@app.on_message(filters.command("unbanall")) +async def unbanall_cmd(_, message): + if message.chat.type.name == "PRIVATE": + return await message.reply("__ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ ʙᴇ ᴜsᴇ ɪɴ ɢʀᴏᴜᴘs ᴀɴᴅ ᴄʜᴀɴɴᴇʟs!__") + if not await check_admin(message.chat.id, message.from_user.id): + return await message.reply("__ᴏɴʟʏ ᴀᴅᴍɪɴs ᴄᴀɴ ᴜɴᴍᴜᴛᴇᴀʟʟ!__") + + bot_member = await app.get_chat_member(message.chat.id, app.me.id) + if not bot_member.privileges or not bot_member.privileges.can_restrict_members: + return await message.reply("`I don't have enough permissions!`") + + done = await message.reply("sᴇᴀʀᴄʜɪɴɢ ᴘᴀʀᴛɪᴄɪᴘᴀɴᴛ ʟɪsᴛs") + p = 0 + try: + async for member in app.get_chat_members(message.chat.id, filter=ChatMembersFilter.BANNED): + try: + await app.unban_chat_member(message.chat.id, member.user.id) + p += 1 + except Exception: + pass + except Exception as e: + LOGGER.error(f"Unbanall error: {e}") + + if p == 0: + await done.edit("ɴᴏ ᴏɴᴇ ɪs ʙᴀɴɴᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ") + else: + await done.edit(f"sᴜᴄᴇssғᴜʟʟʏ ᴜɴʙᴀɴɴᴇᴅ **{p}** ᴜsᴇʀs") + +@app.on_message(filters.command("unmuteall")) +async def unmuteall_cmd(_, message): + if message.chat.type.name == "PRIVATE": + return await message.reply("__This command can be use in groups and channels!__") + if not await check_admin(message.chat.id, message.from_user.id): + return await message.reply("__ᴏɴʟʏ ᴀᴅᴍɪɴs ᴄᴀɴ ᴜɴᴍᴜᴛᴇᴀʟʟ!__") + + bot_member = await app.get_chat_member(message.chat.id, app.me.id) + if not bot_member.privileges or not bot_member.privileges.can_restrict_members: + return await message.reply("`I don't have enough permissions!`") + + done = await message.reply("Working ...") + p = 0 + try: + async for member in app.get_chat_members(message.chat.id, filter=ChatMembersFilter.RESTRICTED): + try: + await app.restrict_chat_member(message.chat.id, member.user.id, ChatPermissions( + can_send_messages=True, can_send_media_messages=True, can_send_other_messages=True, can_add_web_page_previews=True + )) + p += 1 + except Exception: + pass + except Exception as e: + LOGGER.error(f"unmuteall error: {e}") + + if p == 0: + await done.edit("ɴᴏ ᴏɴᴇ ɪs ᴍᴜᴛᴇᴅ ɪɴ ᴛʜɪs ᴄʜᴀᴛ") + else: + await done.edit(f"sᴜᴄᴇssғᴜʟʟʏ ᴜɴᴍᴜᴛᴇᴅ **{p}** ᴜsᴇʀs") + +@app.on_message(filters.command("users")) +async def users_cmd(_, message): + if message.chat.type.name == "PRIVATE": + return + if not await check_admin(message.chat.id, message.from_user.id): + return + title = message.chat.title or "this chat" + mentions = f"ᴜsᴇʀs ɪɴ {title}: \n" + async for member in app.get_chat_members(message.chat.id): + if member.user.is_deleted: + mentions += f"\nᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs {member.user.id}" + else: + mentions += f"\n[{member.user.first_name}](tg://user?id={member.user.id}) ❣ {member.user.id}" + + with open("userslist.txt", "w+", encoding="utf-8") as file: + file.write(mentions) + await message.reply_document("userslist.txt", caption=f"ᴜsᴇʀs ɪɴ {title}") + import os; os.remove("userslist.txt") + +__mod_name__ = "Aᴅᴠᴀɴᴄᴇ" +__help__ = """ +➥ /unbanall : ᴜɴʙᴀɴ ᴀʟʟ ᴍᴀᴍʙᴇʀ +➥ /unmuteall : ᴜɴᴍᴜᴛᴇ ᴀʟʟ ᴍᴀᴍʙᴇʀ +➥ /users : ɢᴇᴛ ɢʀᴏᴜᴘ ᴜsᴇʀs ʟɪsᴛ +""" diff --git a/QueenNoxi/modules/urlshortner.py b/QueenNoxi/modules/urlshortner.py new file mode 100644 index 0000000000000000000000000000000000000000..7a3ddd5222d8f8d183144cbdeafeaaea6a21d667 --- /dev/null +++ b/QueenNoxi/modules/urlshortner.py @@ -0,0 +1,88 @@ +from pyrogram import Client, enums, filters, idle + +import re +from requests import get +import asyncio +from QueenNoxi import pbot as queennoxi + +from pyrogram.types import InlineKeyboardButton as ikb, InlineKeyboardMarkup as ikm, Message +from pyrogram.enums import ChatAction, ParseMode +import pyshorteners +shortener = pyshorteners.Shortener() +from pyrogram.handlers import MessageHandler +@queennoxi.on_message(filters.command(["short"])) +async def short_urls(bot, message): + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "**Example:**\n\n`/short [url]`") +# url_pattern = re.compile(r'https?://\S+') + link=message.command[1] +# link = url_pattern.findall(urls) + +# Check if any URLs were found +# if link not in urls: +# return await message.reply_text("this is not valid provide url") +# else: + try: + + tiny_link = shortener.tinyurl.short(link) + + + dagd_link = shortener.dagd.short(link) + + clckru_link = shortener.clckru.short(link) + + shorted=[tiny_link,dagd_link,clckru_link] + url=[ + [ikb("Tiny Url",url=tiny_link)], + + [ikb("Dagd Url",url=dagd_link), + + ikb("Clckru Url",url=clckru_link) + ] + ] + await message.reply_text(f"Here are few shortened links :",reply_markup=ikm(url)) + + except Exception as e: + await message.reply_text(f"Either the link is already shortened or is invalid.") + +@queennoxi.on_message(filters.command(["unshort"])) +async def unshort(bot, message): + await bot.send_chat_action(message.chat.id, ChatAction.TYPING) + if len(message.command) < 2: + return await message.reply_text( + "**Example:**\n\n`/unshort [short - url]`") + link=message.text.split(' ')[1] + + try: + + x = get(link, allow_redirects=True).url + + url=[ + + [ikb + + ("View Link",url=x) + + ] + + ] + + + + await message.reply_text(f"Here's the unshortened link :\n`{x}` " ,reply_markup=ikm(url)) + + + + except Exception as e: + + await message.reply_text(f"ᴇʀʀᴏʀ: {e} ") +# queennoxi.add_handler(MessageHandler(short_urls)) +# queennoxi.add_handler(MessageHandler(unshort)) +__help__ = """ +ᴍᴀᴋᴇ sʜᴏʀᴛs ᴏғ ᴀ ɢɪᴠᴇɴ ʟɪɴᴋ + ❍ /short <url> *:Example `/short https://t.me/mr_sukkun`. + *""" + +__mod_name__ = "Sʜᴏʀᴛᴇɴᴇʀ" diff --git a/QueenNoxi/modules/userinfo.py b/QueenNoxi/modules/userinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..48bb8d912c35dfa753656a27b4b8359e4bda1a5d --- /dev/null +++ b/QueenNoxi/modules/userinfo.py @@ -0,0 +1,200 @@ +import html +import os +import re +import asyncio +from pyrogram import filters, Client, enums +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + CallbackQuery, +) +from pyrogram.errors import RPCError +from telethon import events +from telethon.tl.functions.channels import GetFullChannelRequest +from telethon.tl.types import ChannelParticipantsAdmins + +import QueenNoxi.modules.sql.userinfo_sql as sql +from QueenNoxi import ( + DEMONS, + DEV_USERS, + DRAGONS, + INFOPIC, + OWNER_ID, + OWNER_IDS, + TIGERS, + WOLVES, + pbot, + telethn, + BOT_NAME, + BOT_USERNAME, + BOT_ID +) +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import sudo_plus +from QueenNoxi.modules.helper_funcs.extraction import extract_user +from QueenNoxi.modules.sql.global_bans_sql import is_user_gbanned +from QueenNoxi.modules.sql.users_sql import get_user_num_chats + +def no_by_per(totalhp, percentage): + return totalhp * percentage / 100 + +def get_percentage(totalhp, earnedhp): + matched_less = totalhp - earnedhp + per_of_totalhp = 100 - matched_less * 100.0 / totalhp + return str(int(per_of_totalhp)) + +async def hpmanager(user): + total_hp = (get_user_num_chats(user.id) + 10) * 10 + new_hp = total_hp + if not await is_user_gbanned(user.id): + if not user.username: + new_hp -= no_by_per(total_hp, 25) + try: + photos = [p async for p in pbot.get_chat_photos(user.id)] + if not photos: + new_hp -= no_by_per(total_hp, 25) + except: + new_hp -= no_by_per(total_hp, 25) + if not sql.get_user_me_info(user.id): + new_hp -= no_by_per(total_hp, 20) + if not sql.get_user_bio(user.id): + new_hp -= no_by_per(total_hp, 10) + else: + new_hp = no_by_per(total_hp, 5) + return { + "earnedhp": int(new_hp), + "totalhp": int(total_hp), + "percentage": get_percentage(total_hp, new_hp), + } + +def make_bar(per): + done = min(round(int(per) / 10), 10) + return "■" * done + "□" * (10 - done) + +@pbot.on_message(filters.command("id")) +async def get_id(client: Client, message: Message): + chat = message.chat + if len(message.command) > 1: + user_id = await extract_user(message, message.command[1:]) + if user_id: + user = await client.get_users(user_id) + await message.reply_text(f"{user.first_name}'s ID is `{user.id}`.") + else: + await message.reply_text("I can't find that user.") + else: + if chat.type == enums.ChatType.PRIVATE: + await message.reply_text(f"Your ID is `{message.from_user.id}`.") + else: + await message.reply_text(f"This group's ID is `{chat.id}`.") + +@pbot.on_message(filters.command("info")) +async def info(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) or message.from_user.id + user = await client.get_users(user_id) + chat = message.chat + + text = ( + f"✦ **User Info** ✦\n" + f"➻ **ID:** `{user.id}`\n" + f"➻ **First Name:** {html.escape(user.first_name)}\n" + ) + if user.last_name: + text += f"➻ **Last Name:** {html.escape(user.last_name)}\n" + if user.username: + text += f"➻ **Username:** @{user.username}\n" + + text += f"➻ **Mention:** {user.mention}\n" + + # Presence in group + if chat.type != enums.ChatType.PRIVATE: + try: + member = await chat.get_member(user.id) + text += f"➻ **Status:** `{member.status}`\n" + except: + pass + + userhp = await hpmanager(user) + text += f"\n**Health:** `{userhp['earnedhp']}/{userhp['totalhp']}`\n" + text += f"[{make_bar(userhp['percentage'])} {userhp['percentage']}%]\n" + + # Disaster Levels + if user.id == OWNER_ID: + text += "\nThis user is **GOD**.\n" + elif user.id in DEV_USERS: + text += "\nThis user is a **Developer**.\n" + elif user.id in DRAGONS: + text += "\nThis user is a **Dragon**.\n" + + if INFOPIC: + try: + photo = [p async for p in client.get_chat_photos(user.id, limit=1)][0] + await message.reply_photo( + photo.file_id, + caption=text, + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("Health Guide", url="https://t.me/queennoxibotzone/90")], + [InlineKeyboardButton("Add Me", url=f"https://t.me/{BOT_USERNAME}?startgroup=true")] + ]) + ) + except: + await message.reply_text(text) + else: + await message.reply_text(text) + +@pbot.on_message(filters.command("me")) +async def about_me(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) or message.from_user.id + user = await client.get_users(user_id) + info = sql.get_user_me_info(user.id) + if info: + await message.reply_text(f"**{user.first_name}**:\n{info}") + else: + await message.reply_text("No info set yet!") + +@pbot.on_message(filters.command("setme")) +async def set_about_me(client: Client, message: Message): + if len(message.command) < 2: + await message.reply_text("Usage: /setme <info>") + return + text = message.text.split(None, 1)[1] + sql.set_user_me_info(message.from_user.id, text) + await message.reply_text("Info updated!") + +@pbot.on_message(filters.command("bio")) +async def about_bio(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) or message.from_user.id + user = await client.get_users(user_id) + bio = sql.get_user_bio(user.id) + if bio: + await message.reply_text(f"**{user.first_name}**'s Bio:\n{bio}") + else: + await message.reply_text("No bio set yet!") + +@pbot.on_message(filters.command("setbio")) +async def set_about_bio(client: Client, message: Message): + if not message.reply_to_message: + await message.reply_text("Reply to someone to set their bio!") + return + if len(message.command) < 2: + await message.reply_text("Usage: /setbio <text>") + return + + target_user = message.reply_to_message.from_user + if target_user.id == message.from_user.id: + await message.reply_text("You can't set your own bio!") + return + + text = message.text.split(None, 1)[1] + sql.set_user_bio(target_user.id, text) + await message.reply_text(f"Updated {target_user.first_name}'s bio!") + +__mod_name__ = "Info" +__help__ = """ +• `/id`: Get user/chat ID +• `/info`: Get user info +• `/me`: Get user-set info +• `/setme`: Set your own info +• `/bio`: Get user bio (set by others) +• `/setbio`: Set another user's bio +""" diff --git a/QueenNoxi/modules/users.py b/QueenNoxi/modules/users.py new file mode 100644 index 0000000000000000000000000000000000000000..b68aeb87b0429f1cf54daa6e587a6146efbd5481 --- /dev/null +++ b/QueenNoxi/modules/users.py @@ -0,0 +1,162 @@ +import asyncio +import time +import datetime +import logging +from pyrogram import filters, Client, enums +from pyrogram.types import Message +from pyrogram.errors import ( + FloodWait, + InputUserDeactivated, + UserIsBlocked, + PeerIdInvalid, +) + +from QueenNoxi import pbot as QueenNoxi +from QueenNoxi import DEV_USERS, OWNER_ID, LOGGER +from QueenNoxi.modules.no_sql import ( + get_served_chats, + get_served_users, + remove_served_chat, + remove_served_users +) + +USERS_GROUP = 4 +CHAT_GROUP = 5 + +async def get_user_id(username): + if not username: + return None + if len(username) <= 5 and not str(username).isdigit(): + return None + + if str(username).startswith("@"): + username = username[1:] + + try: + user = await QueenNoxi.get_users(username) + return user.id + except Exception as e: + LOGGER.error(f"Error fetching user {username}: {e}") + return None + + +@QueenNoxi.on_message(filters.command(["bchat", "broadcastgroups"]) & filters.user(OWNER_ID) & filters.reply) +async def broadcast_handler(bot: Client, m: Message): + all_chats = get_served_chats() or [] + await bot.send_message( + OWNER_ID, + f"{m.from_user.mention} or {m.from_user.id} Is started the Broadcast......", + ) + broadcast_msg = m.reply_to_message + sts_msg = await m.reply_text(f"broadcasting ..") + done = 0 + failed = 0 + success = 0 + start_time = time.time() + total_chats = len(all_chats) + + for chat in all_chats: + sts = await send_chat(chat["chat_id"], broadcast_msg) + + if sts == 200: + success += 1 + else: + failed += 1 + done += 1 + if not done % 20: + await sts_msg.edit( + f"Broadcast In Progress: \nTotal chats {total_chats} \nCompleted: {done} / {total_chats}\nSuccess: {success}\nFailed: {failed}" + ) + + completed_in = datetime.timedelta(seconds=int(time.time() - start_time)) + await sts_msg.edit( + f"Broadcast Completed: \nCompleted In {completed_in}.\n\nTotal chats {total_chats}\nCompleted: {done} / {total_chats}\nSuccess: {success}\nFailed: {failed}" + ) + + +async def send_chat(chat_id, message): + try: + await message.forward(chat_id=int(chat_id)) + return 200 + except FloodWait as e: + await asyncio.sleep(e.value) + return await send_chat(chat_id, message) + except InputUserDeactivated: + remove_served_chat(chat_id) + LOGGER.info(f"{chat_id} : Deactivated") + return 400 + except UserIsBlocked: + remove_served_chat(chat_id) + LOGGER.info(f"{chat_id} : Blocked The Bot") + return 400 + except PeerIdInvalid: + remove_served_chat(chat_id) + LOGGER.info(f"{chat_id} : User Id Invalid") + return 400 + except Exception as e: + remove_served_chat(chat_id) + LOGGER.error(f"{chat_id} : {e}") + return 500 + + +@QueenNoxi.on_message(filters.command(["buser", "broadcastusers"]) & filters.user(OWNER_ID) & filters.reply) +async def broadcast_users_handler(bot: Client, m: Message): + all_users = get_served_users() or [] + await bot.send_message( + OWNER_ID, + f"{m.from_user.mention} or {m.from_user.id} Is started the Broadcast......", + ) + broadcast_msg = m.reply_to_message + sts_msg = await m.reply_text(f"broadcasting ..") + done = 0 + failed = 0 + success = 0 + start_time = time.time() + total_users = len(all_users) + + for user in all_users: + sts = await send_msg(user["user_id"], broadcast_msg) + if sts == 200: + success += 1 + else: + failed += 1 + done += 1 + if not done % 20: + await sts_msg.edit( + f"Broadcast In Progress: \nTotal Users {total_users} \nCompleted: {done} / {total_users}\nSuccess: {success}\nFailed: {failed}" + ) + + completed_in = datetime.timedelta(seconds=int(time.time() - start_time)) + await sts_msg.edit( + f"Broadcast Completed: \nCompleted In {completed_in}.\n\nTotal Users {total_users}\nCompleted: {done} / {total_users}\nSuccess: {success}\nFailed: {failed}" + ) + + +async def send_msg(user_id, message): + try: + await message.forward(user_id) + return 200 + except FloodWait as e: + await asyncio.sleep(e.value) + return await send_msg(user_id, message) + except InputUserDeactivated: + remove_served_users(user_id) + LOGGER.info(f"{user_id} : Deactivated") + return 400 + except UserIsBlocked: + remove_served_users(user_id) + LOGGER.info(f"{user_id} : Blocked The Bot") + return 400 + except PeerIdInvalid: + remove_served_users(user_id) + LOGGER.info(f"{user_id} : User Id Invalid") + return 400 + except Exception as e: + LOGGER.error(f"{user_id} : {e}") + return 500 + + +def __stats__(): + return f"• {len(get_served_users())} ᴜsᴇʀs, ᴀᴄʀᴏss {len(get_served_chats())} ᴄʜᴀᴛs" + + diff --git a/QueenNoxi/modules/wallpaper.py b/QueenNoxi/modules/wallpaper.py new file mode 100644 index 0000000000000000000000000000000000000000..ac7b811030fd6c9ba3faecb27b9aef57ceb18d66 --- /dev/null +++ b/QueenNoxi/modules/wallpaper.py @@ -0,0 +1,35 @@ +import random + +import requests +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message + +from QueenNoxi import pbot + +@pbot.on_message(filters.command(["wall", "wallpaper"])) +async def wall(_, message: Message): + " ғɪxᴇᴅ ᴡᴀʟʟ ʙʏ ᴍᴜᴋᴇsʜ" + try: + text = message.text.split(None, 1)[1] + except IndexError: + text = None + if not text: + return await message.reply_text("`Please give some query to search.`") + m = await message.reply_text("`Searching for wallpapers...`") + try: + url = requests.get(f"https://api.safone.me/wall?query={text}").json()["results"] + ran = random.randint(0, 3) + await message.reply_photo( + photo=url[ran]["imageUrl"], + caption=f"🥀 **ʀᴇǫᴜᴇsᴛᴇᴅ ʙʏ :** {message.from_user.mention}", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton("ʟɪɴᴋ", url=url[ran]["imageUrl"])], + ] + ), + ) + await m.delete() + except Exception as e: + await m.edit_text( + f"`ᴡᴀʟʟᴘᴀᴘᴇʀ ɴᴏᴛ ғᴏᴜɴᴅ ғᴏʀ : `{text}`", + ) diff --git a/QueenNoxi/modules/warns.py b/QueenNoxi/modules/warns.py new file mode 100644 index 0000000000000000000000000000000000000000..93a179de3926fd0eda96b9717351d4482dec40e4 --- /dev/null +++ b/QueenNoxi/modules/warns.py @@ -0,0 +1,225 @@ +import html +import re +from typing import Optional + +from pyrogram import filters, Client, enums +from pyrogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + User, +) +from pyrogram.errors import BadRequest + +from QueenNoxi import TIGERS, WOLVES, pbot, BOT_ID +from QueenNoxi.modules.disable import DisableAbleCommandHandler +from QueenNoxi.modules.helper_funcs.chat_status import ( + bot_admin, + can_restrict, + is_user_admin, + user_admin, + user_admin_no_reply, +) +from QueenNoxi.modules.helper_funcs.extraction import ( + extract_user, + extract_user_and_text, +) +from QueenNoxi.modules.helper_funcs.string_handling import split_quotes +from QueenNoxi.modules.log_channel import loggable +from QueenNoxi.modules.sql import warns_sql as sql + +WARN_HANDLER_GROUP = 9 + +async def warn( + user: User, + chat_id: int, + reason: str, + message: Message, + warner: User = None, +) -> str: + if await is_user_admin(message.chat, user.id): + return + + if user.id in TIGERS: + await message.reply_text("Tigers can't be warned.") + return + if user.id in WOLVES: + await message.reply_text("Wolves are warn immune.") + return + + warner_tag = warner.mention if warner else "Automated warn filter." + limit, soft_warn = sql.get_warn_setting(chat_id) + num_warns, reasons = sql.warn_user(user.id, chat_id, reason) + + if num_warns >= limit: + sql.reset_warns(user.id, chat_id) + if soft_warn: # punch (kick) + await message.chat.unban_member(user.id) + reply = f"<b>Punch Event</b>\n<b>User:</b> {user.mention}\n<b>Count:</b> {limit}" + else: # ban + await message.chat.ban_member(user.id) + reply = f"<b>Ban Event</b>\n<b>User:</b> {user.mention}\n<b>Count:</b> {limit}" + + for r in reasons: + reply += f"\n - {html.escape(r or 'No reason')}" + + log_reason = f"<b>{html.escape(message.chat.title)}:</b>\n#WARN_BAN\n<b>Admin:</b> {warner_tag}\n<b>User:</b> {user.mention}\n<b>Reason:</b> {reason}\n<b>Counts:</b> {num_warns}/{limit}" + await message.reply_text(reply) + else: + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton("✨ REMOVE ✨", callback_data=f"rm_warn({user.id})")]] + ) + reply = f"<b>Warn Event</b>\n<b>User:</b> {user.mention}\n<b>Count:</b> {num_warns}/{limit}" + if reason: + reply += f"\n<b>Reason:</b> {html.escape(reason)}" + + log_reason = f"<b>{html.escape(message.chat.title)}:</b>\n#WARN\n<b>Admin:</b> {warner_tag}\n<b>User:</b> {user.mention}\n<b>Reason:</b> {reason}\n<b>Counts:</b> {num_warns}/{limit}" + await message.reply_text(reply, reply_markup=keyboard) + + return log_reason + +@pbot.on_callback_query(filters.regex(r"rm_warn\((.+?)\)")) +@user_admin_no_reply +@bot_admin +@loggable +async def button(client: Client, query: CallbackQuery) -> str: + user = query.from_user + user_id = int(query.data.split("(")[1].split(")")[0]) + chat = query.message.chat + + if sql.remove_warn(user_id, chat.id): + await query.message.edit_text(f"Warn removed by {user.mention}.") + warned = await client.get_users(user_id) + return f"<b>{html.escape(chat.title)}:</b>\n#UNWARN\n<b>Admin:</b> {user.mention}\n<b>User:</b> {warned.mention}" + else: + await query.answer("User already has no warns.", show_alert=True) + return "" + +@pbot.on_message(filters.command(["warn", "dwarn"]) & filters.group) +@user_admin +@can_restrict +@loggable +async def warn_user(client: Client, message: Message) -> str: + user_id, reason = await extract_user_and_text(message, message.command[1:]) + if message.command[0].startswith("d") and message.reply_to_message: + await message.reply_to_message.delete() + + if not user_id: + await message.reply_text("Invalid user!") + return "" + + warned = await client.get_users(user_id) + return await warn(warned, message.chat.id, reason, message, message.from_user) + +@pbot.on_message(filters.command(["resetwarn", "resetwarns"]) & filters.group) +@user_admin +@bot_admin +@loggable +async def reset_warns(client: Client, message: Message) -> str: + user_id = await extract_user(message, message.command[1:]) + if not user_id: + await message.reply_text("No user designated!") + return "" + + sql.reset_warns(user_id, message.chat.id) + await message.reply_text("Warns reset!") + warned = await client.get_users(user_id) + return f"<b>{html.escape(message.chat.title)}:</b>\n#RESET_WARN\n<b>Admin:</b> {message.from_user.mention}\n<b>User:</b> {warned.mention}" + +@pbot.on_message(filters.command("warns") & filters.group) +async def warns(client: Client, message: Message): + user_id = await extract_user(message, message.command[1:]) or message.from_user.id + result = sql.get_warns(user_id, message.chat.id) + limit, _ = sql.get_warn_setting(message.chat.id) + + if result and result[0] != 0: + num, reasons = result + text = f"This user has {num}/{limit} warns." + if reasons: + text += "\nReasons:" + for r in reasons: + text += f"\n • {r}" + await message.reply_text(text) + else: + await message.reply_text("This user has no warns.") + +@pbot.on_message(filters.command("addwarn") & filters.group) +@user_admin +async def add_warn_filter(client: Client, message: Message): + args = message.text.split(None, 1) + if len(args) < 2: return + extracted = split_quotes(args[1]) + if len(extracted) < 2: return + + keyword, content = extracted[0].lower(), extracted[1] + sql.add_warn_filter(message.chat.id, keyword, content) + await message.reply_text(f"Warn filter added for '{keyword}'!") + +@pbot.on_message(filters.command(["nowarn", "stopwarn"]) & filters.group) +@user_admin +async def remove_warn_filter(client: Client, message: Message): + args = message.text.split(None, 1) + if len(args) < 2: return + keyword = split_quotes(args[1])[0] + if sql.remove_warn_filter(message.chat.id, keyword): + await message.reply_text(f"Stopped warning for '{keyword}'.") + else: + await message.reply_text("Filter not found.") + +@pbot.on_message(filters.command("warnlimit") & filters.group) +@user_admin +@loggable +async def set_warn_limit(client: Client, message: Message) -> str: + args = message.command[1:] + if args and args[0].isdigit(): + limit = int(args[0]) + if limit < 3: + await message.reply_text("Min limit is 3.") + return "" + sql.set_warn_limit(message.chat.id, limit) + await message.reply_text(f"Limit set to {limit}.") + return f"<b>{html.escape(message.chat.title)}:</b>\n#SET_WARN_LIMIT\n<b>Admin:</b> {message.from_user.mention}\nSet limit to {limit}" + + limit, _ = sql.get_warn_setting(message.chat.id) + await message.reply_text(f"Current limit is {limit}.") + return "" + +@pbot.on_message(filters.command("strongwarn") & filters.group) +@user_admin +async def set_warn_strength(client: Client, message: Message): + args = message.command[1:] + if args: + if args[0].lower() in ("on", "yes"): + sql.set_warn_strength(message.chat.id, False) # False = ban + await message.reply_text("Strong warns (ban) enabled.") + else: + sql.set_warn_strength(message.chat.id, True) # True = punch + await message.reply_text("Strong warns disabled (punch).") + else: + _, soft = sql.get_warn_setting(message.chat.id) + await message.reply_text(f"Warns set to {'punch' if soft else 'ban'}.") + +@pbot.on_message(filters.text & filters.group, group=WARN_HANDLER_GROUP) +async def warn_filter_msg(client: Client, message: Message): + chat_id = message.chat.id + if not message.from_user: return + triggers = sql.get_chat_warn_triggers(chat_id) + if not triggers: return + + for trigger in triggers: + if re.search(rf"( |^|[^\w]){re.escape(trigger)}( |$|[^\w])", message.text, flags=re.IGNORECASE): + filt = sql.get_warn_filter(chat_id, trigger) + await warn(message.from_user, chat_id, filt.reply, message) + break + +__mod_name__ = "Warns" +__help__ = """ + • `/warns`: Check user warns. + • `/warn`: Warn a user. + • `/resetwarn`: Reset user warns. + • `/addwarn <keyword> <reply>`: Alert on keyword. + • `/nowarn <keyword>`: Stop alert. + • `/warnlimit <num>`: Set limit. + • `/strongwarn <on/off>`: Ban or kick on limit. +""" diff --git a/QueenNoxi/modules/weather.py b/QueenNoxi/modules/weather.py new file mode 100644 index 0000000000000000000000000000000000000000..88c4fd6148db4105ab694591f67f056a86a72f6e --- /dev/null +++ b/QueenNoxi/modules/weather.py @@ -0,0 +1,29 @@ +import io +import aiohttp +from pyrogram import filters +from QueenNoxi import pbot as app + +@app.on_message(filters.command("weather")) +async def _(_, message): + if getattr(message, "forward_from", None): + return + if len(message.command) == 1: + await message.reply("Please specify a city! e.g., /weather London") + return + sample_url = "https://wttr.in/{}.png" + input_str = message.text.split(None, 1)[1] + async with aiohttp.ClientSession() as session: + response_api_zero = await session.get(sample_url.format(input_str)) + response_api = await response_api_zero.read() + with io.BytesIO(response_api) as out_file: + out_file.name = "weather.png" + await message.reply_photo(photo=out_file) + +__help__ = """ +ɪ ᴄᴀɴ ғɪɴᴅ ᴡᴇᴀᴛʜᴇʀ ᴏғ ᴀʟʟ ᴄɪᴛɪᴇs + + ❍ /weather <ᴄɪᴛʏ>*:* ᴀᴅᴠᴀɴᴄᴇᴅ ᴡᴇᴀᴛʜᴇʀ ᴍᴏᴅᴜʟᴇ, ᴜsᴀɢᴇ sᴀᴍᴇ ᴀs /ᴡᴇᴀᴛʜᴇʀ + ❍ /weather ᴍᴏᴏɴ*:* ɢᴇᴛ ᴛʜᴇ ᴄᴜʀʀᴇɴᴛ sᴛᴀᴛᴜs ᴏғ ᴍᴏᴏɴ +""" + +__mod_name__ = "Wᴇᴀᴛʜᴇʀ" diff --git a/QueenNoxi/modules/webss.py b/QueenNoxi/modules/webss.py new file mode 100644 index 0000000000000000000000000000000000000000..32777fb0f77a77281367708307b2b990dbe69fdf --- /dev/null +++ b/QueenNoxi/modules/webss.py @@ -0,0 +1,85 @@ +from base64 import b64decode +from inspect import getfullargspec +from io import BytesIO + +from pyrogram import filters +from pyrogram.types import Message + +from QueenNoxi import pbot as app +from QueenNoxi.utils.post import post + + +async def take_screenshot(url: str, full: bool = False): + url = "https://" + url if not url.startswith("http") else url + payload = { + "url": url, + "width": 1920, + "height": 1080, + "scale": 1, + "format": "jpeg", + } + if full: + payload["full"] = True + data = await post( + "https://webscreenshot.vercel.app/api", + data=payload, + ) + if "image" not in data: + return None + b = data["image"].replace("data:image/jpeg;base64,", "") + file = BytesIO(b64decode(b)) + file.name = "webss.jpg" + return file + + +async def eor(msg: Message, **kwargs): + func = ( + (msg.edit_text if msg.from_user.is_self else msg.reply) + if msg.from_user + else msg.reply + ) + spec = getfullargspec(func.__wrapped__).args + return await func(**{k: v for k, v in kwargs.items() if k in spec}) + + +@app.on_message(filters.command(["webss", "ss", "webshot"])) +async def take_ss(_, message: Message): + if len(message.command) < 2: + return await eor(message, text="ɢɪᴠᴇ ᴀ ᴜʀʟ ᴛᴏ ғᴇᴛᴄʜ sᴄʀᴇᴇɴsʜᴏᴛ.") + + if len(message.command) == 2: + url = message.text.split(None, 1)[1] + full = False + elif len(message.command) == 3: + url = message.text.split(None, 2)[1] + full = message.text.split(None, 2)[2].lower().strip() in [ + "yes", + "y", + "1", + "true", + ] + else: + return await eor(message, text="ɪɴᴠᴀʟɪᴅ ᴄᴏᴍᴍᴀɴᴅ.") + + m = await eor(message, text="ᴄᴀᴘᴛᴜʀɪɴɢ sᴄʀᴇᴇɴsʜᴏᴛ...") + + try: + photo = await take_screenshot(url, full) + if not photo: + return await m.edit("ғᴀɪʟᴇᴅ ᴛᴏ ᴛᴀᴋᴇ sᴄʀᴇᴇɴsʜᴏᴛ.") + + m = await m.edit("ᴜᴘʟᴏᴀᴅɪɴɢ...") + + if not full: + await message.reply_document(photo) + else: + await message.reply_document(photo) + await m.delete() + except Exception as e: + await m.edit(str(e)) + + +__help__ = """ +» /webss *:* Sᴇɴᴅs ᴛʜᴇ sᴄʀᴇᴇɴsʜᴏᴛ ᴏғ ᴛʜᴇ ɢɪᴠᴇɴ ᴜʀʟ. +""" +__mod_name__ = "Wᴇʙsʜᴏᴛ" diff --git a/QueenNoxi/modules/welcome.py b/QueenNoxi/modules/welcome.py new file mode 100644 index 0000000000000000000000000000000000000000..f0615d595aa2200b960d1495ed83a04ebb82ba7e --- /dev/null +++ b/QueenNoxi/modules/welcome.py @@ -0,0 +1,252 @@ +import html +import random +import re +import time +import asyncio +from pyrogram import filters, Client, enums +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + CallbackQuery, + ChatPermissions +) +from pyrogram.errors import RPCError + +import QueenNoxi +import QueenNoxi.modules.sql.welcome_sql as sql +from QueenNoxi import ( + DEMONS, + DEV_USERS, + DRAGONS, + EVENT_LOGS, + LOGGER, + OWNER_IDS, + TIGERS, + WOLVES, + pbot, + BOT_ID +) +from QueenNoxi.modules.helper_funcs.chat_status import ( + is_user_ban_protected, + user_admin, + bot_admin, + can_restrict, + connection_status +) +from QueenNoxi.modules.helper_funcs.misc import build_keyboard, revert_buttons +from QueenNoxi.modules.helper_funcs.msg_types import get_welcome_type, Types +from QueenNoxi.modules.helper_funcs.string_handling import ( + escape_invalid_curly_brackets, + markdown_parser, +) +from QueenNoxi.modules.log_channel import loggable +from QueenNoxi.modules.sql.global_bans_sql import is_user_gbanned + +VALID_WELCOME_FORMATTERS = [ + "first", + "last", + "fullname", + "username", + "id", + "count", + "chatname", + "mention", +] + +# Verified user waitlist (for captcha) +VERIFIED_USER_WAITLIST = {} + +async def send(message: Message, text: str, keyboard: InlineKeyboardMarkup, backup_message: str): + chat = message.chat + cleanserv = sql.clean_service(chat.id) + reply_to = message.id if not cleanserv else None + + if cleanserv: + try: + await message.delete() + except RPCError: + pass + + try: + return await message.reply_text( + text, + reply_markup=keyboard, + disable_web_page_preview=True, + reply_to_message_id=reply_to + ) + except RPCError as excp: + LOGGER.error(f"Error sending welcome: {excp}") + return await message.reply_text(backup_message) + +@pbot.on_message(filters.new_chat_members & filters.group) +@loggable +async def new_member(client: Client, message: Message): + chat = message.chat + new_members = message.new_chat_members + + should_welc, cust_welcome, cust_content, welc_type = sql.get_welc_pref(chat.id) + welc_mutes = sql.welcome_mutes(chat.id) + + for new_mem in new_members: + if new_mem.id == BOT_ID: + # Bot joined a new chat + try: + await client.send_message( + EVENT_LOGS, + f"#NEW_GROUP\n**Group Name:** {html.escape(chat.title)}\n**Chat ID:** `{chat.id}`" + ) + except Exception as e: + LOGGER.warning(f"Could not send new group log to EVENT_LOGS: {e}") + await message.reply_text("Watashi ga kita!") + continue + + if await is_user_gbanned(new_mem.id): + await chat.ban_member(new_mem.id) + continue + + # Special welcomes for devs/sudos etc. + if new_mem.id in OWNER_IDS: + await message.reply_text("The King has arrived!") + continue + elif new_mem.id in DEV_USERS: + await message.reply_text("One of my creators joined!") + continue + + if should_welc: + buttons = sql.get_welc_buttons(chat.id) + keyb = build_keyboard(buttons) + keyboard = InlineKeyboardMarkup(keyb) + + first_name = new_mem.first_name or "User" + last_name = new_mem.last_name or "" + fullname = f"{first_name} {last_name}".strip() + count = await chat.get_member_count() + mention = new_mem.mention + username = f"@{new_mem.username}" if new_mem.username else mention + + if cust_welcome: + if cust_welcome == sql.DEFAULT_WELCOME: + cust_welcome = random.choice(sql.DEFAULT_WELCOME_MESSAGES).format(first=first_name) + + res = cust_welcome.format( + first=first_name, + last=last_name or first_name, + fullname=fullname, + username=username, + mention=mention, + count=count, + chatname=chat.title, + id=new_mem.id + ) + else: + res = random.choice(sql.DEFAULT_WELCOME_MESSAGES).format(first=first_name) + + if welc_type == Types.TEXT or welc_type == Types.BUTTON_TEXT: + sent = await send(message, res, keyboard, "Welcome!") + else: + # Handle media welcomes + sent = await client.send_cached_media( + chat.id, + cust_content, + caption=res, + reply_markup=keyboard + ) + + # Clean previous welcome + prev_welc = sql.get_clean_pref(chat.id) + if prev_welc: + try: + await client.delete_messages(chat.id, prev_welc) + except RPCError: + pass + if sent: + sql.set_clean_welcome(chat.id, sent.id) + + # Handle Welcome Mutes (Captcha) + if welc_mutes and not await is_user_ban_protected(chat, new_mem.id): + if welc_mutes == "soft": + await chat.restrict_member(new_mem.id, ChatPermissions(can_send_messages=True, can_send_media_messages=False)) + elif welc_mutes == "strong": + # Implementation of captcha button + pass + +@pbot.on_message(filters.left_chat_member & filters.group) +async def left_member(client: Client, message: Message): + chat = message.chat + user = message.left_chat_member + + if user.id == BOT_ID: + return + + should_goodbye, cust_goodbye, goodbye_type = sql.get_gdbye_pref(chat.id) + if should_goodbye: + first_name = user.first_name or "User" + res = cust_goodbye.format(first=first_name) if cust_goodbye else f"Goodbye {first_name}!" + await message.reply_text(res) + +@pbot.on_message(filters.command("welcome") & filters.group) +@user_admin +async def welcome(client: Client, message: Message): + args = message.command[1:] + chat = message.chat + + if not args: + pref, _, _, _ = sql.get_welc_pref(chat.id) + await message.reply_text(f"Welcome preference is set to: `{pref}`") + return + + if args[0].lower() in ("on", "yes"): + sql.set_welc_preference(chat.id, True) + await message.reply_text("I'll greet members when they join!") + elif args[0].lower() in ("off", "no"): + sql.set_welc_preference(chat.id, False) + await message.reply_text("I'll stop welcoming people.") + +@pbot.on_message(filters.command("setwelcome") & filters.group) +@user_admin +async def set_welcome_msg(client: Client, message: Message): + chat = message.chat + text, data_type, content, buttons = await get_welcome_type(message) + + if not data_type: + await message.reply_text("You didn't specify what to reply with!") + return + + sql.set_custom_welcome(chat.id, content, text, data_type, buttons) + await message.reply_text("Successfully set custom welcome message!") + +@pbot.on_message(filters.command("resetwelcome") & filters.group) +@user_admin +async def reset_welcome(client: Client, message: Message): + chat = message.chat + sql.set_custom_welcome(chat.id, None, sql.DEFAULT_WELCOME, Types.TEXT) + await message.reply_text("Successfully reset welcome message to default!") + +@pbot.on_message(filters.command("cleanservice") & filters.group) +@user_admin +async def cleanservice(client: Client, message: Message): + args = message.command[1:] + chat = message.chat + if not args: + pref = sql.clean_service(chat.id) + await message.reply_text(f"Clean service is currently: `{pref}`") + return + + if args[0].lower() in ("on", "yes"): + sql.set_clean_service(chat.id, True) + await message.reply_text("I will now clean service messages!") + else: + sql.set_clean_service(chat.id, False) + await message.reply_text("I will no longer clean service messages.") + +__mod_name__ = "Welcome" +__help__ = """ +Manage welcome and goodbye messages in your group. + +**Commands:** +• `/welcome <on/off>`: Toggle greetings +• `/setwelcome <msg>`: Set custom welcome +• `/resetwelcome`: Reset welcome +• `/cleanservice <on/off>`: Clean join/leave messages +""" diff --git a/QueenNoxi/modules/whois.py b/QueenNoxi/modules/whois.py new file mode 100644 index 0000000000000000000000000000000000000000..b582d63a26f509db514b694afebde7d64688cedc --- /dev/null +++ b/QueenNoxi/modules/whois.py @@ -0,0 +1,87 @@ +from datetime import datetime + +from pyrogram import filters +from pyrogram.errors import PeerIdInvalid +from pyrogram.types import Message, User + +from QueenNoxi import pbot + + +def ReplyCheck(message: Message): + reply_id = None + + if message.reply_to_message: + reply_id = message.reply_to_message.message_id + + elif not message.from_user.is_self: + reply_id = message.message_id + + return reply_id + + +infotext = ( + "[{full_name}](tg://user?id={user_id})\n\n" + " ➻ ᴜsᴇʀ ɪᴅ: `{user_id}`\n" + " ➻ ғɪʀsᴛ ɴᴀᴍᴇ: `{first_name}`\n" + " ➻ ʟᴀsᴛ ɴᴀᴍᴇ: `{last_name}`\n" + " ➻ ᴜsᴇʀɴᴀᴍᴇ: `@{username}`\n" + " ➻ ʟᴀsᴛ sᴇᴇɴ: `{last_online}`" +) + + +def LastOnline(user: User): + if user.is_bot: + return "" + elif user.status == "recently": + return "ʀᴇᴄᴇɴᴛʟʏ" + elif user.status == "within_week": + return "ᴡɪᴛʜɪɴ ᴛʜᴇ ʟᴀsᴛ ᴡᴇᴇᴋ" + elif user.status == "within_month": + return "ᴡɪᴛʜɪɴ ᴛʜᴇ ʟᴀsᴛ ᴍᴏɴᴛʜ" + elif user.status == "long_time_ago": + return "ᴀ ʟᴏɴɢ ᴛɪᴍᴇ ᴀɢᴏ :(" + elif user.status == "online": + return "ᴄᴜʀʀᴇɴᴛʟʏ ᴏɴʟɪɴᴇ" + elif user.status == "offline": + return datetime.fromtimestamp(user.status.date).strftime( + "%a, %d %b %Y, %H:%M:%S" + ) + + +def FullName(user: User): + return user.first_name + " " + user.last_name if user.last_name else user.first_name + + +@pbot.on_message(filters.command("whois")) +async def whois(client, message): + cmd = message.command + if not message.reply_to_message and len(cmd) == 1: + get_user = message.from_user.id + elif len(cmd) == 1: + get_user = message.reply_to_message.from_user.id + elif len(cmd) > 1: + get_user = cmd[1] + try: + get_user = int(cmd[1]) + except ValueError: + pass + try: + user = await client.get_users(get_user) + except PeerIdInvalid: + await message.reply("I don't know that user.") + return + desc = await client.get_chat(get_user) + desc = desc.description + await message.reply_text( + infotext.format( + full_name=FullName(user), + user_id=user.id, + user_dc=user.dc_id, + first_name=user.first_name, + last_name=user.last_name if user.last_name else "", + username=user.username if user.username else "", + last_online=LastOnline(user), + bio=desc if desc else "`ᴇᴍᴩᴛʏ.`", + ), + disable_web_page_preview=True, + ) diff --git a/QueenNoxi/modules/wiki.py b/QueenNoxi/modules/wiki.py new file mode 100644 index 0000000000000000000000000000000000000000..882194a18e4edb36828e503dda21cd269d7171f2 --- /dev/null +++ b/QueenNoxi/modules/wiki.py @@ -0,0 +1,57 @@ +import wikipedia +import html +import os +from pyrogram import filters, Client, enums +from pyrogram.types import Message +from wikipedia.exceptions import DisambiguationError, PageError + +from QueenNoxi import pbot +from QueenNoxi.modules.disable import DisableAbleCommandHandler + +@pbot.on_message(filters.command("wiki")) +async def wiki(client: Client, message: Message): + if len(message.command) < 2: + await message.reply_text("Please provide logic for what to search on Wikipedia!") + return + + search = message.text.split(None, 1)[1] + try: + res = wikipedia.summary(search) + except DisambiguationError as e: + await message.reply_text( + f"Disambiguated pages found! Adjust your query accordingly.\n<i>{e}</i>", + parse_mode=enums.ParseMode.HTML, + ) + return + except PageError as e: + await message.reply_text( + f"<code>{e}</code>", parse_mode=enums.ParseMode.HTML + ) + return + except Exception as e: + await message.reply_text(f"Error: {e}") + return + + if res: + result = f"<b>{html.escape(search)}</b>\n\n" + result += f"<i>{html.escape(res)}</i>\n" + result += f"""<a href="https://en.wikipedia.org/wiki/{search.replace(" ", "%20")}">Read more...</a>""" + + if len(result) > 4000: + file_path = f"wiki_{message.from_user.id}.txt" + with open(file_path, "w", encoding="utf-8") as f: + f.write(result) + await message.reply_document(file_path, caption="Result is too long, sending as file.") + if os.path.exists(file_path): + os.remove(file_path) + else: + await message.reply_text( + result, + parse_mode=enums.ParseMode.HTML, + disable_web_page_preview=True + ) + +__help__ = """ +» /wiki (text) : Search about the given text on Wikipedia. +""" +__mod_name__ = "Wiki" diff --git a/QueenNoxi/modules/writetool.py b/QueenNoxi/modules/writetool.py new file mode 100644 index 0000000000000000000000000000000000000000..bd9d52bb203bf300481abeca6587f634be787bab --- /dev/null +++ b/QueenNoxi/modules/writetool.py @@ -0,0 +1,55 @@ +"""MIT License + +Copyright (c) 2023-24 Noob-QueenNoxi + + GITHUB: NOOB-MUKESH + TELEGRAM: @MR_SUKKUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.""" +from pyrogram import filters +from pyrogram.types import Message +from QueenNoxi import BOT_NAME, BOT_USERNAME +from QueenNoxi import pbot as queennoxi +import requests +@queennoxi.on_message(filters.command("write")) +async def handwrite(_, message: Message): + if message.reply_to_message: + text = message.reply_to_message.text + else: + text =message.text.split(None, 1)[1] + m =await message.reply_text( "`Please wait...,\n\nWriting your text...`") + + write = requests.get(f"https://apis.xditya.me/write?text={text}").url + + caption = f""" +sᴜᴄᴇssғᴜʟʟʏ ᴡʀɪᴛᴛᴇɴ ᴛᴇxᴛ 💘 +✨ **ᴡʀɪᴛᴛᴇɴ ʙʏ :** [{BOT_NAME}](https://t.me/{BOT_USERNAME}) +🥀 **ʀᴇǫᴜᴇsᴛᴇᴅ ʙʏ :** {message.from_user.mention} +""" + await m.delete() + await message.reply_photo(photo=write,caption=caption) + +__mod_name__ = "WʀɪᴛᴇTᴏᴏʟ" + +__help__ = """ + + ᴡʀɪᴛᴇs ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ ᴏɴ ᴡʜɪᴛᴇ ᴘᴀɢᴇ ᴡɪᴛʜ ᴀ ᴘᴇɴ 🖊 + +❍ /write <ᴛᴇxᴛ> *:* ᴡʀɪᴛᴇs ᴛʜᴇ ɢɪᴠᴇɴ ᴛᴇxᴛ. + """ diff --git a/QueenNoxi/modules/zip.py b/QueenNoxi/modules/zip.py new file mode 100644 index 0000000000000000000000000000000000000000..aa176ee7ad4e16c0ec31e588e664a2b805d79674 --- /dev/null +++ b/QueenNoxi/modules/zip.py @@ -0,0 +1,88 @@ +import os +import zipfile +from pyrogram import filters +from QueenNoxi import pbot as app, TEMP_DOWNLOAD_DIRECTORY + +async def check_admin(chat_id, user_id): + try: + member = await app.get_chat_member(chat_id, user_id) + return member.privileges is not None + except Exception: + return False + +@app.on_message(filters.command("zip")) +async def zip_cmd(_, message): + if not message.reply_to_message: + return await message.reply("Reply to a file to compress it.") + if message.chat.type.name != "PRIVATE": + if not await check_admin(message.chat.id, message.from_user.id): + return await message.reply("Hey, you are not admin. You can't use this command, But you can use in my PM 🙂") + + mone = await message.reply("⏳️ Please wait...") + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + + try: + downloaded_file_name = await message.reply_to_message.download(TEMP_DOWNLOAD_DIRECTORY) + directory_name = downloaded_file_name + zipfile.ZipFile(directory_name + ".zip", "w", zipfile.ZIP_DEFLATED).write(directory_name) + await message.reply_document( + document=directory_name + ".zip", + reply_to_message_id=message.id + ) + os.remove(directory_name) + os.remove(directory_name + ".zip") + await mone.delete() + except Exception as e: + await mone.edit(str(e)) + + +@app.on_message(filters.command("unzip")) +async def unzip_cmd(_, message): + if not message.reply_to_message: + return await message.reply("Reply to a zip file.") + if message.chat.type.name != "PRIVATE": + if not await check_admin(message.chat.id, message.from_user.id): + return await message.reply("Hey, You are not admin. You can't use this command, But you can use in my PM 🙂") + + mone = await message.reply("Processing...") + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + + extracted = TEMP_DOWNLOAD_DIRECTORY + "extracted/" + if not os.path.isdir(extracted): + os.makedirs(extracted) + + try: + downloaded_file_name = await message.reply_to_message.download(TEMP_DOWNLOAD_DIRECTORY) + with zipfile.ZipFile(downloaded_file_name, "r") as zip_ref: + zip_ref.extractall(extracted) + + await mone.edit("Unzipping now 😌") + + def get_lst_of_files(input_directory, output_lst): + for root, dirs, files in os.walk(input_directory): + for file in files: + output_lst.append(os.path.join(root, file)) + return output_lst + + filename = sorted(get_lst_of_files(extracted, [])) + for single_file in filename: + await app.send_document( + message.chat.id, + document=single_file, + reply_to_message_id=message.id + ) + os.remove(single_file) + os.remove(downloaded_file_name) + await mone.delete() + except Exception as e: + await mone.edit(str(e)) + +__help__ = """ +ʜᴇʏ ɪ ᴄᴀɴ ᴄᴏɴᴠᴇʀᴛ ғɪʟᴇs ʜᴇʀᴇ.. + ❍ /zip *:* ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇ ᴛᴏ ᴄᴏᴍᴘʀᴇss ɪᴛ ɪɴ .ᴢɪᴘ ғᴏʀᴍᴀᴛ + ❍ /unzip *:* ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇ ᴛᴏ ᴅᴇᴄᴏᴍᴘʀᴇss ɪᴛ ғʀᴏᴍ ᴛʜᴇ .ᴢɪᴘ ғᴏʀᴍᴀᴛ +""" + +__mod_name__ = "Zɪᴘᴘᴇʀ​" diff --git a/QueenNoxi/modules/zombies.py b/QueenNoxi/modules/zombies.py new file mode 100644 index 0000000000000000000000000000000000000000..98d899ae11738a822df093af9bf59e70c9349031 --- /dev/null +++ b/QueenNoxi/modules/zombies.py @@ -0,0 +1,86 @@ + +from pyrogram import filters +from pyrogram.errors import ChatAdminRequired, UserAdminInvalid +from pyrogram.types import ChatPermissions +from pyrogram.enums import ChatMembersFilter +import requests,asyncio +from QueenNoxi import pbot as app,DEV_USERS,OWNER_ID,DEMONS,DRAGONS + + +OFFICERS = [OWNER_ID] + DEV_USERS + DRAGONS + DEMONS + +# Check if user has admin rights +async def is_administrator(user_id: int, message): + admin = False + administrators = [] + async for m in app.get_chat_members(message.chat.id, filter=ChatMembersFilter.ADMINISTRATORS): + administrators.append(m) + for user in administrators: + if user.user.id == user_id or user_id in OFFICERS: + admin = True + break + return admin + +@app.on_message(filters.command("zombies", prefixes=["/","!"]) & filters.group) +async def rm_deletedacc(client, message): + con = message.text.split(" ", 1)[1].lower() if len(message.command) > 1 else "" + del_u = 0 + del_status = "Group cleaned, 0 deleted accounts found." + + if con != "clean": + kontol = await message.reply("Searching for deleted accounts...") + + participants=[] + async for member in app.get_chat_members(message.chat.id): + participants.append(member) + + + for user in participants: + if user.user.is_deleted: + print(user.user.is_deleted) + del_u += 1 + await asyncio.sleep(1) + if del_u > 0: + del_status = ( + f"Searching... {del_u} Deleted account(s) Zombie on this group, " + "Clean it with command `/zombies clean`" + ) + return await kontol.edit(del_status) + + chat = await client.get_chat(message.chat.id) + + admin=await is_administrator(message.from_user.id,message) + + if not admin: + return await message.reply("Sorry, you are not an admin!") + + memek = await message.reply("Removing deleted accounts...") + participants=[] + async for member in app.get_chat_members(message.chat.id): + participants.append(member) + + for user in participants: + if user.user.is_deleted: + print(user.user.is_deleted) + try: + + await client.ban_chat_member(message.chat.id, user.user.id) + await client.unban_chat_member(message.chat.id, user.user.id) + + except ChatAdminRequired: + return await message.edit("Do not have permission to ban in this group") + except UserAdminInvalid: + del_u -= 1 + await asyncio.sleep(1) + + if del_u > 0: + del_status = f"Cleaned {del_u} Zombies" + + await memek.edit(del_status) + +help_text = """ +*ʀᴇᴍᴏᴠᴇ ᴅᴇʟᴇᴛᴇᴅ ᴀᴄᴄᴏᴜɴᴛs* +❍ /zombies : starts searching for deleted accounts in the group. +❍ /zombies clean : removes the deleted accounts from the group. +""" +__mod_name__ = "Zᴏᴍʙɪᴇ" diff --git a/QueenNoxi/resources/blank_background.png b/QueenNoxi/resources/blank_background.png new file mode 100644 index 0000000000000000000000000000000000000000..394f50f76dfc70e2124b39d32d19362dd1c389fe --- /dev/null +++ b/QueenNoxi/resources/blank_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c77481114376d6ef4f84db34882514a3e3598eaea25c5603b826f765f2085ef +size 889 diff --git a/QueenNoxi/resources/default.ttf b/QueenNoxi/resources/default.ttf new file mode 100644 index 0000000000000000000000000000000000000000..61f1b31045f110cbe564630c455487bda4821c68 --- /dev/null +++ b/QueenNoxi/resources/default.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a9c6fa237d533769265554e5b4f91adf233f9dfed350f6ae307dbf4d17012db +size 91904 diff --git a/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER AT.otf b/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER AT.otf new file mode 100644 index 0000000000000000000000000000000000000000..e7060503bd9413eeb0601fe300754d5a6f8a6126 --- /dev/null +++ b/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER AT.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efe44ac1f2d1db3f2959f0cf495e00f138d782af14a628032f9a634208fc9be3 +size 101068 diff --git a/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER AT.ttf b/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER AT.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0a07503e65605dffc75caa1e617f36375f516cb6 --- /dev/null +++ b/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER AT.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026d2bc24998acbf542d3a7d750c64755ff34cb8fb5fec6875a4f0d628978133 +size 167120 diff --git a/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER BI.otf b/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER BI.otf new file mode 100644 index 0000000000000000000000000000000000000000..b3ff0c1f60dbf17513b3ac8b18fd6ad699e40dc5 --- /dev/null +++ b/QueenNoxi/resources/fonts/AVENGEANCE HEROIC AVENGER BI.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2d958e877dfd57b3216d9907eb756ab21a11f05f3ee3bdd44a746bd1f3cb0b +size 101056 diff --git a/QueenNoxi/resources/fonts/Big Space.otf b/QueenNoxi/resources/fonts/Big Space.otf new file mode 100644 index 0000000000000000000000000000000000000000..04bfaa804d2a09affb019b569b9d6f3fe15cd50a --- /dev/null +++ b/QueenNoxi/resources/fonts/Big Space.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a7ffc34acac7a33b2ad61252fa700770d10be7b1f19a66633331681af297296 +size 45708 diff --git a/QueenNoxi/resources/fonts/CRAWLER-RegularDEMO.ttf b/QueenNoxi/resources/fonts/CRAWLER-RegularDEMO.ttf new file mode 100644 index 0000000000000000000000000000000000000000..018e509ed76d94e53d5c55b9c3654aaf3e08cb1f --- /dev/null +++ b/QueenNoxi/resources/fonts/CRAWLER-RegularDEMO.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:027247270344187686188dba69fbbb870a1ac085e19cdd182245ee12e8028a1d +size 84540 diff --git a/QueenNoxi/resources/fonts/CROWNT.TTF b/QueenNoxi/resources/fonts/CROWNT.TTF new file mode 100644 index 0000000000000000000000000000000000000000..4fb026e2d6e764d459423fb85f617b87138d3703 --- /dev/null +++ b/QueenNoxi/resources/fonts/CROWNT.TTF @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7f5d314aa8b44500da428d3b386692b09fd2429917045999e566d970036388 +size 19208 diff --git a/QueenNoxi/resources/fonts/Chopsic.otf b/QueenNoxi/resources/fonts/Chopsic.otf new file mode 100644 index 0000000000000000000000000000000000000000..f7fb542e190811dadca21f9fd878f7b32f41546b --- /dev/null +++ b/QueenNoxi/resources/fonts/Chopsic.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:781678382345c604694317b82e526fdf07d7c20140e648ebef09debc8a541d04 +size 24264 diff --git a/QueenNoxi/resources/fonts/Crozzoe-Personal-Use.otf b/QueenNoxi/resources/fonts/Crozzoe-Personal-Use.otf new file mode 100644 index 0000000000000000000000000000000000000000..6d6fb7fca4e18e8dce28680b357ca6df7eacbe4e --- /dev/null +++ b/QueenNoxi/resources/fonts/Crozzoe-Personal-Use.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:186ea9b397e082d4be919213bbff04f6a73135447799d7f844b8cb0c35997397 +size 128824 diff --git a/QueenNoxi/resources/fonts/DIGIT.ttf b/QueenNoxi/resources/fonts/DIGIT.ttf new file mode 100644 index 0000000000000000000000000000000000000000..df4c6a5067e47585f40f05520314f83683bcdd36 --- /dev/null +++ b/QueenNoxi/resources/fonts/DIGIT.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d330a549ac9e7e73b8514136989517ef0deacbd003a4f3d66a7252d64d55b31 +size 25480 diff --git a/QueenNoxi/resources/fonts/Damages-Italic.ttf b/QueenNoxi/resources/fonts/Damages-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..17292b01fc131db86a62e45878223b5502ac70b2 --- /dev/null +++ b/QueenNoxi/resources/fonts/Damages-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27ca143d7239e640f623ecab100b31588d590386efe710a6fe6fe47cb2c73d19 +size 21204 diff --git a/QueenNoxi/resources/fonts/Damages.ttf b/QueenNoxi/resources/fonts/Damages.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3eaa6e333f31dc2b9137909f85d9daa4d8580458 --- /dev/null +++ b/QueenNoxi/resources/fonts/Damages.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3437720ebea199a4082bc2c82d912fb451162b7368414b2552f8b587baa1a512 +size 27724 diff --git a/QueenNoxi/resources/fonts/Damages3D-Italic.ttf b/QueenNoxi/resources/fonts/Damages3D-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7a0b727203c9616230bccab97507d7984cef6ce4 --- /dev/null +++ b/QueenNoxi/resources/fonts/Damages3D-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:491fc1a6e8aadaf7e43f71d2058cde393039ae7a6e4eb6390ae2e7a2dca537d9 +size 25516 diff --git a/QueenNoxi/resources/fonts/Damages3D.ttf b/QueenNoxi/resources/fonts/Damages3D.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6881356a3ba0f84298af497e742ef479e6d5b84d --- /dev/null +++ b/QueenNoxi/resources/fonts/Damages3D.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85fc29fdffa03ccfec2738794d07f5c0f2c96f247a38a05dc50e45c16d12da16 +size 35588 diff --git a/QueenNoxi/resources/fonts/Damages3DFilled-Italic.ttf b/QueenNoxi/resources/fonts/Damages3DFilled-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..736184b1e0e969ab3bc1724ef5f01c0419884b4a --- /dev/null +++ b/QueenNoxi/resources/fonts/Damages3DFilled-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:422f0acc1be040a4b27b426a7f230bb97dfae5d4016ff7e0237ec57668513352 +size 34380 diff --git a/QueenNoxi/resources/fonts/Damages3DFilled.ttf b/QueenNoxi/resources/fonts/Damages3DFilled.ttf new file mode 100644 index 0000000000000000000000000000000000000000..339e47f8e504f6e37467a379a20df76a77f76aa1 --- /dev/null +++ b/QueenNoxi/resources/fonts/Damages3DFilled.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e1fd4522f32db371a57c7ad9e44061b993ee536571738f8db679638d6eef706 +size 51572 diff --git a/QueenNoxi/resources/fonts/Damar Kurung.otf b/QueenNoxi/resources/fonts/Damar Kurung.otf new file mode 100644 index 0000000000000000000000000000000000000000..e39220d5311e7daa640f380fc758c13c028b2b59 --- /dev/null +++ b/QueenNoxi/resources/fonts/Damar Kurung.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:284928db06d0713873543da929cdc96800f3435ff47ce160723dc57fe4d03ced +size 18620 diff --git a/QueenNoxi/resources/fonts/Damar Kurung.ttf b/QueenNoxi/resources/fonts/Damar Kurung.ttf new file mode 100644 index 0000000000000000000000000000000000000000..72f2afbc82cdedb3412594a284fdfdb866aa38b1 --- /dev/null +++ b/QueenNoxi/resources/fonts/Damar Kurung.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c639bbddbd4ae1f57eb09e52b18f7684ff24029fdf7ee544beabfe179610d76a +size 18620 diff --git a/QueenNoxi/resources/fonts/Dark Ministry.ttf b/QueenNoxi/resources/fonts/Dark Ministry.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f9c72f54ec5f2a7ca95ab02fa992a07ec383b82e --- /dev/null +++ b/QueenNoxi/resources/fonts/Dark Ministry.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:129a04efb659f2f3b6244410ce024505edbcd83d7a0060c5426c9561b6f6585b +size 8100 diff --git a/QueenNoxi/resources/fonts/Dark Seed.otf b/QueenNoxi/resources/fonts/Dark Seed.otf new file mode 100644 index 0000000000000000000000000000000000000000..dbbf4fec95c94f92b810e83c2efab8210ed236c7 --- /dev/null +++ b/QueenNoxi/resources/fonts/Dark Seed.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3032cb0a21d4b946bdb1e0a1a29c4017c706043db6aff95249e893d840c6429c +size 19952 diff --git a/QueenNoxi/resources/fonts/Dark.ttf b/QueenNoxi/resources/fonts/Dark.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3bc1bddff7c96d3b8e65f25b959d5c136771bdba --- /dev/null +++ b/QueenNoxi/resources/fonts/Dark.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70a0b32ade35a2c9513fbf8c81ae02d67c4d6675023caeade9d6e2c0a1b9d717 +size 22896 diff --git a/QueenNoxi/resources/fonts/Dash-Dermo.ttf b/QueenNoxi/resources/fonts/Dash-Dermo.ttf new file mode 100644 index 0000000000000000000000000000000000000000..527e09aed6731a12a5fc2aa395b90a6713c47ccc --- /dev/null +++ b/QueenNoxi/resources/fonts/Dash-Dermo.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:037bd4e92caae57cf6454b379acbae614a99eb04d4efe048f55f4d39974263bf +size 21136 diff --git a/QueenNoxi/resources/fonts/Dead Revolution.otf b/QueenNoxi/resources/fonts/Dead Revolution.otf new file mode 100644 index 0000000000000000000000000000000000000000..f4f02318e1f2b0fb242719335994949e6bb54542 --- /dev/null +++ b/QueenNoxi/resources/fonts/Dead Revolution.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd21158f78e61e673da9bc1fd084dade1ef3de4efeaf55a0018f3024e8027d67 +size 21260 diff --git a/QueenNoxi/resources/fonts/DezertDemoDash.ttf b/QueenNoxi/resources/fonts/DezertDemoDash.ttf new file mode 100644 index 0000000000000000000000000000000000000000..baa076edf99d42d4d50b5e25c83ee149bff454ce --- /dev/null +++ b/QueenNoxi/resources/fonts/DezertDemoDash.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54fe0bf3dc8942e20d3a8562ddf03ad263d8177024384f32fca8b59cbe0eb1fa +size 8808 diff --git a/QueenNoxi/resources/fonts/DezertDemoItalic.ttf b/QueenNoxi/resources/fonts/DezertDemoItalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0d0ed167f205050ee9ad2d6d338e2782c0129ebe --- /dev/null +++ b/QueenNoxi/resources/fonts/DezertDemoItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45afe04b1cbfc399fe6a054776066655cd02c76853f23067f4bb1ce3d5e31a2d +size 6740 diff --git a/QueenNoxi/resources/fonts/DezertDemoItalicDash.ttf b/QueenNoxi/resources/fonts/DezertDemoItalicDash.ttf new file mode 100644 index 0000000000000000000000000000000000000000..282d2b14207c8462075f331660331d040885fbb4 --- /dev/null +++ b/QueenNoxi/resources/fonts/DezertDemoItalicDash.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5001aac1e86d4a4e975f8f8b812afad16fb9beeca3bcb8cdea486ffac75bd13 +size 9364 diff --git a/QueenNoxi/resources/fonts/DezertDemoOutline.ttf b/QueenNoxi/resources/fonts/DezertDemoOutline.ttf new file mode 100644 index 0000000000000000000000000000000000000000..87ca689a992f9ce5f0d83f3deb772f1632bdbd15 --- /dev/null +++ b/QueenNoxi/resources/fonts/DezertDemoOutline.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c3211e4e11fb9f342917a7751eb44a6a0a1aaab4f118efa2a764437746bd31d +size 9808 diff --git a/QueenNoxi/resources/fonts/DezertDemoOutlineDash.ttf b/QueenNoxi/resources/fonts/DezertDemoOutlineDash.ttf new file mode 100644 index 0000000000000000000000000000000000000000..11379260e375c046955d34e4b7d6f8329e2fdbc4 --- /dev/null +++ b/QueenNoxi/resources/fonts/DezertDemoOutlineDash.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57490221c669fd055b3ba0a7cde2479272bff206c5a8cc695cf01edb60d1197c +size 13852 diff --git a/QueenNoxi/resources/fonts/DezertDemoRegular.ttf b/QueenNoxi/resources/fonts/DezertDemoRegular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0314933a9ea94e794334f31666614edd60ea436d --- /dev/null +++ b/QueenNoxi/resources/fonts/DezertDemoRegular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d32f2eadc78d259b32e0f0db83516e13c1fc4f3fc908968df81bb7b5d8ab6e37 +size 6312 diff --git a/QueenNoxi/resources/fonts/DisposableDroidBB_bld.ttf b/QueenNoxi/resources/fonts/DisposableDroidBB_bld.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f4249d6b47e5c4dea56e6738a865093f592215d0 --- /dev/null +++ b/QueenNoxi/resources/fonts/DisposableDroidBB_bld.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6be633b7cfde24055d1dcad7d55a539d12b6f42fb95660e607a58e0c1fb9bb0a +size 29236 diff --git a/QueenNoxi/resources/fonts/DragonForcE.ttf b/QueenNoxi/resources/fonts/DragonForcE.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8dfc545028d904e9cdf25894dc0a4aa25c9950aa --- /dev/null +++ b/QueenNoxi/resources/fonts/DragonForcE.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7013ee9141781afc55da59c518b9eb9dbab5f2953f59e384fd7a396191c0cab3 +size 17276 diff --git a/QueenNoxi/resources/fonts/Dramaga Demo.otf b/QueenNoxi/resources/fonts/Dramaga Demo.otf new file mode 100644 index 0000000000000000000000000000000000000000..22c2e53c9f2335ab968a726afc8a9e3cecdfe7fb --- /dev/null +++ b/QueenNoxi/resources/fonts/Dramaga Demo.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:678fdfe51959258e658ec028b0679fb32a26d9d56082c7c7a25aea09c7eca407 +size 11592 diff --git a/QueenNoxi/resources/fonts/Dramaga Demo.ttf b/QueenNoxi/resources/fonts/Dramaga Demo.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e89d96f49bf775a02f97db291c31007a837c4892 --- /dev/null +++ b/QueenNoxi/resources/fonts/Dramaga Demo.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05aebda0ab9f75837995de226d4353fd712af17e3e8a6144f53fab71095520a2 +size 43200 diff --git a/QueenNoxi/resources/fonts/Dreamscar.ttf b/QueenNoxi/resources/fonts/Dreamscar.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e73027866d6d892fe23b6f9b28d24459e31e7163 --- /dev/null +++ b/QueenNoxi/resources/fonts/Dreamscar.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58a92276a65f689fcf0d302b9fe776e391f76364a0178aa8fe6f2ac1caf7c0a9 +size 21244 diff --git a/QueenNoxi/resources/fonts/EVILDEAD.TTF b/QueenNoxi/resources/fonts/EVILDEAD.TTF new file mode 100644 index 0000000000000000000000000000000000000000..40cc89fb3960931c089da12b321742fc7046f1a6 --- /dev/null +++ b/QueenNoxi/resources/fonts/EVILDEAD.TTF @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f43bcb89e4fe14c599c435e1f7e67d171258edce121962944f4bd4c11611981 +size 16364 diff --git a/QueenNoxi/resources/fonts/Europhonic.otf b/QueenNoxi/resources/fonts/Europhonic.otf new file mode 100644 index 0000000000000000000000000000000000000000..8634dad7fa740799364c6875f36ae2940a58bf40 --- /dev/null +++ b/QueenNoxi/resources/fonts/Europhonic.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df37427b1a7a4b194ff51eef5a9ab6b4fb2fa1dfec3bc338286d1a2a6c70153d +size 19788 diff --git a/QueenNoxi/resources/fonts/Exorcista_-Jed_40.ttf b/QueenNoxi/resources/fonts/Exorcista_-Jed_40.ttf new file mode 100644 index 0000000000000000000000000000000000000000..81f1706b94d30ce6adcb002304e98fbbb7a98536 --- /dev/null +++ b/QueenNoxi/resources/fonts/Exorcista_-Jed_40.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5286304cbff96a126412ba8d0b719118be0e20d2cbacc3dc26ae57ba14c448df +size 17868 diff --git a/QueenNoxi/resources/fonts/Fire Flight FREE.otf b/QueenNoxi/resources/fonts/Fire Flight FREE.otf new file mode 100644 index 0000000000000000000000000000000000000000..32480c9d4159816b0e824cc3edb1b1747b0cc37c --- /dev/null +++ b/QueenNoxi/resources/fonts/Fire Flight FREE.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5abd9f08445b554b15c7cc10372157860e81ce12e995ec1632ebdcc1cd6f50e +size 21508 diff --git a/QueenNoxi/resources/fonts/Fire Flight FREE.ttf b/QueenNoxi/resources/fonts/Fire Flight FREE.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4aef36b659e0119de33e91645bb2e1a2d0c492a4 --- /dev/null +++ b/QueenNoxi/resources/fonts/Fire Flight FREE.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d43e88991f47f65ca422829c4cb520d80e0fd8ab597b2e4deb2ac9ca9fb0bb96 +size 51652 diff --git a/QueenNoxi/resources/fonts/FontRemix.ttf b/QueenNoxi/resources/fonts/FontRemix.ttf new file mode 100644 index 0000000000000000000000000000000000000000..351de7b5a976f3080edeae2d032165abbf647414 --- /dev/null +++ b/QueenNoxi/resources/fonts/FontRemix.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afbd30c1ad67286eff72df63b5d563a6d3827c01bca881013ae87982930ac369 +size 56364 diff --git a/QueenNoxi/resources/fonts/FontRemix2.ttf b/QueenNoxi/resources/fonts/FontRemix2.ttf new file mode 100644 index 0000000000000000000000000000000000000000..def5db2437da3a8342445b4603ac323390e45ba7 --- /dev/null +++ b/QueenNoxi/resources/fonts/FontRemix2.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f95c7f62a6f11a6d7662b1a0a00cdd731434eb66e9a802f14bd0fa4007410825 +size 34956 diff --git a/QueenNoxi/resources/fonts/Friend Head.otf b/QueenNoxi/resources/fonts/Friend Head.otf new file mode 100644 index 0000000000000000000000000000000000000000..ccfac2c762bd869a6d36334901eda5257b20a4cc --- /dev/null +++ b/QueenNoxi/resources/fonts/Friend Head.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2be2cdf8ab161d4693738248ae88297dadeff43807dec383841ea21dcde2263 +size 23912 diff --git a/QueenNoxi/resources/fonts/Frostbite Boss Fight.otf b/QueenNoxi/resources/fonts/Frostbite Boss Fight.otf new file mode 100644 index 0000000000000000000000000000000000000000..ff2fc25d720d8daadbd9f6dbb784b9331c63ecba --- /dev/null +++ b/QueenNoxi/resources/fonts/Frostbite Boss Fight.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dd0201d3d183e4f11a0f8c687841cddfd8d3ece7c18c7b6bbe1a1909bc9fa99 +size 25192 diff --git a/QueenNoxi/resources/fonts/Fucking Hostile.ttf b/QueenNoxi/resources/fonts/Fucking Hostile.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3d60672bc1147428e08e737a89d71372563a449a --- /dev/null +++ b/QueenNoxi/resources/fonts/Fucking Hostile.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff634ab7dd40bfaa427d9babcd1606a2bec6843ec4026b77b8538e12ecfe8eb5 +size 311856 diff --git a/QueenNoxi/resources/fonts/GRAMES.ttf b/QueenNoxi/resources/fonts/GRAMES.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ff636799e50aa094231e5b706ca8b477094da1c4 --- /dev/null +++ b/QueenNoxi/resources/fonts/GRAMES.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbee498be7b1b58085a17aab47e86d6dd0ea981b16fbfd44aec39832b6d5b32c +size 36276 diff --git a/QueenNoxi/resources/fonts/Gang Wolfik.ttf b/QueenNoxi/resources/fonts/Gang Wolfik.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fd9b0c8cb24c7b58cd6465cd624804c743847409 --- /dev/null +++ b/QueenNoxi/resources/fonts/Gang Wolfik.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8272ccd83f97be3e0f3d05b5ce68dfe8647da38859360292d21d53afcb79161d +size 28988 diff --git a/QueenNoxi/resources/fonts/Garda.ttf b/QueenNoxi/resources/fonts/Garda.ttf new file mode 100644 index 0000000000000000000000000000000000000000..29f073150a6e5a67d195454976a2b0fcfd4c1b83 --- /dev/null +++ b/QueenNoxi/resources/fonts/Garda.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159056d769a73c3a4f951143aead48fd775a0cb19b2e69b23565d6725bcc61b0 +size 5048 diff --git a/QueenNoxi/resources/fonts/Garreng-Personal-Use.otf b/QueenNoxi/resources/fonts/Garreng-Personal-Use.otf new file mode 100644 index 0000000000000000000000000000000000000000..4a0ad65ab2fba00de6009150a4f928a576b7d020 --- /dev/null +++ b/QueenNoxi/resources/fonts/Garreng-Personal-Use.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e617a60c869cc2e5c1ef3567020d361cccdf9050494b4dc7a81022240a03d293 +size 80276 diff --git a/QueenNoxi/resources/fonts/Geizer.otf b/QueenNoxi/resources/fonts/Geizer.otf new file mode 100644 index 0000000000000000000000000000000000000000..81f4f85d21a93bce39d8f700f5dab394b3f899c2 --- /dev/null +++ b/QueenNoxi/resources/fonts/Geizer.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:188b15b1f3b42c164bbb075026f7f37ae8240d7b8d3ae4e2922698ce602d1e90 +size 33072 diff --git a/QueenNoxi/resources/fonts/Georgent.otf b/QueenNoxi/resources/fonts/Georgent.otf new file mode 100644 index 0000000000000000000000000000000000000000..777d09bf6fb5475c8a21ebbdb9b39faf8ffc43e0 --- /dev/null +++ b/QueenNoxi/resources/fonts/Georgent.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e6c2ab6d696359515f582c8ead7a089774d671fbfab558de9786b9f65462d70 +size 43940 diff --git a/QueenNoxi/resources/fonts/Georgent.ttf b/QueenNoxi/resources/fonts/Georgent.ttf new file mode 100644 index 0000000000000000000000000000000000000000..572f866d2d6e2cdc9580f8cad96b45394804c39f --- /dev/null +++ b/QueenNoxi/resources/fonts/Georgent.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f55512b4e2ea387b1c115010ec5ec16c981e06fc5dd28157ef0104934d64620 +size 12232 diff --git a/QueenNoxi/resources/fonts/Hallowed Grad.otf b/QueenNoxi/resources/fonts/Hallowed Grad.otf new file mode 100644 index 0000000000000000000000000000000000000000..fb2bbbc2827ffed8878214752f6b6b93cd211c9b --- /dev/null +++ b/QueenNoxi/resources/fonts/Hallowed Grad.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3448e14d67b4147396442b84e87e0d2362fe9f86401c24cab621db7096b0847a +size 15428 diff --git a/QueenNoxi/resources/fonts/Harker Bold Italic.otf b/QueenNoxi/resources/fonts/Harker Bold Italic.otf new file mode 100644 index 0000000000000000000000000000000000000000..a3759e87f79550f9c1b6c711b5f8c20cfd0f3685 --- /dev/null +++ b/QueenNoxi/resources/fonts/Harker Bold Italic.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6e6fc02c4179508505d82266445ad8f078d64e5d73ffd1175a7d6644373db63 +size 30456 diff --git a/QueenNoxi/resources/fonts/Harker Italic.otf b/QueenNoxi/resources/fonts/Harker Italic.otf new file mode 100644 index 0000000000000000000000000000000000000000..8058902936283f4bfc060dd7ff4bc6ad57207250 --- /dev/null +++ b/QueenNoxi/resources/fonts/Harker Italic.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5cc97017e0eebdfefe3e2b7261eba759a6011b958de0d90b06b64a1c62ac07d +size 30456 diff --git a/QueenNoxi/resources/fonts/Maghrib.ttf b/QueenNoxi/resources/fonts/Maghrib.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0056cbcbd902b280c8ffcbafb1c6885bd50fc41c --- /dev/null +++ b/QueenNoxi/resources/fonts/Maghrib.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b1914953c7bdb2bf68c68fca275ef63c4aede5ec084c9788dc1f1a020d34c51 +size 81788 diff --git a/QueenNoxi/resources/fonts/ObelixProB-cyr Alfi Stefa.ttf b/QueenNoxi/resources/fonts/ObelixProB-cyr Alfi Stefa.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9eff21ab4948c4b2d330acd5274a3e6c18b92551 --- /dev/null +++ b/QueenNoxi/resources/fonts/ObelixProB-cyr Alfi Stefa.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40ba364fcb2f93178cb9431e2d4ea82e607408873dcf0767248fb217dedc6095 +size 22236 diff --git a/QueenNoxi/resources/fonts/RealFast Alfi Stefa.ttf b/QueenNoxi/resources/fonts/RealFast Alfi Stefa.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e19f6613646add3a63955ac177376445f6468e7a --- /dev/null +++ b/QueenNoxi/resources/fonts/RealFast Alfi Stefa.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c836de18a3b67fa81487a5ca9a61eeb8bd64ebd88002f29d531bfde69c9e3baf +size 272532 diff --git a/QueenNoxi/resources/fonts/Roboto-Italic.ttf b/QueenNoxi/resources/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1cd51b2c5ae5b6017aa1cf6300b2fc80ad91a22c --- /dev/null +++ b/QueenNoxi/resources/fonts/Roboto-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cd9337020d0b1593bae42dcca5cfc0fb86f5806ef469d66d09eae1e56ce346b +size 328640 diff --git a/QueenNoxi/resources/fonts/Roboto-Medium.ttf b/QueenNoxi/resources/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..38245334ef1ffae8853c94b3e559504a8e0c887b --- /dev/null +++ b/QueenNoxi/resources/fonts/Roboto-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02868d6a7e80d4e7836b6263dd50d26a63e923a1223c9e027aad6dc2501c27cc +size 160696 diff --git a/QueenNoxi/resources/fonts/Roboto-Regular.ttf b/QueenNoxi/resources/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0e3da8ef61a8ab91cd02a6c82418e9060ed20e47 --- /dev/null +++ b/QueenNoxi/resources/fonts/Roboto-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16466ef65064e6f3885a6d2806b8949ac1ac38b524dd0cf8fc96565eb4cc28e8 +size 158604 diff --git a/QueenNoxi/resources/fonts/TruenoBlkOl.otf b/QueenNoxi/resources/fonts/TruenoBlkOl.otf new file mode 100644 index 0000000000000000000000000000000000000000..6578c0b018270e02302e969a8eaad78064da2769 --- /dev/null +++ b/QueenNoxi/resources/fonts/TruenoBlkOl.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:847c1422270a4e0cafcf24a3b4dccb7f823c117fd0646c6ef8196d12cdeeb144 +size 159536 diff --git a/QueenNoxi/resources/fonts/TruenoRg.otf b/QueenNoxi/resources/fonts/TruenoRg.otf new file mode 100644 index 0000000000000000000000000000000000000000..54e7fabdf8aa596c93f9b8f85607cfd2b878fe14 --- /dev/null +++ b/QueenNoxi/resources/fonts/TruenoRg.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03b9e3215ebb9a0a6aed1df732549b67f15c5fecc5fd8d8aac31a3f302cbf87f +size 37948 diff --git a/QueenNoxi/resources/fonts/TruenoRgIt.otf b/QueenNoxi/resources/fonts/TruenoRgIt.otf new file mode 100644 index 0000000000000000000000000000000000000000..acd242f0b52275ef8fb7dca4ee861d1f34e1b9c7 --- /dev/null +++ b/QueenNoxi/resources/fonts/TruenoRgIt.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5267d1e70b3a26994a443d887d9ce2799f16618255220eb874ed5fb50773955 +size 54244 diff --git a/QueenNoxi/resources/fonts/TruenoSBdIt.otf b/QueenNoxi/resources/fonts/TruenoSBdIt.otf new file mode 100644 index 0000000000000000000000000000000000000000..151bd49fb62a6b7afdf263a9199b6f5e5312b9da --- /dev/null +++ b/QueenNoxi/resources/fonts/TruenoSBdIt.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b33b2226ae54cd7505a39953c24aa5b6ee18bfdbd434862a6e1d8007e4812ff1 +size 75556 diff --git a/QueenNoxi/resources/fonts/UrbanJungleDEMO.otf b/QueenNoxi/resources/fonts/UrbanJungleDEMO.otf new file mode 100644 index 0000000000000000000000000000000000000000..2ddeb215700c2c0cc7d011528b395bba2161a8b6 --- /dev/null +++ b/QueenNoxi/resources/fonts/UrbanJungleDEMO.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:981309d89d3e7b3cd5b866ab88a2d78a119a8ab93cfbe0e6b52d41c85edf47e7 +size 243708 diff --git a/QueenNoxi/resources/fonts/Vendetta.otf b/QueenNoxi/resources/fonts/Vendetta.otf new file mode 100644 index 0000000000000000000000000000000000000000..782c328c1319d7ba9e7137f4d8a3b96420efdef6 --- /dev/null +++ b/QueenNoxi/resources/fonts/Vendetta.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e1cd3977b7463e3cb4724b3f4903f2ea87b0e2d8bc689eac1e4f0c1a49254cc +size 110372 diff --git a/QueenNoxi/resources/fonts/__init__.py b/QueenNoxi/resources/fonts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/QueenNoxi/resources/fonts/__init__.py @@ -0,0 +1 @@ + diff --git a/QueenNoxi/resources/fonts/digital.ttf b/QueenNoxi/resources/fonts/digital.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c89de39dad1c2fd9cb81a5928f627498bc1a003c --- /dev/null +++ b/QueenNoxi/resources/fonts/digital.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36a2aefc772bf9df3157008579d37bde8b52921985c9894231ba1576580d2002 +size 23344 diff --git a/QueenNoxi/resources/fonts/elric.TTF b/QueenNoxi/resources/fonts/elric.TTF new file mode 100644 index 0000000000000000000000000000000000000000..d79568fc048c6acaa232c8b8841fe25cf6ceb19d --- /dev/null +++ b/QueenNoxi/resources/fonts/elric.TTF @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94278497fef52147ce168334a6c8a4b10acb515d5c9d072c834da36bcf1bf5a8 +size 51276 diff --git a/QueenNoxi/resources/fonts/font.otf b/QueenNoxi/resources/fonts/font.otf new file mode 100644 index 0000000000000000000000000000000000000000..19f1a356b3686ad91ec3e0bf0e0991e3f436e343 --- /dev/null +++ b/QueenNoxi/resources/fonts/font.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce85c044fc8227164e8f3461d3257a32c25f48000ca55c4e73b4397f70148d32 +size 125304 diff --git a/QueenNoxi/resources/fonts/fontx.ttf b/QueenNoxi/resources/fonts/fontx.ttf new file mode 100644 index 0000000000000000000000000000000000000000..67b0e194272790e74283fb42f186641f5c571bcb --- /dev/null +++ b/QueenNoxi/resources/fonts/fontx.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f53a60b095bfcbb7cadcc55955980eeb46b977770ac552b34185f4538af6775 +size 30980 diff --git a/QueenNoxi/resources/fonts/hawkmoon.ttf b/QueenNoxi/resources/fonts/hawkmoon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e95c6fe021c3705fe6cdfa273fb4f4a662af0bc6 --- /dev/null +++ b/QueenNoxi/resources/fonts/hawkmoon.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c8b61e93a383230071afd2eb885e48d7c433d1f21bae684ebb00bd5f8c59a04 +size 59056 diff --git a/QueenNoxi/resources/fonts/monumentextended-regular.otf b/QueenNoxi/resources/fonts/monumentextended-regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..99b838a01ffeade2645ed59e6fbd9ab3ffc09010 --- /dev/null +++ b/QueenNoxi/resources/fonts/monumentextended-regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62b958648c1a7f8e984788b7f02902d11531f76e481ce0b9043ad53840a72d09 +size 34380 diff --git a/QueenNoxi/resources/images.jpeg b/QueenNoxi/resources/images.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4134aba2a4d45a312840889d3bca53fa164d3578 Binary files /dev/null and b/QueenNoxi/resources/images.jpeg differ diff --git a/QueenNoxi/resources/mukesh.jpg b/QueenNoxi/resources/mukesh.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d679f37e683923ec1964b10973edcc69355e62f5 --- /dev/null +++ b/QueenNoxi/resources/mukesh.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1dd8379934e73ac20d5a800dce130d8a0245c23b8a8963d697138a375904fa5 +size 83265 diff --git a/QueenNoxi/utils/admins.py b/QueenNoxi/utils/admins.py new file mode 100644 index 0000000000000000000000000000000000000000..ae38fb71b98d3acda51b7c21bcd1571119c15db8 --- /dev/null +++ b/QueenNoxi/utils/admins.py @@ -0,0 +1,77 @@ +from typing import Callable + +from pyrogram.enums import ChatMemberStatus +from pyrogram.types import Message + +from QueenNoxi import DEV_USERS, DRAGONS, pbot + + +def can_change_info(func: Callable) -> Callable: + async def non_admin(_, message: Message): + if message.from_user.id in DRAGONS: + return await func(_, message) + + check = await pbot.get_chat_member(message.chat.id, message.from_user.id) + if check.status not in [ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR]: + return await message.reply_text( + "» ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ʙᴀʙʏ, ᴘʟᴇᴀsᴇ sᴛᴀʏ ɪɴ ʏᴏᴜʀ ʟɪᴍɪᴛs." + ) + + admin = ( + await pbot.get_chat_member(message.chat.id, message.from_user.id) + ).privileges + if admin.can_change_info: + return await func(_, message) + else: + return await message.reply_text( + "`You don't have permissions to change group info." + ) + + return non_admin + + +def can_restrict(func: Callable) -> Callable: + async def non_admin(_, message: Message): + if message.from_user.id in DEV_USERS: + return await func(_, message) + + check = await pbot.get_chat_member(message.chat.id, message.from_user.id) + if check.status not in [ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR]: + return await message.reply_text( + "» ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ʙᴀʙʏ, ᴘʟᴇᴀsᴇ sᴛᴀʏ ɪɴ ʏᴏᴜʀ ʟɪᴍɪᴛs." + ) + + admin = ( + await pbot.get_chat_member(message.chat.id, message.from_user.id) + ).privileges + if admin.can_restrict_members: + return await func(_, message) + else: + return await message.reply_text( + "`You don't have permissions to restrict users in this chat." + ) + + return non_admin + +def can_promote(func: Callable) -> Callable: + async def non_admin(_, message: Message): + if message.from_user.id in DEV_USERS: + return await func(_, message) + + check = await pbot.get_chat_member(message.chat.id, message.from_user.id) + if check.status not in [ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR]: + return await message.reply_text( + "» ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴀɴ ᴀᴅᴍɪɴ ʙᴀʙʏ, ᴘʟᴇᴀsᴇ sᴛᴀʏ ɪɴ ʏᴏᴜʀ ʟɪᴍɪᴛs." + ) + + admin = ( + await pbot.get_chat_member(message.chat.id, message.from_user.id) + ).privileges + if admin.can_promote_members: + return await func(_, message) + else: + return await message.reply_text( + "`You don't have permissions to promote|demote users in this chat." + ) + + return non_admin diff --git a/QueenNoxi/utils/errors.py b/QueenNoxi/utils/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..278e1bad1088ea408bf2ab398949fbdba492d33c --- /dev/null +++ b/QueenNoxi/utils/errors.py @@ -0,0 +1,55 @@ +import sys +import traceback +from functools import wraps + +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden + +from QueenNoxi import OWNER_ID, pbot + + +def split_limits(text): + if len(text) < 2048: + return [text] + + lines = text.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < 2048: + small_msg += line + else: + result.append(small_msg) + small_msg = line + + result.append(small_msg) + + return result + + +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + return + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ERROR** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + 0 if not message.from_user else message.from_user.id, + 0 if not message.chat else message.chat.id, + message.text or message.caption, + "".join(errors), + ), + ) + for x in error_feedback: + pass + # raise err + + return capture diff --git a/QueenNoxi/utils/fonts.py b/QueenNoxi/utils/fonts.py new file mode 100644 index 0000000000000000000000000000000000000000..c4e177466540d1b6dd21bb179e31f471c407d9f5 --- /dev/null +++ b/QueenNoxi/utils/fonts.py @@ -0,0 +1,2361 @@ +class Fonts: + def typewriter(text): + style = { + "a": "𝚊", + "b": "𝚋", + "c": "𝚌", + "d": "𝚍", + "e": "𝚎", + "f": "𝚏", + "g": "𝚐", + "h": "𝚑", + "i": "𝚒", + "j": "𝚓", + "k": "𝚔", + "l": "𝚕", + "m": "𝚖", + "n": "𝚗", + "o": "𝚘", + "p": "𝚙", + "q": "𝚚", + "r": "𝚛", + "s": "𝚜", + "t": "𝚝", + "u": "𝚞", + "v": "𝚟", + "w": "𝚠", + "x": "𝚡", + "y": "𝚢", + "z": "𝚣", + "A": "𝙰", + "B": "𝙱", + "C": "𝙲", + "D": "𝙳", + "E": "𝙴", + "F": "𝙵", + "G": "𝙶", + "H": "𝙷", + "I": "𝙸", + "J": "𝙹", + "K": "𝙺", + "L": "𝙻", + "M": "𝙼", + "N": "𝙽", + "O": "𝙾", + "P": "𝙿", + "Q": "𝚀", + "R": "𝚁", + "S": "𝚂", + "T": "𝚃", + "U": "𝚄", + "V": "𝚅", + "W": "𝚆", + "X": "𝚇", + "Y": "𝚈", + "Z": "𝚉", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def outline(text): + style = { + "a": "𝕒", + "b": "𝕓", + "c": "𝕔", + "d": "𝕕", + "e": "𝕖", + "f": "𝕗", + "g": "𝕘", + "h": "𝕙", + "i": "𝕚", + "j": "𝕛", + "k": "𝕜", + "l": "𝕝", + "m": "𝕞", + "n": "𝕟", + "o": "𝕠", + "p": "𝕡", + "q": "𝕢", + "r": "𝕣", + "s": "𝕤", + "t": "𝕥", + "u": "𝕦", + "v": "𝕧", + "w": "𝕨", + "x": "𝕩", + "y": "𝕪", + "z": "𝕫", + "A": "𝔸", + "B": "𝔹", + "C": "ℂ", + "D": "𝔻", + "E": "𝔼", + "F": "𝔽", + "G": "𝔾", + "H": "ℍ", + "I": "𝕀", + "J": "𝕁", + "K": "𝕂", + "L": "𝕃", + "M": "𝕄", + "N": "ℕ", + "O": "𝕆", + "P": "ℙ", + "Q": "ℚ", + "R": "ℝ", + "S": "𝕊", + "T": "𝕋", + "U": "𝕌", + "V": "𝕍", + "W": "𝕎", + "X": "𝕏", + "Y": "𝕐", + "Z": "ℤ", + "0": "𝟘", + "1": "𝟙", + "2": "𝟚", + "3": "𝟛", + "4": "𝟜", + "5": "𝟝", + "6": "𝟞", + "7": "𝟟", + "8": "𝟠", + "9": "𝟡", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def serief(text): + style = { + "a": "𝐚", + "b": "𝐛", + "c": "𝐜", + "d": "𝐝", + "e": "𝐞", + "f": "𝐟", + "g": "𝐠", + "h": "𝐡", + "i": "𝐢", + "j": "𝐣", + "k": "𝐤", + "l": "𝐥", + "m": "𝐦", + "n": "𝐧", + "o": "𝐨", + "p": "𝐩", + "q": "𝐪", + "r": "𝐫", + "s": "𝐬", + "t": "𝐭", + "u": "𝐮", + "v": "𝐯", + "w": "𝐰", + "x": "𝐱", + "y": "𝐲", + "z": "𝐳", + "A": "𝐀", + "B": "𝐁", + "C": "𝐂", + "D": "𝐃", + "E": "𝐄", + "F": "𝐅", + "G": "𝐆", + "H": "𝐇", + "I": "𝐈", + "J": "𝐉", + "K": "𝐊", + "L": "𝐋", + "M": "𝐌", + "N": "𝐍", + "O": "𝐎", + "P": "𝐏", + "Q": "𝐐", + "R": "𝐑", + "S": "𝐒", + "T": "𝐓", + "U": "𝐔", + "V": "𝐕", + "W": "𝐖", + "X": "𝐗", + "Y": "𝐘", + "Z": "𝐙", + "0": "𝟎", + "1": "𝟏", + "2": "𝟐", + "3": "𝟑", + "4": "𝟒", + "5": "𝟓", + "6": "𝟔", + "7": "𝟕", + "8": "𝟖", + "9": "𝟗", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bold_cool(text): + style = { + "a": "𝒂", + "b": "𝒃", + "c": "𝒄", + "d": "𝒅", + "e": "𝒆", + "f": "𝒇", + "g": "𝒈", + "h": "𝒉", + "i": "𝒊", + "j": "𝒋", + "k": "𝒌", + "l": "𝒍", + "m": "𝒎", + "n": "𝒏", + "o": "𝒐", + "p": "𝒑", + "q": "𝒒", + "r": "𝒓", + "s": "𝒔", + "t": "𝒕", + "u": "𝒖", + "v": "𝒗", + "w": "𝒘", + "x": "𝒙", + "y": "𝒚", + "z": "𝒛", + "A": "𝑨", + "B": "𝑩", + "C": "𝑪", + "D": "𝑫", + "E": "𝑬", + "F": "𝑭", + "G": "𝑮", + "H": "𝑯", + "I": "𝑰", + "J": "𝑱", + "K": "𝑲", + "L": "𝑳", + "M": "𝑴", + "N": "𝑵", + "O": "𝑶", + "P": "𝑷", + "Q": "𝑸", + "R": "𝑹", + "S": "𝑺", + "T": "𝑻", + "U": "𝑼", + "V": "𝑽", + "W": "𝑾", + "X": "𝑿", + "Y": "𝒀", + "Z": "𝒁", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def cool(text): + style = { + "a": "𝑎", + "b": "𝑏", + "c": "𝑐", + "d": "𝑑", + "e": "𝑒", + "f": "𝑓", + "g": "𝑔", + "h": "ℎ", + "i": "𝑖", + "j": "𝑗", + "k": "𝑘", + "l": "𝑙", + "m": "𝑚", + "n": "𝑛", + "o": "𝑜", + "p": "𝑝", + "q": "𝑞", + "r": "𝑟", + "s": "𝑠", + "t": "𝑡", + "u": "𝑢", + "v": "𝑣", + "w": "𝑤", + "x": "𝑥", + "y": "𝑦", + "z": "𝑧", + "A": "𝐴", + "B": "𝐵", + "C": "𝐶", + "D": "𝐷", + "E": "𝐸", + "F": "𝐹", + "G": "𝐺", + "H": "𝐻", + "I": "𝐼", + "J": "𝐽", + "K": "𝐾", + "L": "𝐿", + "M": "𝑀", + "N": "𝑁", + "O": "𝑂", + "P": "𝑃", + "Q": "𝑄", + "R": "𝑅", + "S": "𝑆", + "T": "𝑇", + "U": "𝑈", + "V": "𝑉", + "W": "𝑊", + "X": "𝑋", + "Y": "𝑌", + "Z": "𝑍", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def smallcap(text): + style = { + "a": "ᴀ", + "b": "ʙ", + "c": "ᴄ", + "d": "ᴅ", + "e": "ᴇ", + "f": "ғ", + "g": "ɢ", + "h": "ʜ", + "i": "ɪ", + "j": "J", + "k": "ᴋ", + "l": "ʟ", + "m": "ᴍ", + "n": "ɴ", + "o": "ᴏ", + "p": "ᴘ", + "q": "ǫ", + "r": "ʀ", + "s": "s", + "t": "ᴛ", + "u": "ᴜ", + "v": "ᴠ", + "w": "ᴡ", + "x": "x", + "y": "ʏ", + "z": "ᴢ", + "A": "A", + "B": "B", + "C": "C", + "D": "D", + "E": "E", + "F": "F", + "G": "G", + "H": "H", + "I": "I", + "J": "J", + "K": "K", + "L": "L", + "M": "M", + "N": "N", + "O": "O", + "P": "P", + "Q": "Q", + "R": "R", + "S": "S", + "T": "T", + "U": "U", + "V": "V", + "W": "W", + "X": "X", + "Y": "Y", + "Z": "Z", + "0": "𝟶", + "1": "𝟷", + "2": "𝟸", + "3": "𝟹", + "4": "𝟺", + "5": "𝟻", + "6": "𝟼", + "7": "𝟽", + "8": "𝟾", + "9": "𝟿", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def script(text): + style = { + "a": "𝒶", + "b": "𝒷", + "c": "𝒸", + "d": "𝒹", + "e": "ℯ", + "f": "𝒻", + "g": "ℊ", + "h": "𝒽", + "i": "𝒾", + "j": "𝒿", + "k": "𝓀", + "l": "𝓁", + "m": "𝓂", + "n": "𝓃", + "o": "ℴ", + "p": "𝓅", + "q": "𝓆", + "r": "𝓇", + "s": "𝓈", + "t": "𝓉", + "u": "𝓊", + "v": "𝓋", + "w": "𝓌", + "x": "𝓍", + "y": "𝓎", + "z": "𝓏", + "A": "𝒜", + "B": "ℬ", + "C": "𝒞", + "D": "𝒟", + "E": "ℰ", + "F": "ℱ", + "G": "𝒢", + "H": "ℋ", + "I": "ℐ", + "J": "𝒥", + "K": "𝒦", + "L": "ℒ", + "M": "ℳ", + "N": "𝒩", + "O": "𝒪", + "P": "𝒫", + "Q": "𝒬", + "R": "ℛ", + "S": "𝒮", + "T": "𝒯", + "U": "𝒰", + "V": "𝒱", + "W": "𝒲", + "X": "𝒳", + "Y": "𝒴", + "Z": "𝒵", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bold_script(text): + style = { + "a": "𝓪", + "b": "𝓫", + "c": "𝓬", + "d": "𝓭", + "e": "𝓮", + "f": "𝓯", + "g": "𝓰", + "h": "𝓱", + "i": "𝓲", + "j": "𝓳", + "k": "𝓴", + "l": "𝓵", + "m": "𝓶", + "n": "𝓷", + "o": "𝓸", + "p": "𝓹", + "q": "𝓺", + "r": "𝓻", + "s": "𝓼", + "t": "𝓽", + "u": "𝓾", + "v": "𝓿", + "w": "𝔀", + "x": "𝔁", + "y": "𝔂", + "z": "𝔃", + "A": "𝓐", + "B": "𝓑", + "C": "𝓒", + "D": "𝓓", + "E": "𝓔", + "F": "𝓕", + "G": "𝓖", + "H": "𝓗", + "I": "𝓘", + "J": "𝓙", + "K": "𝓚", + "L": "𝓛", + "M": "𝓜", + "N": "𝓝", + "O": "𝓞", + "P": "𝓟", + "Q": "𝓠", + "R": "𝓡", + "S": "𝓢", + "T": "𝓣", + "U": "𝓤", + "V": "𝓥", + "W": "𝓦", + "X": "𝓧", + "Y": "𝓨", + "Z": "𝓩", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def tiny(text): + style = { + "a": "ᵃ", + "b": "ᵇ", + "c": "ᶜ", + "d": "ᵈ", + "e": "ᵉ", + "f": "ᶠ", + "g": "ᵍ", + "h": "ʰ", + "i": "ⁱ", + "j": "ʲ", + "k": "ᵏ", + "l": "ˡ", + "m": "ᵐ", + "n": "ⁿ", + "o": "ᵒ", + "p": "ᵖ", + "q": "ᵠ", + "r": "ʳ", + "s": "ˢ", + "t": "ᵗ", + "u": "ᵘ", + "v": "ᵛ", + "w": "ʷ", + "x": "ˣ", + "y": "ʸ", + "z": "ᶻ", + "A": "ᵃ", + "B": "ᵇ", + "C": "ᶜ", + "D": "ᵈ", + "E": "ᵉ", + "F": "ᶠ", + "G": "ᵍ", + "H": "ʰ", + "I": "ⁱ", + "J": "ʲ", + "K": "ᵏ", + "L": "ˡ", + "M": "ᵐ", + "N": "ⁿ", + "O": "ᵒ", + "P": "ᵖ", + "Q": "ᵠ", + "R": "ʳ", + "S": "ˢ", + "T": "ᵗ", + "U": "ᵘ", + "V": "ᵛ", + "W": "ʷ", + "X": "ˣ", + "Y": "ʸ", + "Z": "ᶻ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def comic(text): + style = { + "a": "ᗩ", + "b": "ᗷ", + "c": "ᑕ", + "d": "ᗪ", + "e": "ᗴ", + "f": "ᖴ", + "g": "ᘜ", + "h": "ᕼ", + "i": "I", + "j": "ᒍ", + "k": "K", + "l": "ᒪ", + "m": "ᗰ", + "n": "ᑎ", + "o": "O", + "p": "ᑭ", + "q": "ᑫ", + "r": "ᖇ", + "s": "Տ", + "t": "T", + "u": "ᑌ", + "v": "ᐯ", + "w": "ᗯ", + "x": "᙭", + "y": "Y", + "z": "ᘔ", + "A": "ᗩ", + "B": "ᗷ", + "C": "ᑕ", + "D": "ᗪ", + "E": "ᗴ", + "F": "ᖴ", + "G": "ᘜ", + "H": "ᕼ", + "I": "I", + "J": "ᒍ", + "K": "K", + "L": "ᒪ", + "M": "ᗰ", + "N": "ᑎ", + "O": "O", + "P": "ᑭ", + "Q": "ᑫ", + "R": "ᖇ", + "S": "Տ", + "T": "T", + "U": "ᑌ", + "V": "ᐯ", + "W": "ᗯ", + "X": "᙭", + "Y": "Y", + "Z": "ᘔ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def san(text): + style = { + "a": "𝗮", + "b": "𝗯", + "c": "𝗰", + "d": "𝗱", + "e": "𝗲", + "f": "𝗳", + "g": "𝗴", + "h": "𝗵", + "i": "𝗶", + "j": "𝗷", + "k": "𝗸", + "l": "𝗹", + "m": "𝗺", + "n": "𝗻", + "o": "𝗼", + "p": "𝗽", + "q": "𝗾", + "r": "𝗿", + "s": "𝘀", + "t": "𝘁", + "u": "𝘂", + "v": "𝘃", + "w": "𝘄", + "x": "𝘅", + "y": "𝘆", + "z": "𝘇", + "A": "𝗔", + "B": "𝗕", + "C": "𝗖", + "D": "𝗗", + "E": "𝗘", + "F": "𝗙", + "G": "𝗚", + "H": "𝗛", + "I": "𝗜", + "J": "𝗝", + "K": "𝗞", + "L": "𝗟", + "M": "𝗠", + "N": "𝗡", + "O": "𝗢", + "P": "𝗣", + "Q": "𝗤", + "R": "𝗥", + "S": "𝗦", + "T": "𝗧", + "U": "𝗨", + "V": "𝗩", + "W": "𝗪", + "X": "𝗫", + "Y": "𝗬", + "Z": "𝗭", + "0": "𝟬", + "1": "𝟭", + "2": "𝟮", + "3": "𝟯", + "4": "𝟰", + "5": "𝟱", + "6": "𝟲", + "7": "𝟳", + "8": "𝟴", + "9": "𝟵", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def slant_san(text): + style = { + "a": "𝙖", + "b": "𝙗", + "c": "𝙘", + "d": "𝙙", + "e": "𝙚", + "f": "𝙛", + "g": "𝙜", + "h": "𝙝", + "i": "𝙞", + "j": "𝙟", + "k": "𝙠", + "l": "𝙡", + "m": "𝙢", + "n": "𝙣", + "o": "𝙤", + "p": "𝙥", + "q": "𝙦", + "r": "𝙧", + "s": "𝙨", + "t": "𝙩", + "u": "𝙪", + "v": "𝙫", + "w": "𝙬", + "x": "𝙭", + "y": "𝙮", + "z": "𝙯", + "A": "𝘼", + "B": "𝘽", + "C": "𝘾", + "D": "𝘿", + "E": "𝙀", + "F": "𝙁", + "G": "𝙂", + "H": "𝙃", + "I": "𝙄", + "J": "𝙅", + "K": "𝙆", + "L": "𝙇", + "M": "𝙈", + "N": "𝙉", + "O": "𝙊", + "P": "𝙋", + "Q": "𝙌", + "R": "𝙍", + "S": "𝙎", + "T": "𝙏", + "U": "𝙐", + "V": "𝙑", + "W": "𝙒", + "X": "𝙓", + "Y": "𝙔", + "Z": "𝙕", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def slant(text): + style = { + "a": "𝘢", + "b": "𝘣", + "c": "𝘤", + "d": "𝘥", + "e": "𝘦", + "f": "𝘧", + "g": "𝘨", + "h": "𝘩", + "i": "𝘪", + "j": "𝘫", + "k": "𝘬", + "l": "𝘭", + "m": "𝘮", + "n": "𝘯", + "o": "𝘰", + "p": "𝘱", + "q": "𝘲", + "r": "𝘳", + "s": "𝘴", + "t": "𝘵", + "u": "𝘶", + "v": "𝘷", + "w": "𝘸", + "x": "𝘹", + "y": "𝘺", + "z": "𝘻", + "A": "𝘈", + "B": "𝘉", + "C": "𝘊", + "D": "𝘋", + "E": "𝘌", + "F": "𝘍", + "G": "𝘎", + "H": "𝘏", + "I": "𝘐", + "J": "𝘑", + "K": "𝘒", + "L": "𝘓", + "M": "𝘔", + "N": "𝘕", + "O": "𝘖", + "P": "𝘗", + "Q": "𝘘", + "R": "𝘙", + "S": "𝘚", + "T": "𝘛", + "U": "𝘜", + "V": "𝘝", + "W": "𝘞", + "X": "𝘟", + "Y": "𝘠", + "Z": "𝘡", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def sim(text): + style = { + "a": "𝖺", + "b": "𝖻", + "c": "𝖼", + "d": "𝖽", + "e": "𝖾", + "f": "𝖿", + "g": "𝗀", + "h": "𝗁", + "i": "𝗂", + "j": "𝗃", + "k": "𝗄", + "l": "𝗅", + "m": "𝗆", + "n": "𝗇", + "o": "𝗈", + "p": "𝗉", + "q": "𝗊", + "r": "𝗋", + "s": "𝗌", + "t": "𝗍", + "u": "𝗎", + "v": "𝗏", + "w": "𝗐", + "x": "𝗑", + "y": "𝗒", + "z": "𝗓", + "A": "𝖠", + "B": "𝖡", + "C": "𝖢", + "D": "𝖣", + "E": "𝖤", + "F": "𝖥", + "G": "𝖦", + "H": "𝖧", + "I": "𝖨", + "J": "𝖩", + "K": "𝖪", + "L": "𝖫", + "M": "𝖬", + "N": "𝖭", + "O": "𝖮", + "P": "𝖯", + "Q": "𝖰", + "R": "𝖱", + "S": "𝖲", + "T": "𝖳", + "U": "𝖴", + "V": "𝖵", + "W": "𝖶", + "X": "𝖷", + "Y": "𝖸", + "Z": "𝖹", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def circles(text): + style = { + "a": "Ⓐ︎", + "b": "Ⓑ︎", + "c": "Ⓒ︎", + "d": "Ⓓ︎", + "e": "Ⓔ︎", + "f": "Ⓕ︎", + "g": "Ⓖ︎", + "h": "Ⓗ︎", + "i": "Ⓘ︎", + "j": "Ⓙ︎", + "k": "Ⓚ︎", + "l": "Ⓛ︎", + "m": "Ⓜ︎", + "n": "Ⓝ︎", + "o": "Ⓞ︎", + "p": "Ⓟ︎", + "q": "Ⓠ︎", + "r": "Ⓡ︎", + "s": "Ⓢ︎", + "t": "Ⓣ︎", + "u": "Ⓤ︎", + "v": "Ⓥ︎", + "w": "Ⓦ︎", + "x": "Ⓧ︎", + "y": "Ⓨ︎", + "z": "Ⓩ︎", + "A": "Ⓐ︎", + "B": "Ⓑ︎", + "C": "Ⓒ︎", + "D": "Ⓓ︎", + "E": "Ⓔ︎", + "F": "Ⓕ︎", + "G": "Ⓖ︎", + "H": "Ⓗ︎", + "I": "Ⓘ︎", + "J": "Ⓙ︎", + "K": "Ⓚ︎", + "L": "Ⓛ︎", + "M": "Ⓜ︎", + "N": "Ⓝ︎", + "O": "Ⓞ︎", + "P": "Ⓟ︎", + "Q": "Ⓠ︎", + "R": "Ⓡ︎", + "S": "Ⓢ︎", + "T": "Ⓣ︎", + "U": "Ⓤ︎", + "V": "Ⓥ︎", + "W": "Ⓦ︎", + "X": "Ⓧ︎", + "Y": "Ⓨ︎", + "Z": "Ⓩ︎", + "0": "⓪", + "1": "①", + "2": "②", + "3": "③", + "4": "④", + "5": "⑤", + "6": "⑥", + "7": "⑦", + "8": "⑧", + "9": "⑨", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def dark_circle(text): + style = { + "a": "🅐︎", + "b": "🅑︎", + "c": "🅒︎", + "d": "🅓︎", + "e": "🅔︎", + "f": "🅕︎", + "g": "🅖︎", + "h": "🅗︎", + "i": "🅘︎", + "j": "🅙︎", + "k": "🅚︎", + "l": "🅛︎", + "m": "🅜︎", + "n": "🅝︎", + "o": "🅞︎", + "p": "🅟︎", + "q": "🅠︎", + "r": "🅡︎", + "s": "🅢︎", + "t": "🅣︎", + "u": "🅤︎", + "v": "🅥︎", + "w": "🅦︎", + "x": "🅧︎", + "y": "🅨︎", + "z": "🅩︎", + "A": "🅐︎", + "B": "🅑︎", + "C": "🅒︎", + "D": "🅓︎", + "E": "🅔︎", + "F": "🅕︎", + "G": "🅖︎", + "H": "🅗︎", + "I": "🅘︎", + "J": "🅙︎", + "K": "🅚︎", + "L": "🅛︎", + "M": "🅜︎", + "N": "🅝︎", + "O": "🅞︎", + "P": "🅟︎", + "Q": "🅠︎", + "R": "🅡︎", + "S": "🅢︎", + "T": "🅣︎", + "U": "🅤︎", + "V": "🅥︎", + "W": "🅦︎", + "X": "🅧︎", + "Y": "🅨︎", + "Z": "🅩", + "0": "⓿", + "1": "➊", + "2": "➋", + "3": "➌", + "4": "➍", + "5": "➎", + "6": "➏", + "7": "➐", + "8": "➑", + "9": "➒", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def gothic(text): + style = { + "a": "𝔞", + "b": "𝔟", + "c": "𝔠", + "d": "𝔡", + "e": "𝔢", + "f": "𝔣", + "g": "𝔤", + "h": "𝔥", + "i": "𝔦", + "j": "𝔧", + "k": "𝔨", + "l": "𝔩", + "m": "𝔪", + "n": "𝔫", + "o": "𝔬", + "p": "𝔭", + "q": "𝔮", + "r": "𝔯", + "s": "𝔰", + "t": "𝔱", + "u": "𝔲", + "v": "𝔳", + "w": "𝔴", + "x": "𝔵", + "y": "𝔶", + "z": "𝔷", + "A": "𝔄", + "B": "𝔅", + "C": "ℭ", + "D": "𝔇", + "E": "𝔈", + "F": "𝔉", + "G": "𝔊", + "H": "ℌ", + "I": "ℑ", + "J": "𝔍", + "K": "𝔎", + "L": "𝔏", + "M": "𝔐", + "N": "𝔑", + "O": "𝔒", + "P": "𝔓", + "Q": "𝔔", + "R": "ℜ", + "S": "𝔖", + "T": "𝔗", + "U": "𝔘", + "V": "𝔙", + "W": "𝔚", + "X": "𝔛", + "Y": "𝔜", + "Z": "ℨ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bold_gothic(text): + style = { + "a": "𝖆", + "b": "𝖇", + "c": "𝖈", + "d": "𝖉", + "e": "𝖊", + "f": "𝖋", + "g": "𝖌", + "h": "𝖍", + "i": "𝖎", + "j": "𝖏", + "k": "𝖐", + "l": "𝖑", + "m": "𝖒", + "n": "𝖓", + "o": "𝖔", + "p": "𝖕", + "q": "𝖖", + "r": "𝖗", + "s": "𝖘", + "t": "𝖙", + "u": "𝖚", + "v": "𝖛", + "w": "𝖜", + "x": "𝖝", + "y": "𝖞", + "z": "𝖟", + "A": "𝕬", + "B": "𝕭", + "C": "𝕮", + "D": "𝕺", + "E": "𝕰", + "F": "𝕱", + "G": "𝕲", + "H": "𝕳", + "I": "𝕴", + "J": "𝕵", + "K": "𝕶", + "L": "𝕷", + "M": "𝕸", + "N": "𝕹", + "O": "𝕺", + "P": "𝕻", + "Q": "𝕼", + "R": "𝕽", + "S": "𝕾", + "T": "𝕿", + "U": "𝖀", + "V": "𝖁", + "W": "𝖂", + "X": "𝖃", + "Y": "𝖄", + "Z": "𝖅", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def cloud(text): + style = { + "a": "a͜͡", + "b": "b͜͡", + "c": "c͜͡", + "d": "d͜͡", + "e": "e͜͡", + "f": "f͜͡", + "g": "g͜͡", + "h": "h͜͡", + "i": "i͜͡", + "j": "j͜͡", + "k": "k͜͡", + "l": "l͜͡", + "m": "m͜͡", + "n": "n͜͡", + "o": "o͜͡", + "p": "p͜͡", + "q": "q͜͡", + "r": "r͜͡", + "s": "s͜͡", + "t": "t͜͡", + "u": "u͜͡", + "v": "v͜͡", + "w": "w͜͡", + "x": "x͜͡", + "y": "y͜͡", + "z": "z͜͡", + "A": "A͜͡", + "B": "B͜͡", + "C": "C͜͡", + "D": "D͜͡", + "E": "E͜͡", + "F": "F͜͡", + "G": "G͜͡", + "H": "H͜͡", + "I": "I͜͡", + "J": "J͜͡", + "K": "K͜͡", + "L": "L͜͡", + "M": "M͜͡", + "N": "N͜͡", + "O": "O͜͡", + "P": "P͜͡", + "Q": "Q͜͡", + "R": "R͜͡", + "S": "S͜͡", + "T": "T͜͡", + "U": "U͜͡", + "V": "V͜͡", + "W": "W͜͡", + "X": "X͜͡", + "Y": "Y͜͡", + "Z": "Z͜͡", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def happy(text): + style = { + "a": "ă̈", + "b": "b̆̈", + "c": "c̆̈", + "d": "d̆̈", + "e": "ĕ̈", + "f": "f̆̈", + "g": "ğ̈", + "h": "h̆̈", + "i": "ĭ̈", + "j": "j̆̈", + "k": "k̆̈", + "l": "l̆̈", + "m": "m̆̈", + "n": "n̆̈", + "o": "ŏ̈", + "p": "p̆̈", + "q": "q̆̈", + "r": "r̆̈", + "s": "s̆̈", + "t": "t̆̈", + "u": "ŭ̈", + "v": "v̆̈", + "w": "w̆̈", + "x": "x̆̈", + "y": "y̆̈", + "z": "z̆̈", + "A": "Ă̈", + "B": "B̆̈", + "C": "C̆̈", + "D": "D̆̈", + "E": "Ĕ̈", + "F": "F̆̈", + "G": "Ğ̈", + "H": "H̆̈", + "I": "Ĭ̈", + "J": "J̆̈", + "K": "K̆̈", + "L": "L̆̈", + "M": "M̆̈", + "N": "N̆̈", + "O": "Ŏ̈", + "P": "P̆̈", + "Q": "Q̆̈", + "R": "R̆̈", + "S": "S̆̈", + "T": "T̆̈", + "U": "Ŭ̈", + "V": "V̆̈", + "W": "W̆̈", + "X": "X̆̈", + "Y": "Y̆̈", + "Z": "Z̆̈", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def sad(text): + style = { + "a": "ȃ̈", + "b": "b̑̈", + "c": "c̑̈", + "d": "d̑̈", + "e": "ȇ̈", + "f": "f̑̈", + "g": "g̑̈", + "h": "h̑̈", + "i": "ȋ̈", + "j": "j̑̈", + "k": "k̑̈", + "l": "l̑̈", + "m": "m̑̈", + "n": "n̑̈", + "o": "ȏ̈", + "p": "p̑̈", + "q": "q̑̈", + "r": "ȓ̈", + "s": "s̑̈", + "t": "t̑̈", + "u": "ȗ̈", + "v": "v̑̈", + "w": "w̑̈", + "x": "x̑̈", + "y": "y̑̈", + "z": "z̑̈", + "A": "Ȃ̈", + "B": "B̑̈", + "C": "C̑̈", + "D": "D̑̈", + "E": "Ȇ̈", + "F": "F̑̈", + "G": "G̑̈", + "H": "H̑̈", + "I": "Ȋ̈", + "J": "J̑̈", + "K": "K̑̈", + "L": "L̑̈", + "M": "M̑̈", + "N": "N̑̈", + "O": "Ȏ̈", + "P": "P̑̈", + "Q": "Q̑̈", + "R": "Ȓ̈", + "S": "S̑̈", + "T": "T̑̈", + "U": "Ȗ̈", + "V": "V̑̈", + "W": "W̑̈", + "X": "X̑̈", + "Y": "Y̑̈", + "Z": "Z̑̈", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def special(text): + style = { + "a": "🇦 ", + "b": "🇧 ", + "c": "🇨 ", + "d": "🇩 ", + "e": "🇪 ", + "f": "🇫 ", + "g": "🇬 ", + "h": "🇭 ", + "i": "🇮 ", + "j": "🇯 ", + "k": "🇰 ", + "l": "🇱 ", + "m": "🇲 ", + "n": "🇳 ", + "o": "🇴 ", + "p": "🇵 ", + "q": "🇶 ", + "r": "🇷 ", + "s": "🇸 ", + "t": "🇹 ", + "u": "🇺 ", + "v": "🇻 ", + "w": "🇼 ", + "x": "🇽 ", + "y": "🇾 ", + "z": "🇿 ", + "A": "🇦 ", + "B": "🇧 ", + "C": "🇨 ", + "D": "🇩 ", + "E": "🇪 ", + "F": "🇫 ", + "G": "🇬 ", + "H": "🇭 ", + "I": "🇮 ", + "J": "🇯 ", + "K": "🇰 ", + "L": "🇱 ", + "M": "🇲 ", + "N": "🇳 ", + "O": "🇴 ", + "P": "🇵 ", + "Q": "🇶 ", + "R": "🇷 ", + "S": "🇸 ", + "T": "🇹 ", + "U": "🇺 ", + "V": "🇻 ", + "W": "🇼 ", + "X": "🇽 ", + "Y": "🇾 ", + "Z": "🇿 ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def square(text): + style = { + "a": "🄰", + "b": "🄱", + "c": "🄲", + "d": "🄳", + "e": "🄴", + "f": "🄵", + "g": "🄶", + "h": "🄷", + "i": "🄸", + "j": "🄹", + "k": "🄺", + "l": "🄻", + "m": "🄼", + "n": "🄽", + "o": "🄾", + "p": "🄿", + "q": "🅀", + "r": "🅁", + "s": "🅂", + "t": "🅃", + "u": "🅄", + "v": "🅅", + "w": "🅆", + "x": "🅇", + "y": "🅈", + "z": "🅉", + "A": "🄰", + "B": "🄱", + "C": "🄲", + "D": "🄳", + "E": "🄴", + "F": "🄵", + "G": "🄶", + "H": "🄷", + "I": "🄸", + "J": "🄹", + "K": "🄺", + "L": "🄻", + "M": "🄼", + "N": "🄽", + "O": "🄾", + "P": "🄿", + "Q": "🅀", + "R": "🅁", + "S": "🅂", + "T": "🅃", + "U": "🅄", + "V": "🅅", + "W": "🅆", + "X": "🅇", + "Y": "🅈", + "Z": "🅉", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def dark_square(text): + style = { + "a": "🅰︎", + "b": "🅱︎", + "c": "🅲︎", + "d": "🅳︎", + "e": "🅴︎", + "f": "🅵︎", + "g": "🅶︎", + "h": "🅷︎", + "i": "🅸︎", + "j": "🅹︎", + "k": "🅺︎", + "l": "🅻︎", + "m": "🅼︎", + "n": "🅽︎", + "o": "🅾︎", + "p": "🅿︎", + "q": "🆀︎", + "r": "🆁︎", + "s": "🆂︎", + "t": "🆃︎", + "u": "🆄︎", + "v": "🆅︎", + "w": "🆆︎", + "x": "🆇︎", + "y": "🆈︎", + "z": "🆉︎", + "A": "🅰︎", + "B": "🅱︎", + "C": "🅲︎", + "D": "🅳︎", + "E": "🅴︎", + "F": "🅵︎", + "G": "🅶︎", + "H": "🅷︎", + "I": "🅸︎", + "J": "🅹︎", + "K": "🅺︎", + "L": "🅻︎", + "M": "🅼︎", + "N": "🅽︎", + "O": "🅾︎", + "P": "🅿︎", + "Q": "🆀︎", + "R": "🆁︎", + "S": "🆂︎", + "T": "🆃︎", + "U": "🆄︎", + "V": "🆅︎", + "W": "🆆︎", + "X": "🆇︎", + "Y": "🆈︎", + "Z": "🆉︎", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def andalucia(text): + style = { + "a": "ꪖ", + "b": "᥇", + "c": "ᥴ", + "d": "ᦔ", + "e": "ꫀ", + "f": "ᠻ", + "g": "ᧁ", + "h": "ꫝ", + "i": "𝓲", + "j": "𝓳", + "k": "𝘬", + "l": "ꪶ", + "m": "ꪑ", + "n": "ꪀ", + "o": "ꪮ", + "p": "ρ", + "q": "𝘲", + "r": "𝘳", + "s": "𝘴", + "t": "𝓽", + "u": "ꪊ", + "v": "ꪜ", + "w": "᭙", + "x": "᥊", + "y": "ꪗ", + "z": "ɀ", + "A": "ꪖ", + "B": "᥇", + "C": "ᥴ", + "D": "ᦔ", + "E": "ꫀ", + "F": "ᠻ", + "G": "ᧁ", + "H": "ꫝ", + "I": "𝓲", + "J": "𝓳", + "K": "𝘬", + "L": "ꪶ", + "M": "ꪑ", + "N": "ꪀ", + "O": "ꪮ", + "P": "ρ", + "Q": "𝘲", + "R": "𝘳", + "S": "𝘴", + "T": "𝓽", + "U": "ꪊ", + "V": "ꪜ", + "W": "᭙", + "X": "᥊", + "Y": "ꪗ", + "Z": "ɀ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def manga(text): + style = { + "a": "卂", + "b": "乃", + "c": "匚", + "d": "ᗪ", + "e": "乇", + "f": "千", + "g": "ᘜ", + "h": "卄", + "i": "|", + "j": "フ", + "k": "Ҝ", + "l": "ㄥ", + "m": "爪", + "n": "几", + "o": "ㄖ", + "p": "卩", + "q": "Ҩ", + "r": "尺", + "s": "丂", + "t": "ㄒ", + "u": "ㄩ", + "v": "ᐯ", + "w": "山", + "x": "乂", + "y": "ㄚ", + "z": "乙", + "A": "卂", + "B": "乃", + "C": "匚", + "D": "ᗪ", + "E": "乇", + "F": "千", + "G": "ᘜ", + "H": "卄", + "I": "|", + "J": "フ", + "K": "Ҝ", + "L": "ㄥ", + "M": "爪", + "N": "几", + "O": "ㄖ", + "P": "卩", + "Q": "Ҩ", + "R": "尺", + "S": "丂", + "T": "ㄒ", + "U": "ㄩ", + "V": "ᐯ", + "W": "山", + "X": "乂", + "Y": "ㄚ", + "Z": "乙", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def stinky(text): + style = { + "a": "a̾", + "b": "b̾", + "c": "c̾", + "d": "d̾", + "e": "e̾", + "f": "f̾", + "g": "g̾", + "h": "h̾", + "i": "i̾", + "j": "j̾", + "k": "k̾", + "l": "l̾", + "m": "m̾", + "n": "n̾", + "o": "o̾", + "p": "p̾", + "q": "q̾", + "r": "r̾", + "s": "s̾", + "t": "t̾", + "u": "u̾", + "v": "v̾", + "w": "w̾", + "x": "x̾", + "y": "y̾", + "z": "z̾", + "A": "A̾", + "B": "B̾", + "C": "C̾", + "D": "D̾", + "E": "E̾", + "F": "F̾", + "G": "G̾", + "H": "H̾", + "I": "I̾", + "J": "J̾", + "K": "K̾", + "L": "L̾", + "M": "M̾", + "N": "N̾", + "O": "O̾", + "P": "P̾", + "Q": "Q̾", + "R": "R̾", + "S": "S̾", + "T": "T̾", + "U": "U̾", + "V": "V̾", + "W": "W̾", + "X": "X̾", + "Y": "Y̾", + "Z": "Z̾", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bubbles(text): + style = { + "a": "ḁͦ", + "b": "b̥ͦ", + "c": "c̥ͦ", + "d": "d̥ͦ", + "e": "e̥ͦ", + "f": "f̥ͦ", + "g": "g̥ͦ", + "h": "h̥ͦ", + "i": "i̥ͦ", + "j": "j̥ͦ", + "k": "k̥ͦ", + "l": "l̥ͦ", + "m": "m̥ͦ", + "n": "n̥ͦ", + "o": "o̥ͦ", + "p": "p̥ͦ", + "q": "q̥ͦ", + "r": "r̥ͦ", + "s": "s̥ͦ", + "t": "t̥ͦ", + "u": "u̥ͦ", + "v": "v̥ͦ", + "w": "w̥ͦ", + "x": "x̥ͦ", + "y": "y̥ͦ", + "z": "z̥ͦ", + "A": "Ḁͦ", + "B": "B̥ͦ", + "C": "C̥ͦ", + "D": "D̥ͦ", + "E": "E̥ͦ", + "F": "F̥ͦ", + "G": "G̥ͦ", + "H": "H̥ͦ", + "I": "I̥ͦ", + "J": "J̥ͦ", + "K": "K̥ͦ", + "L": "L̥ͦ", + "M": "M̥ͦ", + "N": "N̥ͦ", + "O": "O̥ͦ", + "P": "P̥ͦ", + "Q": "Q̥ͦ", + "R": "R̥ͦ", + "S": "S̥ͦ", + "T": "T̥ͦ", + "U": "U̥ͦ", + "V": "V̥ͦ", + "W": "W̥ͦ", + "X": "X̥ͦ", + "Y": "Y̥ͦ", + "Z": "Z̥ͦ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def underline(text): + style = { + "a": "a͟", + "b": "b͟", + "c": "c͟", + "d": "d͟", + "e": "e͟", + "f": "f͟", + "g": "g͟", + "h": "h͟", + "i": "i͟", + "j": "j͟", + "k": "k͟", + "l": "l͟", + "m": "m͟", + "n": "n͟", + "o": "o͟", + "p": "p͟", + "q": "q͟", + "r": "r͟", + "s": "s͟", + "t": "t͟", + "u": "u͟", + "v": "v͟", + "w": "w͟", + "x": "x͟", + "y": "y͟", + "z": "z͟", + "A": "A͟", + "B": "B͟", + "C": "C͟", + "D": "D͟", + "E": "E͟", + "F": "F͟", + "G": "G͟", + "H": "H͟", + "I": "I͟", + "J": "J͟", + "K": "K͟", + "L": "L͟", + "M": "M͟", + "N": "N͟", + "O": "O͟", + "P": "P͟", + "Q": "Q͟", + "R": "R͟", + "S": "S͟", + "T": "T͟", + "U": "U͟", + "V": "V͟", + "W": "W͟", + "X": "X͟", + "Y": "Y͟", + "Z": "Z͟", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def ladybug(text): + style = { + "a": "ꍏ", + "b": "ꌃ", + "c": "ꏳ", + "d": "ꀷ", + "e": "ꏂ", + "f": "ꎇ", + "g": "ꁅ", + "h": "ꀍ", + "i": "ꀤ", + "j": "꒻", + "k": "ꀘ", + "l": "꒒", + "m": "ꎭ", + "n": "ꈤ", + "o": "ꂦ", + "p": "ᖘ", + "q": "ꆰ", + "r": "ꋪ", + "s": "ꌚ", + "t": "꓄", + "u": "ꀎ", + "v": "꒦", + "w": "ꅐ", + "x": "ꉧ", + "y": "ꌩ", + "z": "ꁴ", + "A": "ꍏ", + "B": "ꌃ", + "C": "ꏳ", + "D": "ꀷ", + "E": "ꏂ", + "F": "ꎇ", + "G": "ꁅ", + "H": "ꀍ", + "I": "ꀤ", + "J": "꒻", + "K": "ꀘ", + "L": "꒒", + "M": "ꎭ", + "N": "ꈤ", + "O": "ꂦ", + "P": "ᖘ", + "Q": "ꆰ", + "R": "ꋪ", + "S": "ꌚ", + "T": "꓄", + "U": "ꀎ", + "V": "꒦", + "W": "ꅐ", + "X": "ꉧ", + "Y": "ꌩ", + "Z": "ꁴ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def rays(text): + style = { + "a": "a҉", + "b": "b҉", + "c": "c҉", + "d": "d҉", + "e": "e҉", + "f": "f҉", + "g": "g҉", + "h": "h҉", + "i": "i҉", + "j": "j҉", + "k": "k҉", + "l": "l҉", + "m": "m҉", + "n": "n҉", + "o": "o҉", + "p": "p҉", + "q": "q҉", + "r": "r҉", + "s": "s҉", + "t": "t҉", + "u": "u҉", + "v": "v҉", + "w": "w҉", + "x": "x҉", + "y": "y҉", + "z": "z҉", + "A": "A҉", + "B": "B҉", + "C": "C҉", + "D": "D҉", + "E": "E҉", + "F": "F҉", + "G": "G҉", + "H": "H҉", + "I": "I҉", + "J": "J҉", + "K": "K҉", + "L": "L҉", + "M": "M҉", + "N": "N҉", + "O": "O҉", + "P": "P҉", + "Q": "Q҉", + "R": "R҉", + "S": "S҉", + "T": "T҉", + "U": "U҉", + "V": "V҉", + "W": "W҉", + "X": "X҉", + "Y": "Y҉", + "Z": "Z҉", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def birds(text): + style = { + "a": "a҈", + "b": "b҈", + "c": "c҈", + "d": "d҈", + "e": "e҈", + "f": "f҈", + "g": "g҈", + "h": "h҈", + "i": "i҈", + "j": "j҈", + "k": "k҈", + "l": "l҈", + "m": "m҈", + "n": "n҈", + "o": "o҈", + "p": "p҈", + "q": "q҈", + "r": "r҈", + "s": "s҈", + "t": "t҈", + "u": "u҈", + "v": "v҈", + "w": "w҈", + "x": "x҈", + "y": "y҈", + "z": "z҈", + "A": "A҈", + "B": "B҈", + "C": "C҈", + "D": "D҈", + "E": "E҈", + "F": "F҈", + "G": "G҈", + "H": "H҈", + "I": "I҈", + "J": "J҈", + "K": "K҈", + "L": "L҈", + "M": "M҈", + "N": "N҈", + "O": "O҈", + "P": "P҈", + "Q": "Q҈", + "R": "R҈", + "S": "S҈", + "T": "T҈", + "U": "U҈", + "V": "V҈", + "W": "W҈", + "X": "X҈", + "Y": "Y҈", + "Z": "Z҈", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def slash(text): + style = { + "a": "a̸", + "b": "b̸", + "c": "c̸", + "d": "d̸", + "e": "e̸", + "f": "f̸", + "g": "g̸", + "h": "h̸", + "i": "i̸", + "j": "j̸", + "k": "k̸", + "l": "l̸", + "m": "m̸", + "n": "n̸", + "o": "o̸", + "p": "p̸", + "q": "q̸", + "r": "r̸", + "s": "s̸", + "t": "t̸", + "u": "u̸", + "v": "v̸", + "w": "w̸", + "x": "x̸", + "y": "y̸", + "z": "z̸", + "A": "A̸", + "B": "B̸", + "C": "C̸", + "D": "D̸", + "E": "E̸", + "F": "F̸", + "G": "G̸", + "H": "H̸", + "I": "I̸", + "J": "J̸", + "K": "K̸", + "L": "L̸", + "M": "M̸", + "N": "N̸", + "O": "O̸", + "P": "P̸", + "Q": "Q̸", + "R": "R̸", + "S": "S̸", + "T": "T̸", + "U": "U̸", + "V": "V̸", + "W": "W̸", + "X": "X̸", + "Y": "Y̸", + "Z": "Z̸", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def stop(text): + style = { + "a": "a⃠", + "b": "b⃠", + "c": "c⃠", + "d": "d⃠", + "e": "e⃠", + "f": "f⃠", + "g": "g⃠", + "h": "h⃠", + "i": "i⃠", + "j": "j⃠", + "k": "k⃠", + "l": "l⃠", + "m": "m⃠", + "n": "n⃠", + "o": "o⃠", + "p": "p⃠", + "q": "q⃠", + "r": "r⃠", + "s": "s⃠", + "t": "t⃠", + "u": "u⃠", + "v": "v⃠", + "w": "w⃠", + "x": "x⃠", + "y": "y⃠", + "z": "z⃠", + "A": "A⃠", + "B": "B⃠", + "C": "C⃠", + "D": "D⃠", + "E": "E⃠", + "F": "F⃠", + "G": "G⃠", + "H": "H⃠", + "I": "I⃠", + "J": "J⃠", + "K": "K⃠", + "L": "L⃠", + "M": "M⃠", + "N": "N⃠", + "O": "O⃠", + "P": "P⃠", + "Q": "Q⃠", + "R": "R⃠", + "S": "S⃠", + "T": "T⃠", + "U": "U⃠", + "V": "V⃠", + "W": "W⃠", + "X": "X⃠", + "Y": "Y⃠", + "Z": "Z⃠", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def skyline(text): + style = { + "a": "a̺͆", + "b": "b̺͆", + "c": "c̺͆", + "d": "d̺͆", + "e": "e̺͆", + "f": "f̺͆", + "g": "g̺͆", + "h": "h̺͆", + "i": "i̺͆", + "j": "j̺͆", + "k": "k̺͆", + "l": "l̺͆", + "m": "m̺͆", + "n": "n̺͆", + "o": "o̺͆", + "p": "p̺͆", + "q": "q̺͆", + "r": "r̺͆", + "s": "s̺͆", + "t": "t̺͆", + "u": "u̺͆", + "v": "v̺͆", + "w": "w̺͆", + "x": "x̺͆", + "y": "y̺͆", + "z": "z̺͆", + "A": "A̺͆", + "B": "B̺͆", + "C": "C̺͆", + "D": "D̺͆", + "E": "E̺͆", + "F": "F̺͆", + "G": "G̺͆", + "H": "H̺͆", + "I": "I̺͆", + "J": "J̺͆", + "K": "K̺͆", + "L": "L̺͆", + "M": "M̺͆", + "N": "N̺͆", + "O": "O̺͆", + "P": "P̺͆", + "Q": "Q̺͆", + "R": "R̺͆", + "S": "S̺͆", + "T": "T̺͆", + "U": "U̺͆", + "V": "V̺͆", + "W": "W̺͆", + "X": "X̺͆", + "Y": "Y̺͆", + "Z": "Z̺͆", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def arrows(text): + style = { + "a": "a͎", + "b": "b͎", + "c": "c͎", + "d": "d͎", + "e": "e͎", + "f": "f͎", + "g": "g͎", + "h": "h͎", + "i": "i͎", + "j": "j͎", + "k": "k͎", + "l": "l͎", + "m": "m͎", + "n": "n͎", + "o": "o͎", + "p": "p͎", + "q": "q͎", + "r": "r͎", + "s": "s͎", + "t": "t͎", + "u": "u͎", + "v": "v͎", + "w": "w͎", + "x": "x͎", + "y": "y͎", + "z": "z͎", + "A": "A͎", + "B": "B͎", + "C": "C͎", + "D": "D͎", + "E": "E͎", + "F": "F͎", + "G": "G͎", + "H": "H͎", + "I": "I͎", + "J": "J͎", + "K": "K͎", + "L": "L͎", + "M": "M͎", + "N": "N͎", + "O": "O͎", + "P": "P͎", + "Q": "Q͎", + "R": "R͎", + "S": "S͎", + "T": "T͎", + "U": "U͎", + "V": "V͎", + "W": "W͎", + "X": "X͎", + "Y": "Y͎", + "Z": "Z͎", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def rvnes(text): + style = { + "a": "ል", + "b": "ጌ", + "c": "ር", + "d": "ዕ", + "e": "ቿ", + "f": "ቻ", + "g": "ኗ", + "h": "ዘ", + "i": "ጎ", + "j": "ጋ", + "k": "ጕ", + "l": "ረ", + "m": "ጠ", + "n": "ክ", + "o": "ዐ", + "p": "የ", + "q": "ዒ", + "r": "ዪ", + "s": "ነ", + "t": "ፕ", + "u": "ሁ", + "v": "ሀ", + "w": "ሠ", + "x": "ሸ", + "y": "ሃ", + "z": "ጊ", + "A": "ል", + "B": "ጌ", + "C": "ር", + "D": "ዕ", + "E": "ቿ", + "F": "ቻ", + "G": "ኗ", + "H": "ዘ", + "I": "ጎ", + "J": "ጋ", + "K": "ጕ", + "L": "ረ", + "M": "ጠ", + "N": "ክ", + "O": "ዐ", + "P": "የ", + "Q": "ዒ", + "R": "ዪ", + "S": "ነ", + "T": "ፕ", + "U": "ሁ", + "V": "ሀ", + "W": "ሠ", + "X": "ሸ", + "Y": "ሃ", + "Z": "ጊ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def strike(text): + style = { + "a": "a̶", + "b": "b̶", + "c": "c̶", + "d": "d̶", + "e": "e̶", + "f": "f̶", + "g": "g̶", + "h": "h̶", + "i": "i̶", + "j": "j̶", + "k": "k̶", + "l": "l̶", + "m": "m̶", + "n": "n̶", + "o": "o̶", + "p": "p̶", + "q": "q̶", + "r": "r̶", + "s": "s̶", + "t": "t̶", + "u": "u̶", + "v": "v̶", + "w": "w̶", + "x": "x̶", + "y": "y̶", + "z": "z̶", + "A": "A̶", + "B": "B̶", + "C": "C̶", + "D": "D̶", + "E": "E̶", + "F": "F̶", + "G": "G̶", + "H": "H̶", + "I": "I̶", + "J": "J̶", + "K": "K̶", + "L": "L̶", + "M": "M̶", + "N": "N̶", + "O": "O̶", + "P": "P̶", + "Q": "Q̶", + "R": "R̶", + "S": "S̶", + "T": "T̶", + "U": "U̶", + "V": "V̶", + "W": "W̶", + "X": "X̶", + "Y": "Y̶", + "Z": "Z̶", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def frozen(text): + style = { + "a": "a༙", + "b": "b༙", + "c": "c༙", + "d": "d༙", + "e": "e༙", + "f": "f༙", + "g": "g༙", + "h": "h༙", + "i": "i༙", + "j": "j༙", + "k": "k༙", + "l": "l༙", + "m": "m༙", + "n": "n༙", + "o": "o༙", + "p": "p༙", + "q": "q༙", + "r": "r༙", + "s": "s༙", + "t": "t༙", + "u": "u༙", + "v": "v༙", + "w": "w༙", + "x": "x༙", + "y": "y༙", + "z": "z༙", + "A": "A༙", + "B": "B༙", + "C": "C༙", + "D": "D༙", + "E": "E༙", + "F": "F༙", + "G": "G༙", + "H": "H༙", + "I": "I༙", + "J": "J༙", + "K": "K༙", + "L": "L༙", + "M": "M༙", + "N": "N༙", + "O": "O༙", + "P": "P༙", + "Q": "Q༙", + "R": "R༙", + "S": "S༙", + "T": "T༙", + "U": "U༙", + "V": "V༙", + "W": "W༙", + "X": "X༙", + "Y": "Y༙", + "Z": "Z༙", + } + for i, j in style.items(): + text = text.replace(i, j) + return text diff --git a/QueenNoxi/utils/formatter.py b/QueenNoxi/utils/formatter.py new file mode 100644 index 0000000000000000000000000000000000000000..c9d35b6e639d7d6f513602300a958e5314980545 --- /dev/null +++ b/QueenNoxi/utils/formatter.py @@ -0,0 +1,26 @@ +def get_readable_time(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + + while count < 4: + count += 1 + if count < 3: + remainder, result = divmod(seconds, 60) + else: + remainder, result = divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += time_list.pop() + ", " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time diff --git a/QueenNoxi/utils/functions.py b/QueenNoxi/utils/functions.py new file mode 100644 index 0000000000000000000000000000000000000000..503e36af9a20464cc2e42628239637626091c5fa --- /dev/null +++ b/QueenNoxi/utils/functions.py @@ -0,0 +1,11 @@ +from io import BytesIO + +from aiohttp import ClientSession + + +async def make_carbon(code): + url = "https://carbonara.solopov.dev/api/cook" + async with ClientSession().post(url, json={"code": code}) as resp: + image = BytesIO(await resp.read()) + image.name = "carbon.png" + return image diff --git a/QueenNoxi/utils/mongo.py b/QueenNoxi/utils/mongo.py new file mode 100644 index 0000000000000000000000000000000000000000..258d4bdbfcb98617c61189b720ee8bf3e5902a39 --- /dev/null +++ b/QueenNoxi/utils/mongo.py @@ -0,0 +1,122 @@ +from typing import Dict, Union + +from motor.motor_asyncio import AsyncIOMotorClient as MongoCli + +from QueenNoxi import MONGO_DB_URI + +mongo = MongoCli(MONGO_DB_URI) +db = mongo.QueenNoxi + +coupledb = db.couple +karmadb = db.karma + + +async def _get_lovers(chat_id: int): + lovers = await coupledb.find_one({"chat_id": chat_id}) + if lovers: + lovers = lovers["couple"] + else: + lovers = {} + return lovers + + +async def get_couple(chat_id: int, date: str): + lovers = await _get_lovers(chat_id) + if date in lovers: + return lovers[date] + else: + return False + + +async def save_couple(chat_id: int, date: str, couple: dict): + lovers = await _get_lovers(chat_id) + lovers[date] = couple + await coupledb.update_one( + {"chat_id": chat_id}, + {"$set": {"couple": lovers}}, + upsert=True, + ) + + +async def get_karmas_count() -> dict: + chats_count = 0 + karmas_count = 0 + async for chat in karmadb.find({"chat_id": {"$lt": 0}}): + for i in chat["karma"]: + karma_ = chat["karma"][i]["karma"] + if karma_ > 0: + karmas_count += karma_ + chats_count += 1 + return {"chats_count": chats_count, "karmas_count": karmas_count} + + +async def user_global_karma(user_id) -> int: + total_karma = 0 + async for chat in karmadb.find({"chat_id": {"$lt": 0}}): + karma = await get_karma(chat["chat_id"], await int_to_alpha(user_id)) + if karma and (int(karma["karma"]) > 0): + total_karma += int(karma["karma"]) + return total_karma + + +async def get_karmas(chat_id: int) -> Dict[str, int]: + karma = await karmadb.find_one({"chat_id": chat_id}) + if not karma: + return {} + return karma["karma"] + + +async def get_karma(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + karmas = await get_karmas(chat_id) + if name in karmas: + return karmas[name] + + +async def update_karma(chat_id: int, name: str, karma: dict): + name = name.lower().strip() + karmas = await get_karmas(chat_id) + karmas[name] = karma + await karmadb.update_one( + {"chat_id": chat_id}, {"$set": {"karma": karmas}}, upsert=True + ) + + +async def is_karma_on(chat_id: int) -> bool: + chat = await karmadb.find_one({"chat_id_toggle": chat_id}) + if not chat: + return True + return False + + +async def karma_on(chat_id: int): + is_karma = await is_karma_on(chat_id) + if is_karma: + return + return await karmadb.delete_one({"chat_id_toggle": chat_id}) + + +async def karma_off(chat_id: int): + is_karma = await is_karma_on(chat_id) + if not is_karma: + return + return await karmadb.insert_one({"chat_id_toggle": chat_id}) + + +async def int_to_alpha(user_id: int) -> str: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + text = "" + user_id = str(user_id) + for i in user_id: + text += alphabet[int(i)] + return text + + +async def alpha_to_int(user_id_alphabet: str) -> int: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + user_id = "" + for i in user_id_alphabet: + index = alphabet.index(i) + user_id += str(index) + user_id = int(user_id) + return user_id diff --git a/QueenNoxi/utils/pastebin.py b/QueenNoxi/utils/pastebin.py new file mode 100644 index 0000000000000000000000000000000000000000..ef7908cb77cfe3b893e590c5e5c9e954c6690c2c --- /dev/null +++ b/QueenNoxi/utils/pastebin.py @@ -0,0 +1,24 @@ +import socket,requests +from asyncio import get_running_loop +from functools import partial + + +def _netcat(host, port, content): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.sendall(content.encode()) + s.shutdown(socket.SHUT_WR) + while True: + data = s.recv(4096).decode("utf-8").strip("\n\x00") + if not data: + break + return data + s.close() + + +async def paste(content): + url ="https://pastebin.com/api/api_post.php" + data = {"api_dev_key":"9Rfu50iV5l3EuRWATw7EDLuC37RED-C4","api_paste_code": content,"api_option": "paste"} + response = requests.post(url, data=data) + link=response.text + return link diff --git a/QueenNoxi/utils/post.py b/QueenNoxi/utils/post.py new file mode 100644 index 0000000000000000000000000000000000000000..7eabd9dc072f1303c93864e326e7725c6d2b2be7 --- /dev/null +++ b/QueenNoxi/utils/post.py @@ -0,0 +1,8 @@ +async def post(url: str, *args, **kwargs): + import QueenNoxi + async with QueenNoxi.aiohttpsession.post(url, *args, **kwargs) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data diff --git a/QueenNoxi/verify_gifs.py b/QueenNoxi/verify_gifs.py new file mode 100644 index 0000000000000000000000000000000000000000..b3d992f63f3f2aa92a9dd5fd305fd2520e7b5e68 --- /dev/null +++ b/QueenNoxi/verify_gifs.py @@ -0,0 +1,22 @@ +import asyncio +import aiohttp + +urls = [ + "https://raw.githubusercontent.com/Abishnoi69/QueenNoxi/main/QueenNoxi/resources/hacking.gif", + "https://raw.githubusercontent.com/Abishnoi69/QueenNoxi/main/QueenNoxi/resources/police.gif", + "https://raw.githubusercontent.com/Abishnoi69/QueenNoxi/main/QueenNoxi/resources/bomb.gif", + "https://raw.githubusercontent.com/Abishnoi69/QueenNoxi/main/QueenNoxi/resources/sleep.gif", + "https://raw.githubusercontent.com/Abishnoi69/QueenNoxi/main/QueenNoxi/resources/brain.gif", + "https://raw.githubusercontent.com/Abishnoi69/QueenNoxi/main/QueenNoxi/resources/clock.gif" +] + +async def verify(): + async with aiohttp.ClientSession() as session: + for url in urls: + try: + async with session.get(url, timeout=10) as resp: + print(f"{url}: {resp.status} - {resp.headers.get('Content-Type')}") + except Exception as e: + print(f"{url}: FAILED - {e}") + +asyncio.run(verify()) diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bfb6956dbf32d8c168ea28aac9f4d48cd41f2a30 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +--- +title: Queen Noxibot +emoji: 👸 +colorFrom: purple +colorTo: pink +sdk: docker +pinned: false +--- + +# Queen Noxibot + +A powerful Telegram group management bot migrated to Pyrogram and deployed on HuggingFace Spaces. + +## Setup +Refer to [SETUP_GUIDE.md](SETUP_GUIDE.md) for full configuration instructions. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..034e848032092eaf8ef96eac731b6ed5961987f3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md new file mode 100644 index 0000000000000000000000000000000000000000..85fe3e93929c7aa1554ca55b0452c689d70f60a8 --- /dev/null +++ b/SETUP_GUIDE.md @@ -0,0 +1,166 @@ +# 🤖 QueenNoxi — Complete Setup Guide + +> Get every API key and config value needed to run the bot. + +--- + +## 1. Telegram API Credentials (`API_ID` & `API_HASH`) + +1. Go to **[my.telegram.org](https://my.telegram.org)** +2. Log in with your phone number +3. Click **"API development tools"** +4. Create an app (any name/platform is fine) +5. Copy **App api_id** → `API_ID` +6. Copy **App api_hash** → `API_HASH` + +--- + +## 2. Bot Token (`TOKEN`) + +1. Open Telegram → message **[@BotFather](https://t.me/BotFather)** +2. Send `/newbot` +3. Choose a **name** (display name, e.g. `Queen Noxi`) +4. Choose a **username** (must end in `bot`, e.g. `QueenNoxiGroupBot`) +5. BotFather replies with your token: + ``` + 123456789:AABBccDDeeFFggHHiiJJ... + ``` + → `TOKEN` +6. Also set `BOT_NAME` = the display name you chose +7. Also set `BOT_USERNAME` = the username **without @** + +> **Tip:** Also send `/setprivacy` → `Disable` to let the bot read all group messages. + +--- + +## 3. Your Owner ID (`OWNER_IDS`) + +Message **[@userinfobot](https://t.me/userinfobot)** on Telegram. +It replies with your numeric user ID, e.g. `123456789`. + +To add **multiple owners**, separate with spaces: +``` +OWNER_IDS=123456789 987654321 +``` + +--- + +## 4. MongoDB (`MONGO_DB_URI`) + +> You already have MongoDB Atlas — reuse the same cluster or create a new database in it. + +**To add a new database in your existing Atlas cluster:** +1. Go to **[cloud.mongodb.com](https://cloud.mongodb.com)** +2. Open your cluster → **Browse Collections** → **Add My Own Data** +3. Create a database named `QueenNoxi` +4. Your existing connection URI works — just change the DB name at the end: + ``` + mongodb+srv://user:pass@cluster.xxxxx.mongodb.net/QueenNoxi?retryWrites=true&w=majority + ``` + → `MONGO_DB_URI` + +--- + +## 5. PostgreSQL Database (`DATABASE_URL`) + +### 🆓 Recommended: [Neon.tech](https://neon.tech) +*(Free forever — serverless, no credit card, never sleeps)* + +1. Sign up at **[neon.tech](https://neon.tech)** (GitHub login works) +2. Click **"New Project"** → choose a region closest to you +3. Wait ~10 seconds for provisioning +4. Go to **Dashboard → Connection Details** +5. Select **"Pooled connection"** and copy the string: + ``` + postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require + ``` + → `DATABASE_URL` + +| Alternative | Free Tier | Notes | +|---|---|---| +| [Supabase](https://supabase.com) | 500 MB | Pauses after 1 week inactive | +| [Railway](https://railway.app) | $5 credit/mo | Easiest UI | +| [Aiven](https://aiven.io) | 1 free service | Good uptime | + +--- + +## 6. Support Chat (`SUPPORT_CHAT`) + +You can provide either a public username or a full invite link. + +- **Public Group:** Set `SUPPORT_CHAT=yourgroupusername` (without `@`) +- **Private Group:** Set `SUPPORT_CHAT=https://t.me/+xyz123...` (full invite link) + +The bot will automatically handle either format for the support buttons in the menu. + +--- + +## 7. Start Image (`START_IMG`) + +Any **direct image URL** (must end in `.jpg`, `.png`, `.webp`). + +Quick options: +- Upload to [imgbb.com](https://imgbb.com) → get direct link +- Use any public Telegram CDN URL +- Use a raw GitHub image URL + +--- + +## 8. Log Channel (`EVENT_LOGS`) + +1. Create a private Telegram channel +2. Add your bot as **admin** +3. Forward any message from the channel to **[@getidsbot](https://t.me/getidsbot)** +4. Copy the **channel ID** (looks like `-100xxxxxxxxxx`) + → `EVENT_LOGS` + +--- + +## 9. Optional API Keys + +| Variable | Service | Cost | Link | +|---|---|---|---| +| `CASH_API_KEY` | Currency conversion | Free | [api.freecurrencyapi.com](https://freecurrencyapi.com) | +| `TIME_API_KEY` | Timezone lookup | Free | [timezonedb.com](https://timezonedb.com/api) | + +--- + +## 10. Deploying on HuggingFace Spaces + +1. Push this repo to **[Hugging Face](https://huggingface.co/new-space)** + - Space SDK: **Docker** + - Visibility: Public or Private +2. After creating, go to **Settings → Variables and secrets** +3. Add each key below as a **Secret** (sensitive) or Variable: + +| Type | Variables | +|---|---| +| 🔐 Secret | `API_ID`, `API_HASH`, `TOKEN`, `MONGO_DB_URI`, `DATABASE_URL`, `OWNER_IDS` | +| 📝 Variable | `BOT_NAME`, `BOT_USERNAME`, `SUPPORT_CHAT`, `START_IMG`, `EVENT_LOGS`, `WORKERS` | + +4. The Space auto-builds and starts — watch **Logs** for any errors. +5. Your bot should come online within 2–3 minutes. + +> **Health check:** HuggingFace pings `https://your-space.hf.space/health` +> The bot responds with `✅ QueenNoxi is running!` + +--- + +## 11. Minimum Required `.env` for Local Testing + +```env +API_ID=12345678 +API_HASH=your_api_hash_here +TOKEN=bot:token_here +OWNER_IDS=123456789 +MONGO_DB_URI=mongodb+srv://user:pass@cluster.mongodb.net/QueenNoxi +BOT_NAME=QueenNoxi +BOT_USERNAME=yourbotusername +``` + +Run locally with: +```powershell +# Load .env vars and start the bot +Get-Content .env | ForEach-Object { if ($_ -match '^([^#=]+)=(.*)$') { [System.Environment]::SetEnvironmentVariable($Matches[1].Trim(), $Matches[2].Trim()) } } +python app.py +``` diff --git a/Setup venv.bat b/Setup venv.bat new file mode 100644 index 0000000000000000000000000000000000000000..0a84eb21092d85e99ed56428ce2c7635347c893c --- /dev/null +++ b/Setup venv.bat @@ -0,0 +1,6 @@ +TITLE Setting up virtual env +:: Running it once is fine, this just sets up virtual env >> install all modules there +py -m venv env && env\scripts\activate.bat && pip install -r requirements.txt + +:: Note to rerun the requirements.txt in case you ever add a module. +:: Running this multiple time will not make a mess of your setup, dont worry about that bit. diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..3397c9a4928e1b41957484ee4e53e78da37224e8 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-architect \ No newline at end of file diff --git a/app.json b/app.json new file mode 100644 index 0000000000000000000000000000000000000000..9bc2efba05ee4d43d7645cc74cf5be0f9ccdb2ec --- /dev/null +++ b/app.json @@ -0,0 +1,86 @@ +{ + "name": "Queen Noxi", + "description": "Telegram Group Management Bot.", + "logo": "https://te.legra.ph/file/49c43e6fc856534a09853.jpg", + "keywords": [ + "telegram", + "modular", + "group", + "manager" + ], + "repository": "https://github.com/Noob-QueenNoxi/QueenNoxi", + "stack": "heroku-22", + "addons": [ + { + "options": { + "version": "15" + }, + "plan": "heroku-postgresql" + } + ], + "env": { + "API_ID": { + "description": "Get this value from my.telegram.org/apps.", + "required": true, + "value": "" + }, + "API_HASH": { + "description": "Get this value from my.telegram.org/apps.", + "required": true, + "value": "" + }, + "ALLOW_EXCL": { + "description": "Set this to True if you want ! to be a command prefix along with /. Recommended is True", + "value": "True" + }, + "DEL_CMDS": { + "description": "Set this to True if you want to delete command messages from users who don't have the perms to run that command.", + "value": "True" + }, + "ENV": { + "description": "Setting this to ANYTHING will enable environment variables. Leave it as it is", + "required": true, + "value": "True" + }, + "EVENT_LOGS": { + "description": "Event logs channel to note down important bot level events, recommend to make this public. ex: '-123456'", + "required": true, + "value": "" + }, + "MONGO_DB_URI": { + "description": "Required for database connections.", + "required": true, + "value": "" + }, + "CASH_API_KEY": { + "description": " Get your API key from https://www.alphavantage.co/support/#api-key", + "required": false, + "value": "" + }, + "TIME_API_KEY": { + "description": " Get your API key from https://timezonedb.com/api", + "required": false, + "value": "" + }, + "OWNER_ID": { + "description": "Your user ID as an integer.", + "required": true, + "value": "" + }, + "START_IMG": { + "description": "Telegraph link of the image which will be shown at start command.", + "required": true, + "value": "" + }, + "SUPPORT_CHAT": { + "description": "Your Telegram support group chat username where your users will go and bother you with shit Example - The_support_chat.", + "required": true, + "value": "" + }, + "TOKEN": { + "description": "Get bot token from @BotFather on TG", + "required": true, + "value": "" + } + } +} diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..ae7a2564512e8bc0495306e176bb962040255da3 --- /dev/null +++ b/app.py @@ -0,0 +1,41 @@ +""" +HuggingFace Spaces entrypoint. + +HuggingFace Docker Spaces REQUIRE an HTTP server responding on port 7860. +We spin up a minimal aiohttp web server alongside the Telegram bot so the +Space stays healthy. The HTTP server just returns 200 OK on any request. +""" +import asyncio +import os +from aiohttp import web + +# Import of QueenNoxi.main is delayed to avoid event loop conflicts + + +async def health(request): + return web.Response(text="✅ QueenNoxi is running!", content_type="text/plain") + + +async def run_web_server(): + port = int(os.environ.get("PORT", 7860)) + app = web.Application() + app.router.add_get("/", health) + app.router.add_get("/health", health) + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, "0.0.0.0", port) + await site.start() + print(f"[Web] Health server running on port {port}") + + +async def run_all(): + # Start web server sequentially (it is non-blocking) + await run_web_server() + + # Delayed import to ensure Pyrogram objects bind to the loop created by asyncio.run() + from QueenNoxi.__main__ import main + await main() + + +if __name__ == "__main__": + asyncio.run(run_all()) diff --git a/exp.sh b/exp.sh new file mode 100644 index 0000000000000000000000000000000000000000..79add3df572f30bd1fb8b35b1e2c69630d2622ef --- /dev/null +++ b/exp.sh @@ -0,0 +1,2 @@ +sudo bash -c 'echo "{ \"cgroup-parent\": \"/actions_job\",\"experimental\":true}" > /etc/docker/daemon.json' +sudo systemctl restart docker.service \ No newline at end of file diff --git a/heroku.yml b/heroku.yml new file mode 100644 index 0000000000000000000000000000000000000000..5cb10eca1cafc05d7aa93557b0baf63e2849d4d5 --- /dev/null +++ b/heroku.yml @@ -0,0 +1,5 @@ +build: + docker: + worker: Dockerfile +run: + worker: python3 -m QueenNoxi diff --git a/queennoxi.session-journal b/queennoxi.session-journal new file mode 100644 index 0000000000000000000000000000000000000000..b6552524213f58b00e719a14a5c0ee765807222d Binary files /dev/null and b/queennoxi.session-journal differ diff --git a/remote_diag.py b/remote_diag.py new file mode 100644 index 0000000000000000000000000000000000000000..8037a9a92a6690692acdfb7ff5a7cc7b98b4297e --- /dev/null +++ b/remote_diag.py @@ -0,0 +1,23 @@ +import pyrogram +import os +import sys + +print(f"Python Version: {sys.version}") +print(f"Pyrogram Version: {pyrogram.__version__}") +print(f"Pyrogram Path: {pyrogram.__file__}") + +# Check if it's the fixed version +import inspect +from pyrogram.types import ChatPermissions +print(f"ChatPermissions init signature: {inspect.signature(ChatPermissions.__init__)}") + +# Check system time +import time +import requests +try: + r = requests.get("https://google.com", timeout=5) + from email.utils import parsedate_to_datetime + server_t = parsedate_to_datetime(r.headers['Date']).timestamp() + print(f"Sync Check: Local={time.time()}, Server={server_t}, Delta={time.time() - server_t}") +except Exception as e: + print(f"Sync check failed: {e}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..77dc60b7c712dbc16d3f07430213c804b837cb41 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,58 @@ +aiofiles==24.1.0 +aiohttp==3.9.5 +alphabet_detector +apscheduler==3.6.3 +beautifulsoup4==4.12.3 +bing_image_downloader==1.1.2 +bleach==6.1.0 +bs4==0.0.2 +cachetools==4.2.2 +cloudscraper==1.2.71 +countryinfo==0.1.2 +currencyconverter==0.17.26 +dateparser==1.2.0 +emoji==2.12.1 +emoji-country-flag==2.0.1 +faker +feedparser==6.0.11 +ffmpeg-python==0.2.0 +future==0.18.3 +geopy==2.4.1 +gtts +gpytranslate==1.5.1 +hachoir==3.3.0 +heroku3 +html2text==2024.2.26 +jikanpy==4.3.2 +lxml==4.9.4 +markdown2==2.5.0 +MukeshAPI +motor==3.6.0 +nekos.py +opencv-python +pretty-errors +pydictionary==2.0.1 +pyfiglet +# pykeyboard removed due to pyrogram version conflict +pymongo==4.9.2 +pynewtonmath==1.0.1 +pyrate-limiter==2.10.0 +pyshorteners +kurigram>=2.2.13 +regex==2024.5.15 +requests==2.32.3 +search_engine_parser==0.6.8 +secureme==0.0.4 +speedtest-cli==2.1.3 +sqlalchemy==1.4.46 +telegraph==2.2.0 +telethon==1.36.0 +tgcrypto==1.2.5 +wikipedia==1.4.0 +wget +yt_dlp +youtube_search +youtube_search_python +psycopg2-binary +setuptools +python-dotenv \ No newline at end of file diff --git a/restart.bat b/restart.bat new file mode 100644 index 0000000000000000000000000000000000000000..22342d90605fd96a62c1812b083e0bac99c4bc98 --- /dev/null +++ b/restart.bat @@ -0,0 +1,2 @@ +:: starts a cmd to silently start the bat file, just another dirty way of getting things done for my env on windows +start cmd.exe /c start_service.bat \ No newline at end of file diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000000000000000000000000000000000000..3124d55e9821701ba685d2c18700339e5696b17e --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.11.5 diff --git a/start.bat b/start.bat new file mode 100644 index 0000000000000000000000000000000000000000..6949cec2a6147cd5e8362d0bad8b98dd3701e379 --- /dev/null +++ b/start.bat @@ -0,0 +1,4 @@ +@echo off +TITLE Queen Noxi +:: Enables virtual env mode and then starts QueenNoxi +env\scripts\activate.bat && py -m QueenNoxi diff --git a/start_service.bat b/start_service.bat new file mode 100644 index 0000000000000000000000000000000000000000..2ca18b6d579eaa9cac7b499b1d190d3d8de0790b --- /dev/null +++ b/start_service.bat @@ -0,0 +1,31 @@ +@echo off +:: This runs the batch file as an admin - required UAC to be off +:: This is just an asty hack in to get job done cause we host it on windows dedi. +:: BatchGotAdmin +:------------------------------------- +REM --> Check for permissions +>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" + +REM --> If error flag set, we do not have admin. +if '%errorlevel%' NEQ '0' ( + echo Requesting administrative privileges... + goto UACPrompt +) else ( goto gotAdmin ) + +:UACPrompt + echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" + set params = %*:"="" + echo UAC.ShellExecute "cmd.exe", "/c %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" + + "%temp%\getadmin.vbs" + del "%temp%\getadmin.vbs" + exit /B + +:gotAdmin + pushd "%CD%" + CD /D "%~dp0" +:-------------------------------------- +:: your commands begin from this point. +:: stops the service and then starts it +net stop QueenNoxi +net start QueenNoxi diff --git a/test_ai.py b/test_ai.py new file mode 100644 index 0000000000000000000000000000000000000000..190548ae872100bdeff4d1375c02bd69f2f8b884 --- /dev/null +++ b/test_ai.py @@ -0,0 +1,57 @@ +import asyncio +import aiohttp +from MukeshAPI import api + +async def test_ai(): + print("Testing ChatGPT...") + try: + res = api.chatgpt("Hello") + print(f"ChatGPT Result: {res}") + except Exception as e: + print(f"ChatGPT Failed: {e}") + + print("\nTesting Widipe OpenAI...") + async with aiohttp.ClientSession() as session: + try: + async with session.get(f"https://widipe.com/openai?text=Hello", timeout=10) as resp: + res = await resp.json() + print(f"Widipe Result: {res}") + except Exception as e: + print(f"Widipe Failed: {e}") + + print("\nTesting SimSimi...") + async with aiohttp.ClientSession() as session: + try: + async with session.get(f"https://api.simsimi.net/v2/?text=Hello&lc=en", timeout=10) as resp: + res = await resp.json() + print(f"SimSimi Result: {res}") + except Exception as e: + print(f"SimSimi Failed: {e}") + + print("\nTesting Safone AI...") + async with aiohttp.ClientSession() as session: + try: + async with session.get(f"https://api.safone.me/ai?message=Hello", timeout=10) as resp: + res = await resp.json() + print(f"Safone Result: {res}") + except Exception as e: + print(f"Safone Failed: {e}") + + print("\nTesting Vyturex AI...") + async with aiohttp.ClientSession() as session: + try: + async with session.get(f"https://api.vyturex.com/openai?prompt=Hello", timeout=10) as resp: + res = await resp.json() + print(f"Vyturex Result: {res}") + except Exception as e: + print(f"Vyturex Failed: {e}") + + print("\nTesting GPT4...") + try: + res = api.gpt4("Hello") + print(f"GPT4 Result: {res}") + except Exception as e: + print(f"GPT4 Failed: {e}") + +if __name__ == "__main__": + asyncio.run(test_ai()) diff --git a/test_decorator.py b/test_decorator.py new file mode 100644 index 0000000000000000000000000000000000000000..0bd859ad583995a5a056745e0cbc5dad9a8b9b3c --- /dev/null +++ b/test_decorator.py @@ -0,0 +1,20 @@ +import sys +import os + +# Add project root to sys.path +sys.path.append(os.getcwd()) + +from QueenNoxi.modules.disable import DisableAbleCommandHandler + +print(f"DisableAbleCommandHandler: {DisableAbleCommandHandler}") + +def test_func(client, message): + pass + +try: + decorator = DisableAbleCommandHandler(["notes", "saved"]) + print(f"Decorator created: {decorator}") + wrapped = decorator(test_func) + print(f"Wrapped function: {wrapped}") +except Exception as e: + print(f"Error: {e}") diff --git a/updates.json b/updates.json new file mode 100644 index 0000000000000000000000000000000000000000..f86060b14e82a8352d4306cb846795c5451b38a4 --- /dev/null +++ b/updates.json @@ -0,0 +1,598 @@ +{ + "ok": true, + "result": [ + { + "update_id": 530312415, + "message": { + "message_id": 2481705, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332351, + "message_thread_id": 2481587, + "reply_to_message": { + "message_id": 2481588, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778330351, + "message_thread_id": 2481587, + "sticker": { + "width": 512, + "height": 512, + "emoji": "\ud83d\ude2d", + "set_name": "soulscrybambies", + "is_animated": false, + "is_video": true, + "type": "regular", + "thumbnail": { + "file_id": "AAMCBQADHQJRKYSlAAEl3bRp_zK_wOqCEeRG1k47KZeHMX70XQACKhcAAgOnwFXy16T7_uiSRQEAB20AAzsE", + "file_unique_id": "AQADKhcAAgOnwFVy", + "file_size": 8378, + "width": 320, + "height": 320 + }, + "thumb": { + "file_id": "AAMCBQADHQJRKYSlAAEl3bRp_zK_wOqCEeRG1k47KZeHMX70XQACKhcAAgOnwFXy16T7_uiSRQEAB20AAzsE", + "file_unique_id": "AQADKhcAAgOnwFVy", + "file_size": 8378, + "width": 320, + "height": 320 + }, + "file_id": "CAACAgUAAx0CUSmEpQABJd20af8yv8DqghHkRtZOOymXhzF-9F0AAioXAAIDp8BV8tek-_7okkU7BA", + "file_unique_id": "AgADKhcAAgOnwFU", + "file_size": 163775 + } + }, + "sticker": { + "width": 512, + "height": 512, + "emoji": "\ud83d\udc4d", + "set_name": "f7319930609_by_SONALI_ROBOT", + "is_animated": false, + "is_video": false, + "type": "regular", + "thumbnail": { + "file_id": "AAMCBQADHQJRKYSlAAEl3ilp_zK_lzGAeH1axhV587WN-4bIxQACNhEAAiqj0Vax4dB4WZGNywEAB20AAzsE", + "file_unique_id": "AQADNhEAAiqj0VZy", + "file_size": 8566, + "width": 320, + "height": 320 + }, + "thumb": { + "file_id": "AAMCBQADHQJRKYSlAAEl3ilp_zK_lzGAeH1axhV587WN-4bIxQACNhEAAiqj0Vax4dB4WZGNywEAB20AAzsE", + "file_unique_id": "AQADNhEAAiqj0VZy", + "file_size": 8566, + "width": 320, + "height": 320 + }, + "file_id": "CAACAgUAAx0CUSmEpQABJd4paf8yv5cxgHh9WsYVefO1jfuGyMUAAjYRAAIqo9FWseHQeFmRjcs7BA", + "file_unique_id": "AgADNhEAAiqj0VY", + "file_size": 15522 + } + } + }, + { + "update_id": 530312416, + "message": { + "message_id": 2481706, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332394, + "message_thread_id": 2481587, + "reply_to_message": { + "message_id": 2481705, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332351, + "message_thread_id": 2481587, + "sticker": { + "width": 512, + "height": 512, + "emoji": "\ud83d\udc4d", + "set_name": "f7319930609_by_SONALI_ROBOT", + "is_animated": false, + "is_video": false, + "type": "regular", + "thumbnail": { + "file_id": "AAMCBQADHQJRKYSlAAEl3ilp_zK_lzGAeH1axhV587WN-4bIxQACNhEAAiqj0Vax4dB4WZGNywEAB20AAzsE", + "file_unique_id": "AQADNhEAAiqj0VZy", + "file_size": 8566, + "width": 320, + "height": 320 + }, + "thumb": { + "file_id": "AAMCBQADHQJRKYSlAAEl3ilp_zK_lzGAeH1axhV587WN-4bIxQACNhEAAiqj0Vax4dB4WZGNywEAB20AAzsE", + "file_unique_id": "AQADNhEAAiqj0VZy", + "file_size": 8566, + "width": 320, + "height": 320 + }, + "file_id": "CAACAgUAAx0CUSmEpQABJd4paf8yv5cxgHh9WsYVefO1jfuGyMUAAjYRAAIqo9FWseHQeFmRjcs7BA", + "file_unique_id": "AgADNhEAAiqj0VY", + "file_size": 15522 + } + }, + "text": "Yalu krl denn" + } + }, + { + "update_id": 530312417, + "message": { + "message_id": 2481707, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332397, + "sticker": { + "width": 512, + "height": 512, + "emoji": "\ud83d\udc41", + "set_name": "Astasiadream", + "is_animated": false, + "is_video": true, + "type": "regular", + "thumbnail": { + "file_id": "AAMCAgADHQJRKYSlAAEl3itp_zLtM8qdadOP21BqGmd7VOLcsAAC5yMAAvTy6UiqMyz09djgRwEAB20AAzsE", + "file_unique_id": "AQAD5yMAAvTy6Uhy", + "file_size": 8910, + "width": 320, + "height": 320 + }, + "thumb": { + "file_id": "AAMCAgADHQJRKYSlAAEl3itp_zLtM8qdadOP21BqGmd7VOLcsAAC5yMAAvTy6UiqMyz09djgRwEAB20AAzsE", + "file_unique_id": "AQAD5yMAAvTy6Uhy", + "file_size": 8910, + "width": 320, + "height": 320 + }, + "file_id": "CAACAgIAAx0CUSmEpQABJd4raf8y7TPKnWnTj9tQahpne1Ti3LAAAucjAAL08ulIqjMs9PXY4Ec7BA", + "file_unique_id": "AgAD5yMAAvTy6Ug", + "file_size": 239701 + } + } + }, + { + "update_id": 530312418, + "message": { + "message_id": 2481708, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332401, + "text": "Kwd utto unknown soul\ud83d\ude02" + } + }, + { + "update_id": 530312419, + "message": { + "message_id": 2481709, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332409, + "sticker": { + "width": 512, + "height": 512, + "emoji": "\ud83d\udc2c", + "set_name": "zara912", + "is_animated": false, + "is_video": true, + "type": "regular", + "thumbnail": { + "file_id": "AAMCBQADHQJRKYSlAAEl3i1p_zL5lokEKRNlepRTpcJnEubM3gACPRsAAlbbUFVxhfUrYTKk_wEAB20AAzsE", + "file_unique_id": "AQADPRsAAlbbUFVy", + "file_size": 13476, + "width": 320, + "height": 320 + }, + "thumb": { + "file_id": "AAMCBQADHQJRKYSlAAEl3i1p_zL5lokEKRNlepRTpcJnEubM3gACPRsAAlbbUFVxhfUrYTKk_wEAB20AAzsE", + "file_unique_id": "AQADPRsAAlbbUFVy", + "file_size": 13476, + "width": 320, + "height": 320 + }, + "file_id": "CAACAgUAAx0CUSmEpQABJd4taf8y-ZaJBCkTZXqUU6XCZxLmzN4AAj0bAAJW21BVcYX1K2EypP87BA", + "file_unique_id": "AgADPRsAAlbbUFU", + "file_size": 104529 + } + } + }, + { + "update_id": 530312420, + "message": { + "message_id": 2481710, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332412, + "message_thread_id": 2481587, + "reply_to_message": { + "message_id": 2481706, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332394, + "message_thread_id": 2481587, + "text": "Yalu krl denn" + }, + "text": "mkk" + } + }, + { + "update_id": 530312421, + "message": { + "message_id": 2481711, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332413, + "text": "Dil" + } + }, + { + "update_id": 530312422, + "message": { + "message_id": 2481712, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332421, + "message_thread_id": 2481587, + "reply_to_message": { + "message_id": 2481710, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332412, + "message_thread_id": 2481587, + "text": "mkk" + }, + "text": "Mn" + } + }, + { + "update_id": 530312423, + "message": { + "message_id": 27, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": 1700916606, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "type": "private" + }, + "date": 1778332428, + "text": "/start", + "entities": [ + { + "offset": 0, + "length": 6, + "type": "bot_command" + } + ] + } + }, + { + "update_id": 530312424, + "message": { + "message_id": 2481713, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332447, + "message_thread_id": 2481587, + "reply_to_message": { + "message_id": 2481712, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332421, + "message_thread_id": 2481587, + "text": "Mn" + }, + "sticker": { + "width": 511, + "height": 512, + "emoji": "\ud83d\ude12", + "set_name": "Pishieow_by_demybot", + "is_animated": false, + "is_video": false, + "type": "regular", + "thumbnail": { + "file_id": "AAMCBAADHQJRKYSlAAEl3jFp_zMfOMDCQ7E8a9UxkP8LAAGIXzQAAmEQAALxshFQSbTv4TfAbyQBAAdtAAM7BA", + "file_unique_id": "AQADYRAAAvGyEVBy", + "file_size": 5634, + "width": 319, + "height": 320 + }, + "thumb": { + "file_id": "AAMCBAADHQJRKYSlAAEl3jFp_zMfOMDCQ7E8a9UxkP8LAAGIXzQAAmEQAALxshFQSbTv4TfAbyQBAAdtAAM7BA", + "file_unique_id": "AQADYRAAAvGyEVBy", + "file_size": 5634, + "width": 319, + "height": 320 + }, + "file_id": "CAACAgQAAx0CUSmEpQABJd4xaf8zHzjAwkOxPGvVMZD_CwABiF80AAJhEAAC8bIRUEm07-E3wG8kOwQ", + "file_unique_id": "AgADYRAAAvGyEVA", + "file_size": 11860 + } + } + }, + { + "update_id": 530312425, + "message": { + "message_id": 2481714, + "from": { + "id": 1700916606, + "is_bot": false, + "first_name": "\uff22\uff41\uff45\uff4c", + "username": "git_pul_l", + "language_code": "en" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332513, + "message_thread_id": 2481587, + "reply_to_message": { + "message_id": 2481713, + "from": { + "id": 136817688, + "is_bot": true, + "first_name": "Channel", + "username": "Channel_Bot" + }, + "sender_chat": { + "id": -1003065754461, + "title": "\u00bf?", + "username": "CoCo_Gng", + "type": "channel" + }, + "chat": { + "id": -1001361675429, + "title": "\ud835\uddd9\ud835\uddff\ud835\uddf6\ud835\uddf2\ud835\uddfb\ud835\uddf1'\ud835\ude00 \ud835\udddb\ud835\ude02\ud835\uddef \ud83e\ude75\u26d3\ufe0f\u200d\ud83d\udca5", + "username": "FriendsHubLK", + "type": "supergroup" + }, + "date": 1778332447, + "message_thread_id": 2481587, + "sticker": { + "width": 511, + "height": 512, + "emoji": "\ud83d\ude12", + "set_name": "Pishieow_by_demybot", + "is_animated": false, + "is_video": false, + "type": "regular", + "thumbnail": { + "file_id": "AAMCBAADHQJRKYSlAAEl3jFp_zMfOMDCQ7E8a9UxkP8LAAGIXzQAAmEQAALxshFQSbTv4TfAbyQBAAdtAAM7BA", + "file_unique_id": "AQADYRAAAvGyEVBy", + "file_size": 5634, + "width": 319, + "height": 320 + }, + "thumb": { + "file_id": "AAMCBAADHQJRKYSlAAEl3jFp_zMfOMDCQ7E8a9UxkP8LAAGIXzQAAmEQAALxshFQSbTv4TfAbyQBAAdtAAM7BA", + "file_unique_id": "AQADYRAAAvGyEVBy", + "file_size": 5634, + "width": 319, + "height": 320 + }, + "file_id": "CAACAgQAAx0CUSmEpQABJd4xaf8zHzjAwkOxPGvVMZD_CwABiF80AAJhEAAC8bIRUEm07-E3wG8kOwQ", + "file_unique_id": "AgADYRAAAvGyEVA", + "file_size": 11860 + } + }, + "sticker": { + "width": 512, + "height": 512, + "emoji": "\ud83d\udd2b", + "set_name": "Astasiadream", + "is_animated": false, + "is_video": true, + "type": "regular", + "thumbnail": { + "file_id": "AAMCAgADHQJRKYSlAAEl3jJp_zNhQ7BunlcqZKPYUuy7MkTdbwAC1yIAAtND4UgCtf6rWM6EQwEAB20AAzsE", + "file_unique_id": "AQAD1yIAAtND4Uhy", + "file_size": 13362, + "width": 320, + "height": 320 + }, + "thumb": { + "file_id": "AAMCAgADHQJRKYSlAAEl3jJp_zNhQ7BunlcqZKPYUuy7MkTdbwAC1yIAAtND4UgCtf6rWM6EQwEAB20AAzsE", + "file_unique_id": "AQAD1yIAAtND4Uhy", + "file_size": 13362, + "width": 320, + "height": 320 + }, + "file_id": "CAACAgIAAx0CUSmEpQABJd4yaf8zYUOwbp5XKmSj2FLsuzJE3W8AAtciAALTQ-FIArX-q1jOhEM7BA", + "file_unique_id": "AgAD1yIAAtND4Ug", + "file_size": 251139 + } + } + } + ] +} \ No newline at end of file