Spaces:
Running
Running
Alexainc commited on
Commit ·
2362a2a
0
Parent(s):
Initial QueenNoxi Build (Fresh Rebrand)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .deepsource.toml +8 -0
- .env.example +81 -0
- .gitattributes +7 -0
- .github/CODEOWNERS +2 -0
- .github/FUNDING.yml +13 -0
- .github/ISSUE_TEMPLATE/feature_request.md +20 -0
- .github/README.md +139 -0
- .github/dependabot.yml +14 -0
- .github/workflows/codeql.yml +98 -0
- .github/worklfows/pylint.yml +35 -0
- .gitignore +151 -0
- CODE_OF_CONDUCT.md +128 -0
- CONTRIBUTING.md +1 -0
- Dockerfile +46 -0
- Git_Pull.bat +8 -0
- Git_Push.bat +19 -0
- LICENSE +21 -0
- Procfile +2 -0
- QueenNoxi/.gitattributes +1 -0
- QueenNoxi/__init__.py +168 -0
- QueenNoxi/__main__.py +480 -0
- QueenNoxi/brain/data/brain.json +3 -0
- QueenNoxi/brain/data/dictionary.json +3 -0
- QueenNoxi/brain/data/persona.json +3 -0
- QueenNoxi/brain/data/phonetic_map.json +3 -0
- QueenNoxi/brain/data/slang.json +3 -0
- QueenNoxi/brain/data/users.json +3 -0
- QueenNoxi/brain/database/anon.json +3 -0
- QueenNoxi/brain/index.js +280 -0
- QueenNoxi/brain/query.js +62 -0
- QueenNoxi/brain/src/brain.js +150 -0
- QueenNoxi/brain/src/context.js +93 -0
- QueenNoxi/brain/src/dictionary.js +110 -0
- QueenNoxi/brain/src/group.js +115 -0
- QueenNoxi/brain/src/memory.js +204 -0
- QueenNoxi/brain/src/nlu.js +279 -0
- QueenNoxi/brain/src/processor.js +162 -0
- QueenNoxi/brain/src/storage.js +135 -0
- QueenNoxi/brain/src/trainer.js +168 -0
- QueenNoxi/config.py +113 -0
- QueenNoxi/events.py +179 -0
- QueenNoxi/modules/__init__.py +54 -0
- QueenNoxi/modules/admin.py +363 -0
- QueenNoxi/modules/aiimage.py +61 -0
- QueenNoxi/modules/alive.py +70 -0
- QueenNoxi/modules/animation.py +344 -0
- QueenNoxi/modules/anime.py +110 -0
- QueenNoxi/modules/animez.py +415 -0
- QueenNoxi/modules/antiban.py +54 -0
- QueenNoxi/modules/approve.py +166 -0
.deepsource.toml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
|
| 3 |
+
[[analyzers]]
|
| 4 |
+
name = "python"
|
| 5 |
+
enabled = true
|
| 6 |
+
|
| 7 |
+
[analyzers.meta]
|
| 8 |
+
runtime_version = "3.x.x"
|
.env.example
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ┌──────────────────────────────────────────────────────────────────────────┐
|
| 2 |
+
# │ MukeshRobot – Environment Variable Reference │
|
| 3 |
+
# │ Copy this file to .env for local development (never commit .env!) │
|
| 4 |
+
# └──────────────────────────────────────────────────────────────────────────┘
|
| 5 |
+
|
| 6 |
+
# ══════════════════════════════════════════════
|
| 7 |
+
# REQUIRED – bot will NOT start without these
|
| 8 |
+
# ══════════════════════════════════════════════
|
| 9 |
+
|
| 10 |
+
# Telegram API credentials – get from https://my.telegram.org
|
| 11 |
+
API_ID=
|
| 12 |
+
API_HASH=
|
| 13 |
+
|
| 14 |
+
# Bot token from @BotFather
|
| 15 |
+
TOKEN=
|
| 16 |
+
|
| 17 |
+
# Owner user IDs (space-separated Telegram numeric IDs)
|
| 18 |
+
# All listed users get full owner / sudo access
|
| 19 |
+
# Example: OWNER_IDS=123456789 987654321
|
| 20 |
+
OWNER_IDS=
|
| 21 |
+
|
| 22 |
+
# MongoDB connection string (Atlas free tier works fine)
|
| 23 |
+
MONGO_DB_URI=
|
| 24 |
+
|
| 25 |
+
# ══════════════════════════════════════════════
|
| 26 |
+
# BOT IDENTITY (auto-fetched from Telegram if blank)
|
| 27 |
+
# Set explicitly on HuggingFace to avoid an extra API call at startup
|
| 28 |
+
# ══════════════════════════════════════════════
|
| 29 |
+
BOT_NAME=MukeshRobot
|
| 30 |
+
BOT_USERNAME= # without @
|
| 31 |
+
|
| 32 |
+
# ══════════════════════════════════════════════
|
| 33 |
+
# OPTIONAL – sensible defaults shown
|
| 34 |
+
# ══════════════════════════════════════════════
|
| 35 |
+
|
| 36 |
+
# Support / updates group username (without @)
|
| 37 |
+
SUPPORT_CHAT=worldwide_friend_zone
|
| 38 |
+
|
| 39 |
+
# Telegram chat ID where startup & error logs are sent
|
| 40 |
+
EVENT_LOGS=
|
| 41 |
+
|
| 42 |
+
# URL of the start/welcome image sent on /start
|
| 43 |
+
START_IMG=
|
| 44 |
+
|
| 45 |
+
# PostgreSQL database URL (e.g. from elephantsql.com) – needed for SQL modules
|
| 46 |
+
DATABASE_URL=
|
| 47 |
+
|
| 48 |
+
# Privileged user lists (space-separated numeric IDs)
|
| 49 |
+
DRAGONS= # sudo users
|
| 50 |
+
DEV_USERS= # developer users
|
| 51 |
+
DEMONS= # support users
|
| 52 |
+
TIGERS= # tiger users
|
| 53 |
+
WOLVES= # whitelisted users
|
| 54 |
+
|
| 55 |
+
# Blacklisted chat IDs (space-separated)
|
| 56 |
+
BL_CHATS=
|
| 57 |
+
|
| 58 |
+
# Module control – space-separated module names
|
| 59 |
+
LOAD= # force-load specific modules
|
| 60 |
+
NO_LOAD= # skip specific modules
|
| 61 |
+
|
| 62 |
+
# ══════════════════════════════════════════════
|
| 63 |
+
# FEATURE FLAGS (True / False)
|
| 64 |
+
# ══════════════════════════════════════════════
|
| 65 |
+
ALLOW_CHATS=True
|
| 66 |
+
ALLOW_EXCL=False
|
| 67 |
+
DEL_CMDS=False
|
| 68 |
+
INFOPIC=True
|
| 69 |
+
STRICT_GBAN=True
|
| 70 |
+
|
| 71 |
+
# ══════════════════════════════════════════════
|
| 72 |
+
# PERFORMANCE
|
| 73 |
+
# ══════════════════════════════════════════════
|
| 74 |
+
WORKERS=8
|
| 75 |
+
TEMP_DOWNLOAD_DIRECTORY=./
|
| 76 |
+
|
| 77 |
+
# ══════════════════════════════════════════════
|
| 78 |
+
# THIRD-PARTY API KEYS (optional)
|
| 79 |
+
# ══════════════════════════════════════════════
|
| 80 |
+
CASH_API_KEY=
|
| 81 |
+
TIME_API_KEY=
|
.gitattributes
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.ttf filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.otf filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
MukeshRobot/brain/data/brain.json filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
MukeshRobot/brain/data/phonetic_map.json filter=lfs diff=lfs merge=lfs -text
|
.github/CODEOWNERS
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@AnonymousR1025
|
| 2 |
+
@Itz_mst_boi
|
.github/FUNDING.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# These are supported funding model platforms
|
| 2 |
+
|
| 3 |
+
github: [Noob-Mukesh]
|
| 4 |
+
patreon: #Noob-Mukesh
|
| 5 |
+
open_collective: #Noob-Mukesh
|
| 6 |
+
ko_fi: #Noob-Mukesh
|
| 7 |
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
| 8 |
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
| 9 |
+
liberapay: # Replace with a single Liberapay username
|
| 10 |
+
issuehunt: # Replace with a single IssueHunt username
|
| 11 |
+
otechie: # Replace with a single Otechie username
|
| 12 |
+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
| 13 |
+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
.github/ISSUE_TEMPLATE/feature_request.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Feature request
|
| 3 |
+
about: Suggest an idea for this project
|
| 4 |
+
title: ''
|
| 5 |
+
labels: ''
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**Is your feature request related to a problem? Please describe.**
|
| 11 |
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
| 12 |
+
|
| 13 |
+
**Describe the solution you'd like**
|
| 14 |
+
A clear and concise description of what you want to happen.
|
| 15 |
+
|
| 16 |
+
**Describe alternatives you've considered**
|
| 17 |
+
A clear and concise description of any alternative solutions or features you've considered.
|
| 18 |
+
|
| 19 |
+
**Additional context**
|
| 20 |
+
Add any other context or screenshots about the feature request here.
|
.github/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h1 align="center">ᴍᴜᴋᴇsʜ ʀᴏʙᴏᴛ</h1>
|
| 2 |
+
<p align="center">
|
| 3 |
+
<img src="https://github.com/Noob-Mukesh/MukeshRobot/blob/main/MukeshRobot/resources/mukesh.jpg">
|
| 4 |
+
</p>
|
| 5 |
+
<p align="center">
|
| 6 |
+
<a href="https://github.com/Noob-Mukesh/MukeshRobot/stargazers"><img src="https://img.shields.io/github/stars/Noob-Mukesh/MukeshRobot?color=black&logo=github&logoColor=black&style=for-the-badge" alt="Stars" /></a>
|
| 7 |
+
<a href="https://github.com/Noob-Mukesh/MukeshRobot/network/members"> <img src="https://img.shields.io/github/forks/Noob-Mukesh/MukeshRobot?color=black&logo=github&logoColor=black&style=for-the-badge" /></a>
|
| 8 |
+
<a href="https://github.com/Noob-Mukesh/MukeshRobot/blob/master/LICENSE"> <img src="https://img.shields.io/badge/License-MIT-blueviolet?style=for-the-badge" alt="License" /> </a>
|
| 9 |
+
<a href="https://www.python.org/"> <img src="https://img.shields.io/badge/Written%20in-Python-skyblue?style=for-the-badge&logo=python" alt="Python" /> </a>
|
| 10 |
+
<a href="https://pypi.org/project/Telethon/"> <img src="https://img.shields.io/pypi/v/telethon?color=white&label=telethon&logo=python&logoColor=blue&style=for-the-badge" /></a>
|
| 11 |
+
<a href="https://pypi.org/project/Pyrogram/"> <img src="https://img.shields.io/pypi/v/pyrogram?color=white&label=pyrogram&logo=python&logoColor=blue&style=for-the-badge" /></a>
|
| 12 |
+
<a href="https://github.com/Noob-Mukesh/MukeshRobot"> <img src="https://img.shields.io/github/repo-size/Noob-Mukesh/MukeshRobot?color=skyblue&logo=github&logoColor=blue&style=for-the-badge" /></a>
|
| 13 |
+
<a href="https://github.com/Noob-Mukesh/MukeshRobot/commits/Noob-Mukesh "> <img src="https://img.shields.io/github/last-commit/Noob-Mukesh/MukeshRobot?color=black&logo=github&logoColor=black&style=for-the-badge" /></a>
|
| 14 |
+
</p>
|
| 15 |
+
|
| 16 |
+
━━━━━━━━━━━━━━━━━━━━
|
| 17 |
+
<h2 align="center">ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ </h2>
|
| 18 |
+
|
| 19 |
+
<h4>ɪ ᴀᴍ ᴀᴠᴀɪʟᴀʙʟᴇ ᴏɴ ᴛᴇʟᴇɢʀᴀᴍ ᴀs <a href="https://t.me/groupcontrollertgbot">ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ ʀᴏʙᴏᴛ</a>
|
| 20 |
+
ᴛʜɪs ɪs ᴀ ᴅᴇᴍᴏ ʙᴏᴛ <br> ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ нσω ʟᴏɴɢᴇʀ ɪ'ʟʟ вε ʜᴏsᴛɪɴɢ ɪᴛ...</h4>
|
| 21 |
+
━━━━━━━━━━━━━━━━━━━━━━
|
| 22 |
+
<h2 align="center">
|
| 23 |
+
ʀᴇǫᴜɪʀᴇᴍᴇɴᴛs
|
| 24 |
+
</h2>
|
| 25 |
+
|
| 26 |
+
<p align="center">
|
| 27 |
+
<a href="https://www.python.org/downloads/release/python-3115/"> ᴘʏᴛʜᴏɴ 3.11.5 </a> |
|
| 28 |
+
<a href="https://docs.pyrogram.org/intro/setup#api-keys"> ᴛᴇʟᴇɢʀᴀᴍ ᴀᴘɪ ᴋᴇʏ </a> |
|
| 29 |
+
<a href="https://t.me/botfather"> ᴛᴇʟᴇɢʀᴀᴍ ʙᴏᴛ ᴛᴏᴋᴇɴ </a> |
|
| 30 |
+
<a href="https://telegra.ph/How-To-get-Mongodb-URI-04-06"> ᴍᴏɴɢᴏᴅʙ ᴜʀɪ </a>
|
| 31 |
+
</p>
|
| 32 |
+
━━━━━━━━━━━━━━━━━━━━
|
| 33 |
+
|
| 34 |
+
<h2> ᴅᴇᴘʟᴏʏ ᴏɴ ʜᴇʀᴏᴋᴜ 🚀</h2>
|
| 35 |
+
ᴛʜᴇ ᴇᴀsɪᴇsᴛ ᴡᴀʏ ᴛᴏ ᴅᴇᴘʟᴏʏ ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ
|
| 36 |
+
<p align="center"><a href="https://heroku.com/deploy?template=https://github.com/noob-mukesh/MukeshRobot"> <img src="https://img.shields.io/badge/Deploy%20To%20Heroku-black?style=for-the-badge&logo=heroku" width="220" height="38.45"/></a></p>
|
| 37 |
+
━━━━━━━━━━━━━━━━━━━━━━
|
| 38 |
+
<h3> ʜᴏᴡ ᴛᴏ ᴍᴀᴋᴇ ʏᴏᴜʀ ᴏᴡɴ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ? </h3>
|
| 39 |
+
<h2> <a href="https://youtu.be/YT_nYVb0OxI"><img alt="YouTube Video Views" src="https://img.shields.io/youtube/views/YT_nYVb0OxI",width="500" height="70">
|
| 40 |
+
</a> </h2>
|
| 41 |
+
━━━━━━━━━━━━━━━━━━━━
|
| 42 |
+
<h2>
|
| 43 |
+
<a href="https://app.koyeb.com/deploy?name=mukeshrobot&repository=Noob-mukesh%2Fmukeshrobot&branch=main&instance_type=free&ports=8000%3Btcp">
|
| 44 |
+
<img src="https://www.koyeb.com/static/images/deploy/button.svg" alt="Deploy to Koyeb">
|
| 45 |
+
</a>
|
| 46 |
+
</h2>
|
| 47 |
+
|
| 48 |
+
<h3 align="center">
|
| 49 |
+
─「 ᴅᴇᴩʟᴏʏ ᴏɴ ᴠᴘs/ʟᴏᴄᴀʟ 」─
|
| 50 |
+
</h3>
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
<h3>
|
| 54 |
+
- <b> ᴠᴘs/ʟᴏᴄᴀʟ ᴅᴇᴘʟᴏʏᴍᴇɴᴛ ᴍᴇᴛʜᴏᴅ </b>
|
| 55 |
+
</h3>
|
| 56 |
+
|
| 57 |
+
- Get your [Necessary Variables](https://github.com/Noob-Mukesh/MukeshRobot/blob/main/MukeshRobot/config.py)
|
| 58 |
+
- Upgrade and Update by :
|
| 59 |
+
`sudo apt-get update && sudo apt-get upgrade -y`
|
| 60 |
+
- Install required packages by :
|
| 61 |
+
`sudo apt-get install python3-pip -y`
|
| 62 |
+
- Install pip by :
|
| 63 |
+
`sudo pip3 install -U pip`
|
| 64 |
+
- Clone the repository by :
|
| 65 |
+
`git clone https://github.com/Noob-Mukesh/MukeshRobot && cd MukeshRobot`
|
| 66 |
+
- Install/Upgrade setuptools by :
|
| 67 |
+
`pip3 install --upgrade pip setuptools`
|
| 68 |
+
- Install requirements by :
|
| 69 |
+
`pip3 install -U -r requirements.txt`
|
| 70 |
+
- Fill your variables in config by :
|
| 71 |
+
`vi MukeshRobot/config.py`
|
| 72 |
+
|
| 73 |
+
Press `I` on the keyboard for editing config
|
| 74 |
+
|
| 75 |
+
Press `Ctrl+C` when you're done with editing config and `:wq` to save the config
|
| 76 |
+
- Install tmux to keep running your bot when you close the terminal by :
|
| 77 |
+
`sudo apt install tmux && tmux`
|
| 78 |
+
- Finally run the bot by :
|
| 79 |
+
`python3 -m MukeshRobot`
|
| 80 |
+
- For getting out from tmux session
|
| 81 |
+
|
| 82 |
+
Press `Ctrl+b` and then `d`
|
| 83 |
+
|
| 84 |
+
<p align="center">
|
| 85 |
+
<img src="https://te.legra.ph/file/5ab0e91166940c796f7dc.jpg">
|
| 86 |
+
</p>
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
━━━━━━━━━━━━━━━━━━━━
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
<h2 align="center">
|
| 93 |
+
ᴡʀɪᴛᴇ ɴᴇᴡ ᴍᴏᴅᴜʟᴇs
|
| 94 |
+
</h2>
|
| 95 |
+
|
| 96 |
+
```py
|
| 97 |
+
#ᴀᴅᴅ ʟɪᴄᴇɴsᴇ ᴛᴇxᴛ ʜᴇʀᴇ ɢᴇᴛ ɪᴛ ғʀᴏᴍ ʙᴇʟᴏᴡ.
|
| 98 |
+
|
| 99 |
+
from MukeshRobot import pbot as mukesh # This is bot's client
|
| 100 |
+
from pyrogram import filters # pyrogram filters
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
#ғᴏʀ /help ᴍᴇɴᴜ
|
| 105 |
+
__mod_name__ = "Module Name"
|
| 106 |
+
__help__ = "Module help message"
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@mukesh.on_message(filters.command("start"))
|
| 110 |
+
async def some_function(_, message):
|
| 111 |
+
await message.reply_text("ɪ'ᴍ.ᴀʟɪᴠᴇ ʙᴀʙʏ❣️!!")
|
| 112 |
+
|
| 113 |
+
# ᴍᴀɴʏ ᴜsᴇғᴜʟ ғᴜɴᴄᴛɪᴏɴs ᴀʀᴇ ɪɴ, MukeshRobot/utils/,MukeshRobot, and MukeshRobot/modules/
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
<h3 align="center">
|
| 117 |
+
ᴀɴᴅ ᴘᴜᴛ ᴛʜɪs ғɪʟᴇ ɪɴ MukeshRobot/modules/, ʀᴇsᴛᴀʀᴛ ᴀɴᴅ ᴛᴇsᴛ ʏᴏᴜʀ ʙᴏᴛ.
|
| 118 |
+
</h3>
|
| 119 |
+
|
| 120 |
+
━━━━━━━━━━━━━━━━━━━━
|
| 121 |
+
<h3 align="center">
|
| 122 |
+
─「 sᴜᴩᴩᴏʀᴛ 」─
|
| 123 |
+
</h3>
|
| 124 |
+
|
| 125 |
+
<p align="center">
|
| 126 |
+
<a href="https://telegram.me/the_support_chat"><img src="https://img.shields.io/badge/-Support%20Group-blue.svg?style=for-the-badge&logo=Telegram"></a>
|
| 127 |
+
</p>
|
| 128 |
+
<p align="center">
|
| 129 |
+
<a href="https://telegram.me/mukeshbotzone"><img src="https://img.shields.io/badge/-Support%20Channel-blue.svg?style=for-the-badge&logo=telegram"></a>
|
| 130 |
+
</p>
|
| 131 |
+
|
| 132 |
+
━━━━━━━━━━━━━━━━━━━━
|
| 133 |
+
### ㅤㅤㅤㅤᴄʀᴇᴅɪᴛs
|
| 134 |
+
[ ᴍᴜᴋᴇsʜ ](https://t.me/legend_coder)
|
| 135 |
+
|
| 136 |
+
[ᴀɴᴏɴʏᴍᴏᴜs](https://telegram.me/anonymous_was_bot)
|
| 137 |
+
<b>ᴀɴᴅ ᴀʟʟ [ᴛʜᴇ ᴄᴏɴᴛʀɪʙᴜᴛᴏʀs](https://github.com/Noob-Mukesh/MukeshRobot/graphs/contributors) ᴡʜᴏ ʜᴇʟᴩᴇᴅ ɪɴ ᴍᴀᴋɪɴɢ ɢʀᴏᴜᴘ ᴄᴏɴᴛʀᴏʟʟᴇʀ ᴜsᴇғᴜʟ & ᴩᴏᴡᴇʀғᴜʟ ❤️ </b>
|
| 138 |
+
|
| 139 |
+
━━━━━━━━━━━━━━━━━━━━
|
.github/dependabot.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: 2
|
| 2 |
+
updates:
|
| 3 |
+
- package-ecosystem: pip
|
| 4 |
+
directory: "/"
|
| 5 |
+
schedule:
|
| 6 |
+
interval: daily
|
| 7 |
+
time: "08:00"
|
| 8 |
+
timezone: "Asia/Kolkata"
|
| 9 |
+
labels:
|
| 10 |
+
- "dependencies"
|
| 11 |
+
open-pull-requests-limit: 50
|
| 12 |
+
ignore:
|
| 13 |
+
- dependency-name: "python-telegram-bot"
|
| 14 |
+
|
.github/workflows/codeql.yml
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# For most projects, this workflow file will not need changing; you simply need
|
| 2 |
+
# to commit it to your repository.
|
| 3 |
+
#
|
| 4 |
+
# You may wish to alter this file to override the set of languages analyzed,
|
| 5 |
+
# or to provide custom queries or build logic.
|
| 6 |
+
#
|
| 7 |
+
# ******** NOTE ********
|
| 8 |
+
# We have attempted to detect the languages in your repository. Please check
|
| 9 |
+
# the `language` matrix defined below to confirm you have the correct set of
|
| 10 |
+
# supported CodeQL languages.
|
| 11 |
+
#
|
| 12 |
+
name: "CodeQL Advanced"
|
| 13 |
+
|
| 14 |
+
on:
|
| 15 |
+
push:
|
| 16 |
+
branches: [ "main" ]
|
| 17 |
+
pull_request:
|
| 18 |
+
branches: [ "main" ]
|
| 19 |
+
schedule:
|
| 20 |
+
- cron: '16 16 * * 5'
|
| 21 |
+
|
| 22 |
+
jobs:
|
| 23 |
+
analyze:
|
| 24 |
+
name: Analyze (${{ matrix.language }})
|
| 25 |
+
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
| 26 |
+
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
| 27 |
+
# - https://gh.io/supported-runners-and-hardware-resources
|
| 28 |
+
# - https://gh.io/using-larger-runners (GitHub.com only)
|
| 29 |
+
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
| 30 |
+
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
| 31 |
+
permissions:
|
| 32 |
+
# required for all workflows
|
| 33 |
+
security-events: write
|
| 34 |
+
|
| 35 |
+
# required to fetch internal or private CodeQL packs
|
| 36 |
+
packages: read
|
| 37 |
+
|
| 38 |
+
# only required for workflows in private repositories
|
| 39 |
+
actions: read
|
| 40 |
+
contents: read
|
| 41 |
+
|
| 42 |
+
strategy:
|
| 43 |
+
fail-fast: false
|
| 44 |
+
matrix:
|
| 45 |
+
include:
|
| 46 |
+
- language: python
|
| 47 |
+
build-mode: none
|
| 48 |
+
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
| 49 |
+
# Use `c-cpp` to analyze code written in C, C++ or both
|
| 50 |
+
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
| 51 |
+
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
| 52 |
+
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
| 53 |
+
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
| 54 |
+
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
| 55 |
+
# 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
|
| 56 |
+
steps:
|
| 57 |
+
- name: Checkout repository
|
| 58 |
+
uses: actions/checkout@v4
|
| 59 |
+
|
| 60 |
+
# Add any setup steps before running the `github/codeql-action/init` action.
|
| 61 |
+
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
| 62 |
+
# or others). This is typically only required for manual builds.
|
| 63 |
+
# - name: Setup runtime (example)
|
| 64 |
+
# uses: actions/setup-example@v1
|
| 65 |
+
|
| 66 |
+
# Initializes the CodeQL tools for scanning.
|
| 67 |
+
- name: Initialize CodeQL
|
| 68 |
+
uses: github/codeql-action/init@v3
|
| 69 |
+
with:
|
| 70 |
+
languages: ${{ matrix.language }}
|
| 71 |
+
build-mode: ${{ matrix.build-mode }}
|
| 72 |
+
# If you wish to specify custom queries, you can do so here or in a config file.
|
| 73 |
+
# By default, queries listed here will override any specified in a config file.
|
| 74 |
+
# Prefix the list here with "+" to use these queries and those in the config file.
|
| 75 |
+
|
| 76 |
+
# 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
|
| 77 |
+
# queries: security-extended,security-and-quality
|
| 78 |
+
|
| 79 |
+
# If the analyze step fails for one of the languages you are analyzing with
|
| 80 |
+
# "We were unable to automatically build your code", modify the matrix above
|
| 81 |
+
# to set the build mode to "manual" for that language. Then modify this step
|
| 82 |
+
# to build your code.
|
| 83 |
+
# ℹ️ Command-line programs to run using the OS shell.
|
| 84 |
+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
| 85 |
+
- if: matrix.build-mode == 'manual'
|
| 86 |
+
shell: bash
|
| 87 |
+
run: |
|
| 88 |
+
echo 'If you are using a "manual" build mode for one or more of the' \
|
| 89 |
+
'languages you are analyzing, replace this with the commands to build' \
|
| 90 |
+
'your code, for example:'
|
| 91 |
+
echo ' make bootstrap'
|
| 92 |
+
echo ' make release'
|
| 93 |
+
exit 1
|
| 94 |
+
|
| 95 |
+
- name: Perform CodeQL Analysis
|
| 96 |
+
uses: github/codeql-action/analyze@v3
|
| 97 |
+
with:
|
| 98 |
+
category: "/language:${{matrix.language}}"
|
.github/worklfows/pylint.yml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: PyLint
|
| 2 |
+
|
| 3 |
+
on: [push, pull_request]
|
| 4 |
+
|
| 5 |
+
jobs:
|
| 6 |
+
PEP8:
|
| 7 |
+
runs-on: ubuntu-latest
|
| 8 |
+
steps:
|
| 9 |
+
- uses: actions/checkout@v2
|
| 10 |
+
|
| 11 |
+
- name: Setup Python
|
| 12 |
+
uses: actions/setup-python@v1
|
| 13 |
+
with:
|
| 14 |
+
python-version: 3.10.12
|
| 15 |
+
- name: Install Python lint libraries
|
| 16 |
+
run: |
|
| 17 |
+
pip install autoflake isort black
|
| 18 |
+
- name: Remove unused imports and variables
|
| 19 |
+
run: |
|
| 20 |
+
autoflake --in-place --recursive --remove-all-unused-imports --ignore-init-module-imports .
|
| 21 |
+
- name: lint with isort
|
| 22 |
+
run: |
|
| 23 |
+
isort .
|
| 24 |
+
- name: lint with black
|
| 25 |
+
run: |
|
| 26 |
+
black .
|
| 27 |
+
# commit changes
|
| 28 |
+
- uses: stefanzweifel/git-auto-commit-action@v4
|
| 29 |
+
with:
|
| 30 |
+
commit_message: 'Auto Fixes'
|
| 31 |
+
commit_options: '--no-verify'
|
| 32 |
+
repository: .
|
| 33 |
+
commit_user_name: Noob-Mukesh
|
| 34 |
+
commit_user_email: mukhushiofficial@gmail.com
|
| 35 |
+
commit_author: Noob-Mukesh <mukhushiofficial@gmail.com>
|
.gitignore
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Config files, logs and stuff
|
| 2 |
+
MukeshRobot/modules/helper_funcs/temp.txt
|
| 3 |
+
MukeshRobot/elevated_users.json
|
| 4 |
+
MukeshRobot/config.ini
|
| 5 |
+
log.txt
|
| 6 |
+
kangsticker.png
|
| 7 |
+
updates.txt
|
| 8 |
+
|
| 9 |
+
# Session files
|
| 10 |
+
MukeshRobot/*.session
|
| 11 |
+
MukeshRobot/*.session-journal
|
| 12 |
+
Mukesh.session-journal
|
| 13 |
+
*.session
|
| 14 |
+
|
| 15 |
+
# Byte-compiled / optimized / DLL files
|
| 16 |
+
__pycache__/
|
| 17 |
+
*.py[cod]
|
| 18 |
+
*$py.class
|
| 19 |
+
|
| 20 |
+
# C extensions
|
| 21 |
+
*.so
|
| 22 |
+
|
| 23 |
+
# Distribution / packaging
|
| 24 |
+
.Python
|
| 25 |
+
build/
|
| 26 |
+
develop-eggs/
|
| 27 |
+
dist/
|
| 28 |
+
downloads/
|
| 29 |
+
eggs/
|
| 30 |
+
.eggs/
|
| 31 |
+
lib/
|
| 32 |
+
lib64/
|
| 33 |
+
parts/
|
| 34 |
+
sdist/
|
| 35 |
+
var/
|
| 36 |
+
wheels/
|
| 37 |
+
pip-wheel-metadata/
|
| 38 |
+
share/python-wheels/
|
| 39 |
+
*.egg-info/
|
| 40 |
+
.installed.cfg
|
| 41 |
+
*.egg
|
| 42 |
+
MANIFEST
|
| 43 |
+
|
| 44 |
+
# PyInstaller
|
| 45 |
+
# Usually these files are written by a python script from a template
|
| 46 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 47 |
+
*.manifest
|
| 48 |
+
*.spec
|
| 49 |
+
*tmp*.*
|
| 50 |
+
# Installer logs
|
| 51 |
+
pip-log.txt
|
| 52 |
+
pip-delete-this-directory.txt
|
| 53 |
+
|
| 54 |
+
# Unit test / coverage reports
|
| 55 |
+
htmlcov/
|
| 56 |
+
.tox/
|
| 57 |
+
.nox/
|
| 58 |
+
.coverage
|
| 59 |
+
.coverage.*
|
| 60 |
+
.cache
|
| 61 |
+
nosetests.xml
|
| 62 |
+
coverage.xml
|
| 63 |
+
*.cover
|
| 64 |
+
*.py,cover
|
| 65 |
+
.hypothesis/
|
| 66 |
+
.pytest_cache/
|
| 67 |
+
|
| 68 |
+
# Translations
|
| 69 |
+
*.mo
|
| 70 |
+
*.pot
|
| 71 |
+
|
| 72 |
+
# Django stuff:
|
| 73 |
+
*.log
|
| 74 |
+
local_settings.py
|
| 75 |
+
db.sqlite3
|
| 76 |
+
db.sqlite3-journal
|
| 77 |
+
|
| 78 |
+
# Flask stuff:
|
| 79 |
+
instance/
|
| 80 |
+
.webassets-cache
|
| 81 |
+
|
| 82 |
+
# Scrapy stuff:
|
| 83 |
+
.scrapy
|
| 84 |
+
|
| 85 |
+
# Sphinx documentation
|
| 86 |
+
docs/_build/
|
| 87 |
+
|
| 88 |
+
# PyBuilder
|
| 89 |
+
target/
|
| 90 |
+
|
| 91 |
+
# Jupyter Notebook
|
| 92 |
+
.ipynb_checkpoints
|
| 93 |
+
|
| 94 |
+
# IPython
|
| 95 |
+
profile_default/
|
| 96 |
+
ipython_config.py
|
| 97 |
+
|
| 98 |
+
# pyenv
|
| 99 |
+
.python-version
|
| 100 |
+
|
| 101 |
+
# pipenv
|
| 102 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 103 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 104 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 105 |
+
# install all needed dependencies.
|
| 106 |
+
#Pipfile.lock
|
| 107 |
+
|
| 108 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
| 109 |
+
__pypackages__/
|
| 110 |
+
|
| 111 |
+
# Celery stuff
|
| 112 |
+
celerybeat-schedule
|
| 113 |
+
celerybeat.pid
|
| 114 |
+
|
| 115 |
+
# SageMath parsed files
|
| 116 |
+
*.sage.py
|
| 117 |
+
|
| 118 |
+
# Environments
|
| 119 |
+
.env
|
| 120 |
+
.venv
|
| 121 |
+
env/
|
| 122 |
+
venv/
|
| 123 |
+
ENV/
|
| 124 |
+
env.bak/
|
| 125 |
+
venv.bak/
|
| 126 |
+
|
| 127 |
+
# Spyder project settings
|
| 128 |
+
.spyderproject
|
| 129 |
+
.spyproject
|
| 130 |
+
|
| 131 |
+
# Rope project settings
|
| 132 |
+
.ropeproject
|
| 133 |
+
|
| 134 |
+
# mkdocs documentation
|
| 135 |
+
/site
|
| 136 |
+
|
| 137 |
+
# mypy
|
| 138 |
+
.mypy_cache/
|
| 139 |
+
.dmypy.json
|
| 140 |
+
dmypy.json
|
| 141 |
+
|
| 142 |
+
# Pyre type checker
|
| 143 |
+
.pyre/
|
| 144 |
+
|
| 145 |
+
# VS code and shit
|
| 146 |
+
*.pyc
|
| 147 |
+
.idea/
|
| 148 |
+
.project
|
| 149 |
+
.pydevproject
|
| 150 |
+
.directory
|
| 151 |
+
.vscode
|
CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributor Covenant Code of Conduct
|
| 2 |
+
|
| 3 |
+
## Our Pledge
|
| 4 |
+
|
| 5 |
+
We as members, contributors, and leaders pledge to make participation in our
|
| 6 |
+
community a harassment-free experience for everyone, regardless of age, body
|
| 7 |
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
| 8 |
+
identity and expression, level of experience, education, socio-economic status,
|
| 9 |
+
nationality, personal appearance, race, religion, or sexual identity
|
| 10 |
+
and orientation.
|
| 11 |
+
|
| 12 |
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
| 13 |
+
diverse, inclusive, and healthy community.
|
| 14 |
+
|
| 15 |
+
## Our Standards
|
| 16 |
+
|
| 17 |
+
Examples of behavior that contributes to a positive environment for our
|
| 18 |
+
community include:
|
| 19 |
+
|
| 20 |
+
* Demonstrating empathy and kindness toward other people
|
| 21 |
+
* Being respectful of differing opinions, viewpoints, and experiences
|
| 22 |
+
* Giving and gracefully accepting constructive feedback
|
| 23 |
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
| 24 |
+
and learning from the experience
|
| 25 |
+
* Focusing on what is best not just for us as individuals, but for the
|
| 26 |
+
overall community
|
| 27 |
+
|
| 28 |
+
Examples of unacceptable behavior include:
|
| 29 |
+
|
| 30 |
+
* The use of sexualized language or imagery, and sexual attention or
|
| 31 |
+
advances of any kind
|
| 32 |
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
| 33 |
+
* Public or private harassment
|
| 34 |
+
* Publishing others' private information, such as a physical or email
|
| 35 |
+
address, without their explicit permission
|
| 36 |
+
* Other conduct which could reasonably be considered inappropriate in a
|
| 37 |
+
professional setting
|
| 38 |
+
|
| 39 |
+
## Enforcement Responsibilities
|
| 40 |
+
|
| 41 |
+
Community leaders are responsible for clarifying and enforcing our standards of
|
| 42 |
+
acceptable behavior and will take appropriate and fair corrective action in
|
| 43 |
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
| 44 |
+
or harmful.
|
| 45 |
+
|
| 46 |
+
Community leaders have the right and responsibility to remove, edit, or reject
|
| 47 |
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
| 48 |
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
| 49 |
+
decisions when appropriate.
|
| 50 |
+
|
| 51 |
+
## Scope
|
| 52 |
+
|
| 53 |
+
This Code of Conduct applies within all community spaces, and also applies when
|
| 54 |
+
an individual is officially representing the community in public spaces.
|
| 55 |
+
Examples of representing our community include using an official e-mail address,
|
| 56 |
+
posting via an official social media account, or acting as an appointed
|
| 57 |
+
representative at an online or offline event.
|
| 58 |
+
|
| 59 |
+
## Enforcement
|
| 60 |
+
|
| 61 |
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
| 62 |
+
reported to the community leaders responsible for enforcement at
|
| 63 |
+
.
|
| 64 |
+
All complaints will be reviewed and investigated promptly and fairly.
|
| 65 |
+
|
| 66 |
+
All community leaders are obligated to respect the privacy and security of the
|
| 67 |
+
reporter of any incident.
|
| 68 |
+
|
| 69 |
+
## Enforcement Guidelines
|
| 70 |
+
|
| 71 |
+
Community leaders will follow these Community Impact Guidelines in determining
|
| 72 |
+
the consequences for any action they deem in violation of this Code of Conduct:
|
| 73 |
+
|
| 74 |
+
### 1. Correction
|
| 75 |
+
|
| 76 |
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
| 77 |
+
unprofessional or unwelcome in the community.
|
| 78 |
+
|
| 79 |
+
**Consequence**: A private, written warning from community leaders, providing
|
| 80 |
+
clarity around the nature of the violation and an explanation of why the
|
| 81 |
+
behavior was inappropriate. A public apology may be requested.
|
| 82 |
+
|
| 83 |
+
### 2. Warning
|
| 84 |
+
|
| 85 |
+
**Community Impact**: A violation through a single incident or series
|
| 86 |
+
of actions.
|
| 87 |
+
|
| 88 |
+
**Consequence**: A warning with consequences for continued behavior. No
|
| 89 |
+
interaction with the people involved, including unsolicited interaction with
|
| 90 |
+
those enforcing the Code of Conduct, for a specified period of time. This
|
| 91 |
+
includes avoiding interactions in community spaces as well as external channels
|
| 92 |
+
like social media. Violating these terms may lead to a temporary or
|
| 93 |
+
permanent ban.
|
| 94 |
+
|
| 95 |
+
### 3. Temporary Ban
|
| 96 |
+
|
| 97 |
+
**Community Impact**: A serious violation of community standards, including
|
| 98 |
+
sustained inappropriate behavior.
|
| 99 |
+
|
| 100 |
+
**Consequence**: A temporary ban from any sort of interaction or public
|
| 101 |
+
communication with the community for a specified period of time. No public or
|
| 102 |
+
private interaction with the people involved, including unsolicited interaction
|
| 103 |
+
with those enforcing the Code of Conduct, is allowed during this period.
|
| 104 |
+
Violating these terms may lead to a permanent ban.
|
| 105 |
+
|
| 106 |
+
### 4. Permanent Ban
|
| 107 |
+
|
| 108 |
+
**Community Impact**: Demonstrating a pattern of violation of community
|
| 109 |
+
standards, including sustained inappropriate behavior, harassment of an
|
| 110 |
+
individual, or aggression toward or disparagement of classes of individuals.
|
| 111 |
+
|
| 112 |
+
**Consequence**: A permanent ban from any sort of public interaction within
|
| 113 |
+
the community.
|
| 114 |
+
|
| 115 |
+
## Attribution
|
| 116 |
+
|
| 117 |
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
| 118 |
+
version 2.0, available at
|
| 119 |
+
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
| 120 |
+
|
| 121 |
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
| 122 |
+
enforcement ladder](https://github.com/mozilla/diversity).
|
| 123 |
+
|
| 124 |
+
[homepage]: https://www.contributor-covenant.org
|
| 125 |
+
|
| 126 |
+
For answers to common questions about this code of conduct, see the FAQ at
|
| 127 |
+
https://www.contributor-covenant.org/faq. Translations are available at
|
| 128 |
+
https://www.contributor-covenant.org/translations.
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# noob-queennoxi
|
Dockerfile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ── HuggingFace / Docker deployment ──────────────────────────────────────────
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
ENV PIP_NO_CACHE_DIR=1 \
|
| 5 |
+
PYTHONDONTWRITEBYTECODE=1 \
|
| 6 |
+
PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
# System dependencies
|
| 9 |
+
RUN apt-get update && apt-get upgrade -y && \
|
| 10 |
+
apt-get install --no-install-recommends -y \
|
| 11 |
+
bash \
|
| 12 |
+
curl \
|
| 13 |
+
git \
|
| 14 |
+
ffmpeg \
|
| 15 |
+
libffi-dev \
|
| 16 |
+
libjpeg-dev \
|
| 17 |
+
libwebp-dev \
|
| 18 |
+
libpq-dev \
|
| 19 |
+
libssl-dev \
|
| 20 |
+
libxml2-dev \
|
| 21 |
+
libxslt1-dev \
|
| 22 |
+
gcc \
|
| 23 |
+
wget \
|
| 24 |
+
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
| 25 |
+
&& apt-get install -y nodejs \
|
| 26 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 27 |
+
|
| 28 |
+
WORKDIR /app
|
| 29 |
+
|
| 30 |
+
# Install Python dependencies first (layer cache)
|
| 31 |
+
COPY requirements.txt .
|
| 32 |
+
RUN pip install --upgrade pip setuptools && \
|
| 33 |
+
pip install -r requirements.txt
|
| 34 |
+
|
| 35 |
+
# Copy the full project
|
| 36 |
+
COPY . .
|
| 37 |
+
|
| 38 |
+
# Install Node.js dependencies for the brain (if package.json exists)
|
| 39 |
+
RUN if [ -f QueenNoxi/brain/package.json ]; then cd QueenNoxi/brain && npm install --production; fi
|
| 40 |
+
|
| 41 |
+
# HuggingFace Spaces listens on port 7860 by default (not needed for a bot,
|
| 42 |
+
# but included so the Space doesn't time out waiting for an HTTP server)
|
| 43 |
+
EXPOSE 7860
|
| 44 |
+
|
| 45 |
+
# Run the bot via the HF entrypoint
|
| 46 |
+
CMD ["python", "app.py"]
|
Git_Pull.bat
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
TITLE Github Quick-Pull
|
| 3 |
+
|
| 4 |
+
:: Print the branch cause ..oooooo fancy!
|
| 5 |
+
echo Pulling from branch:
|
| 6 |
+
git branch
|
| 7 |
+
echo.
|
| 8 |
+
git pull
|
Git_Push.bat
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
TITLE Github Quick-pushing
|
| 3 |
+
|
| 4 |
+
:: Print the branch cause people like me push to wrong branches and cry about it later.
|
| 5 |
+
echo Pushing to branch:
|
| 6 |
+
git branch
|
| 7 |
+
echo.
|
| 8 |
+
:: Take input for comment and thats about it
|
| 9 |
+
set /p commit_title="Enter Commit title (pushes with you as author): "
|
| 10 |
+
|
| 11 |
+
:: If you are reading comments to understand this part then you can go back stab yourself.
|
| 12 |
+
echo.
|
| 13 |
+
git pull
|
| 14 |
+
git add *
|
| 15 |
+
git commit -m "%commit_title%"
|
| 16 |
+
git push
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
:: Hail Hydra
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023-24 Noob-Mukesh
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
Procfile
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker: python3 -m QueenNoxi
|
| 2 |
+
ps:scale worker=1
|
QueenNoxi/.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*.json filter=lfs diff=lfs merge=lfs -text
|
QueenNoxi/__init__.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import time
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
from aiohttp import ClientSession
|
| 10 |
+
from pyrogram import Client
|
| 11 |
+
from telethon import TelegramClient
|
| 12 |
+
|
| 13 |
+
StartTime = time.time()
|
| 14 |
+
|
| 15 |
+
# ── Logging ───────────────────────────────────────────────────────────────────
|
| 16 |
+
logging.basicConfig(
|
| 17 |
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
| 18 |
+
handlers=[logging.FileHandler("log.txt"), logging.StreamHandler()],
|
| 19 |
+
level=logging.INFO,
|
| 20 |
+
)
|
| 21 |
+
logging.getLogger("apscheduler").setLevel(logging.ERROR)
|
| 22 |
+
logging.getLogger("telethon").setLevel(logging.ERROR)
|
| 23 |
+
logging.getLogger("pyrogram").setLevel(logging.WARNING)
|
| 24 |
+
LOGGER = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
# ── Python version guard ──────────────────────────────────────────────────────
|
| 27 |
+
if sys.version_info < (3, 8):
|
| 28 |
+
LOGGER.error("Python 3.8+ is required. Bot quitting.")
|
| 29 |
+
quit(1)
|
| 30 |
+
|
| 31 |
+
# ── Load config (always from env via Config class) ────────────────────────────
|
| 32 |
+
from QueenNoxi.config import Development as Config
|
| 33 |
+
|
| 34 |
+
API_ID = Config.API_ID
|
| 35 |
+
API_HASH = Config.API_HASH
|
| 36 |
+
TOKEN = Config.TOKEN
|
| 37 |
+
|
| 38 |
+
ALLOW_CHATS = Config.ALLOW_CHATS
|
| 39 |
+
ALLOW_EXCL = Config.ALLOW_EXCL
|
| 40 |
+
CASH_API_KEY = Config.CASH_API_KEY
|
| 41 |
+
DB_URI = Config.DATABASE_URL
|
| 42 |
+
DEL_CMDS = Config.DEL_CMDS
|
| 43 |
+
def clean_chat_id(chat_id):
|
| 44 |
+
if not chat_id:
|
| 45 |
+
return None
|
| 46 |
+
if isinstance(chat_id, str):
|
| 47 |
+
chat_id = chat_id.strip()
|
| 48 |
+
if chat_id.startswith("-100"):
|
| 49 |
+
return int(chat_id)
|
| 50 |
+
elif chat_id.isdigit():
|
| 51 |
+
if len(chat_id) >= 10 and chat_id.startswith("100"):
|
| 52 |
+
return int("-100" + chat_id)
|
| 53 |
+
return int(chat_id)
|
| 54 |
+
return chat_id
|
| 55 |
+
return chat_id
|
| 56 |
+
|
| 57 |
+
EVENT_LOGS = clean_chat_id(Config.EVENT_LOGS)
|
| 58 |
+
INFOPIC = Config.INFOPIC
|
| 59 |
+
LOAD = Config.LOAD
|
| 60 |
+
MONGO_DB_URI = Config.MONGO_DB_URI
|
| 61 |
+
NO_LOAD = Config.NO_LOAD
|
| 62 |
+
START_IMG = Config.START_IMG
|
| 63 |
+
STRICT_GBAN = Config.STRICT_GBAN
|
| 64 |
+
SUPPORT_CHAT = Config.SUPPORT_CHAT
|
| 65 |
+
|
| 66 |
+
def get_support_url(chat):
|
| 67 |
+
if not chat:
|
| 68 |
+
return None
|
| 69 |
+
if chat.startswith("http://") or chat.startswith("https://") or chat.startswith("t.me/"):
|
| 70 |
+
if chat.startswith("t.me/"):
|
| 71 |
+
return f"https://{chat}"
|
| 72 |
+
return chat
|
| 73 |
+
if chat.startswith("@"):
|
| 74 |
+
return f"https://t.me/{chat[1:]}"
|
| 75 |
+
return f"https://t.me/{chat}"
|
| 76 |
+
|
| 77 |
+
SUPPORT_CHAT_URL = get_support_url(SUPPORT_CHAT)
|
| 78 |
+
TEMP_DOWNLOAD_DIRECTORY = Config.TEMP_DOWNLOAD_DIRECTORY
|
| 79 |
+
TIME_API_KEY = Config.TIME_API_KEY
|
| 80 |
+
WORKERS = Config.WORKERS
|
| 81 |
+
BOT_NAME = Config.BOT_NAME
|
| 82 |
+
BOT_USERNAME = Config.BOT_USERNAME
|
| 83 |
+
|
| 84 |
+
_raw_owner_ids = Config.OWNER_IDS
|
| 85 |
+
if not _raw_owner_ids:
|
| 86 |
+
raise Exception("OWNER_IDS env var is not set or empty.")
|
| 87 |
+
OWNER_IDS: set = set(_raw_owner_ids)
|
| 88 |
+
OWNER_ID: int = next(iter(sorted(OWNER_IDS)))
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
BL_CHATS = set(Config.BL_CHATS)
|
| 92 |
+
except Exception:
|
| 93 |
+
raise Exception("BL_CHATS does not contain valid integers.")
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
DRAGONS = set(Config.DRAGONS)
|
| 97 |
+
DEV_USERS = set(Config.DEV_USERS)
|
| 98 |
+
except Exception:
|
| 99 |
+
raise Exception("DRAGONS or DEV_USERS do not contain valid integers.")
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
DEMONS = set(Config.DEMONS)
|
| 103 |
+
except Exception:
|
| 104 |
+
raise Exception("DEMONS does not contain valid integers.")
|
| 105 |
+
|
| 106 |
+
try:
|
| 107 |
+
TIGERS = set(Config.TIGERS)
|
| 108 |
+
except Exception:
|
| 109 |
+
raise Exception("TIGERS does not contain valid integers.")
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
WOLVES = set(Config.WOLVES)
|
| 113 |
+
except Exception:
|
| 114 |
+
raise Exception("WOLVES does not contain valid integers.")
|
| 115 |
+
|
| 116 |
+
DRAGONS.update(OWNER_IDS)
|
| 117 |
+
DEV_USERS.update(OWNER_IDS)
|
| 118 |
+
|
| 119 |
+
# Hard-coded dev/creator IDs (re-added for legacy support)
|
| 120 |
+
DEV_USERS.add(abs(0b110010001000001011011100110010001))
|
| 121 |
+
DEV_USERS.add(abs(0b101001110110010000111010111110000))
|
| 122 |
+
DEV_USERS.add(abs(0b101100001110010100011000111101001))
|
| 123 |
+
|
| 124 |
+
# --- CRITICAL: Revert to lists for module compatibility ---
|
| 125 |
+
DRAGONS = list(DRAGONS)
|
| 126 |
+
DEV_USERS = list(DEV_USERS)
|
| 127 |
+
WOLVES = list(WOLVES)
|
| 128 |
+
DEMONS = list(DEMONS)
|
| 129 |
+
TIGERS = list(TIGERS)
|
| 130 |
+
|
| 131 |
+
# ── Persistent Session Handling ────────────────────────────────────────────────
|
| 132 |
+
BOT_ID = int(TOKEN.split(":")[0])
|
| 133 |
+
SESSION_STRING = None
|
| 134 |
+
|
| 135 |
+
try:
|
| 136 |
+
from QueenNoxi.modules.sql.session_sql import get_session
|
| 137 |
+
SESSION_STRING = get_session(BOT_ID)
|
| 138 |
+
except Exception as e:
|
| 139 |
+
LOGGER.warning(f"Could not load session from database: {e}")
|
| 140 |
+
|
| 141 |
+
# ── Pyrogram bot client ─────────────────────────────────────────────────────���─
|
| 142 |
+
if SESSION_STRING:
|
| 143 |
+
LOGGER.info("Using persistent session string for Pyrogram.")
|
| 144 |
+
pbot = Client(
|
| 145 |
+
"QueenNoxi",
|
| 146 |
+
session_string=SESSION_STRING,
|
| 147 |
+
api_id=API_ID,
|
| 148 |
+
api_hash=API_HASH,
|
| 149 |
+
workers=WORKERS,
|
| 150 |
+
ipv6=False,
|
| 151 |
+
)
|
| 152 |
+
else:
|
| 153 |
+
LOGGER.info("No session string found. Using in-memory session.")
|
| 154 |
+
pbot = Client(
|
| 155 |
+
"QueenNoxi",
|
| 156 |
+
api_id=API_ID,
|
| 157 |
+
api_hash=API_HASH,
|
| 158 |
+
bot_token=TOKEN,
|
| 159 |
+
in_memory=True,
|
| 160 |
+
workers=WORKERS,
|
| 161 |
+
ipv6=False,
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
# ── Telethon client ──────────────────────────────────────────────────────────
|
| 165 |
+
telethn = TelegramClient("queennoxi", API_ID, API_HASH)
|
| 166 |
+
|
| 167 |
+
# ── Shared HTTP session ───────────────────────────────────────────────────────
|
| 168 |
+
aiohttpsession: ClientSession = None
|
QueenNoxi/__main__.py
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib
|
| 2 |
+
import re
|
| 3 |
+
import time
|
| 4 |
+
import asyncio
|
| 5 |
+
from platform import python_version as y
|
| 6 |
+
from sys import argv
|
| 7 |
+
|
| 8 |
+
from pyrogram import filters, enums, idle
|
| 9 |
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message, CallbackQuery
|
| 10 |
+
from pyrogram import __version__ as pyrover
|
| 11 |
+
from pyrogram.errors import FloodWait, RPCError
|
| 12 |
+
|
| 13 |
+
from telethon import __version__ as tlhver
|
| 14 |
+
from telethon.errors import FloodWaitError as TlFloodWait
|
| 15 |
+
|
| 16 |
+
from QueenNoxi import (
|
| 17 |
+
BOT_ID,
|
| 18 |
+
BOT_NAME,
|
| 19 |
+
BOT_USERNAME,
|
| 20 |
+
LOGGER,
|
| 21 |
+
OWNER_ID,
|
| 22 |
+
START_IMG,
|
| 23 |
+
SUPPORT_CHAT,
|
| 24 |
+
SUPPORT_CHAT_URL,
|
| 25 |
+
TOKEN,
|
| 26 |
+
StartTime,
|
| 27 |
+
pbot,
|
| 28 |
+
telethn,
|
| 29 |
+
aiohttpsession
|
| 30 |
+
)
|
| 31 |
+
from QueenNoxi.modules import ALL_MODULES
|
| 32 |
+
from QueenNoxi.modules.no_sql.users_db import get_served_users
|
| 33 |
+
from QueenNoxi.modules.no_sql.chats_db import get_served_chats
|
| 34 |
+
from QueenNoxi.modules.helper_funcs.misc import paginate_modules
|
| 35 |
+
from QueenNoxi.modules.sql.session_sql import save_session
|
| 36 |
+
|
| 37 |
+
# SUPPORT_CHAT_URL is now centralized in QueenNoxi.__init__
|
| 38 |
+
|
| 39 |
+
# --- DEBUG COMMAND LOGGER ---
|
| 40 |
+
@pbot.on_message(filters.group & filters.text, group=-1)
|
| 41 |
+
async def command_logger(client: pbot, message: Message):
|
| 42 |
+
if message.text and (message.text.startswith("/") or message.text.startswith("!")):
|
| 43 |
+
chat_title = message.chat.title if message.chat else "Private"
|
| 44 |
+
user_id = message.from_user.id if message.from_user else (message.sender_chat.id if message.sender_chat else "Unknown")
|
| 45 |
+
LOGGER.info(f"[COMMAND] Chat: {chat_title} ({message.chat.id}) | User: {user_id} | Text: {message.text}")
|
| 46 |
+
|
| 47 |
+
def get_readable_time(seconds: int) -> str:
|
| 48 |
+
count = 0
|
| 49 |
+
ping_time = ""
|
| 50 |
+
time_list = []
|
| 51 |
+
time_suffix_list = ["s", "m", "h", "days"]
|
| 52 |
+
while count < 4:
|
| 53 |
+
count += 1
|
| 54 |
+
remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24)
|
| 55 |
+
if seconds == 0 and remainder == 0:
|
| 56 |
+
break
|
| 57 |
+
time_list.append(int(result))
|
| 58 |
+
seconds = int(remainder)
|
| 59 |
+
for x in range(len(time_list)):
|
| 60 |
+
time_list[x] = str(time_list[x]) + time_suffix_list[x]
|
| 61 |
+
if len(time_list) == 4:
|
| 62 |
+
ping_time += time_list.pop() + ", "
|
| 63 |
+
time_list.reverse()
|
| 64 |
+
ping_time += ":".join(time_list)
|
| 65 |
+
return ping_time
|
| 66 |
+
|
| 67 |
+
PM_START_TEX = """
|
| 68 |
+
ʜᴇʟʟᴏ `{}`, ʜᴏᴡ ᴀʀᴇ ʏᴏᴜ
|
| 69 |
+
ᴡᴀɪᴛ ᴀ ᴍᴏᴍᴇɴᴛ ʙʀᴏ . . .
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
PM_START_TEXT = """
|
| 73 |
+
*ʜᴇʏ* {} , 🥀
|
| 74 |
+
*๏ ɪ'ᴍ {} ʜᴇʀᴇ ᴛᴏ ʜᴇʟᴘ ʏᴏᴜ ᴍᴀɴᴀɢᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘs!
|
| 75 |
+
ʜɪᴛ ʜᴇʟᴘ ᴛᴏ ғɪɴᴅ ᴏᴜᴛ ᴍᴏʀᴇ ᴀʙᴏᴜᴛ ʜᴏᴡ ᴛᴏ ᴜsᴇ ᴍᴇ ɪɴ ᴍʏ ғᴜʟʟ ᴘᴏᴛᴇɴᴛɪᴀʟ!*
|
| 76 |
+
➻ *ᴛʜᴇ ᴍᴏsᴛ ᴩᴏᴡᴇʀғᴜʟ ᴛᴇʟᴇɢʀᴀᴍ ɢʀᴏᴜᴩ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴏᴛ ᴀɴᴅ ɪ ʜᴀᴠᴇ sᴏᴍᴇ ᴀᴡᴇsᴏᴍᴇ ᴀɴᴅ ᴜsᴇғᴜʟ ғᴇᴀᴛᴜʀᴇs.*
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
buttons = [
|
| 80 |
+
[
|
| 81 |
+
InlineKeyboardButton(text="🛡️", callback_data="queennoxi_"),
|
| 82 |
+
InlineKeyboardButton(text="💳", callback_data="source_"),
|
| 83 |
+
InlineKeyboardButton(text="🧑💻", callback_data="owner_main"),
|
| 84 |
+
InlineKeyboardButton(text="🖥️", callback_data="Main_help"),
|
| 85 |
+
],
|
| 86 |
+
[
|
| 87 |
+
InlineKeyboardButton(
|
| 88 |
+
text="Aᴅᴅ Mᴇ ᴛᴏ Yᴏᴜʀ Gʀᴏᴜᴘ",
|
| 89 |
+
url=f"https://t.me/{BOT_USERNAME}?startgroup=true",
|
| 90 |
+
),
|
| 91 |
+
],
|
| 92 |
+
[
|
| 93 |
+
InlineKeyboardButton(text="📚 ʜᴇʟᴘ ᴀɴᴅ ᴄᴏᴍᴍᴀᴀɴᴅs", callback_data="Main_help"),
|
| 94 |
+
],
|
| 95 |
+
]
|
| 96 |
+
|
| 97 |
+
HELP_STRINGS = f"""
|
| 98 |
+
» *{BOT_NAME} ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴ ʙᴇʟʟᴏᴡ ᴛᴏ ɢᴇᴛ ᴅᴇsᴄʀɪᴘᴛɪᴏᴘ ᴀʙᴏᴜᴛ sᴘᴇᴄɪғɪᴄs ᴄᴏᴍᴍᴀɴᴅ*"""
|
| 99 |
+
|
| 100 |
+
IMPORTED = {}
|
| 101 |
+
HELPABLE = {}
|
| 102 |
+
CHAT_SETTINGS = {}
|
| 103 |
+
USER_SETTINGS = {}
|
| 104 |
+
|
| 105 |
+
for module_name in ALL_MODULES:
|
| 106 |
+
try:
|
| 107 |
+
imported_module = importlib.import_module("QueenNoxi.modules." + module_name)
|
| 108 |
+
if not hasattr(imported_module, "__mod_name__"):
|
| 109 |
+
imported_module.__mod_name__ = imported_module.__name__
|
| 110 |
+
|
| 111 |
+
IMPORTED[imported_module.__mod_name__.lower()] = imported_module
|
| 112 |
+
if hasattr(imported_module, "__help__") and imported_module.__help__:
|
| 113 |
+
HELPABLE[imported_module.__mod_name__.lower()] = imported_module
|
| 114 |
+
|
| 115 |
+
if hasattr(imported_module, "__chat_settings__"):
|
| 116 |
+
CHAT_SETTINGS[imported_module.__mod_name__.lower()] = imported_module
|
| 117 |
+
|
| 118 |
+
if hasattr(imported_module, "__user_settings__"):
|
| 119 |
+
USER_SETTINGS[imported_module.__mod_name__.lower()] = imported_module
|
| 120 |
+
|
| 121 |
+
except Exception as e:
|
| 122 |
+
LOGGER.error(f"Error loading module {module_name}: {e}")
|
| 123 |
+
|
| 124 |
+
async def send_help(chat_id, text, keyboard=None):
|
| 125 |
+
if not keyboard:
|
| 126 |
+
keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help"))
|
| 127 |
+
await pbot.send_photo(
|
| 128 |
+
chat_id=chat_id,
|
| 129 |
+
photo=START_IMG,
|
| 130 |
+
caption=text,
|
| 131 |
+
reply_markup=keyboard,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
@pbot.on_message(filters.command("start"))
|
| 135 |
+
async def start(client: pbot, message: Message):
|
| 136 |
+
args = message.command[1:]
|
| 137 |
+
uptime = get_readable_time((time.time() - StartTime))
|
| 138 |
+
if message.chat.type == enums.ChatType.PRIVATE:
|
| 139 |
+
if len(args) >= 1:
|
| 140 |
+
if args[0].lower() == "help":
|
| 141 |
+
await send_help(message.chat.id, HELP_STRINGS)
|
| 142 |
+
elif args[0].lower().startswith("ghelp_"):
|
| 143 |
+
mod = args[0].lower().split("_", 1)[1]
|
| 144 |
+
if not HELPABLE.get(mod, False):
|
| 145 |
+
return
|
| 146 |
+
await send_help(
|
| 147 |
+
message.chat.id,
|
| 148 |
+
HELPABLE[mod].__help__,
|
| 149 |
+
InlineKeyboardMarkup(
|
| 150 |
+
[[InlineKeyboardButton(text="◁", callback_data="help_back")]]
|
| 151 |
+
),
|
| 152 |
+
)
|
| 153 |
+
else:
|
| 154 |
+
first_name = message.from_user.first_name if message.from_user else "User"
|
| 155 |
+
# Animation effect
|
| 156 |
+
lol = await message.reply_text(PM_START_TEX.format(first_name))
|
| 157 |
+
await asyncio.sleep(0.3)
|
| 158 |
+
await lol.edit_text("❤")
|
| 159 |
+
await asyncio.sleep(0.2)
|
| 160 |
+
await lol.edit_text("ꜱᴛᴀʀᴛɪɴɢ... ")
|
| 161 |
+
await asyncio.sleep(0.2)
|
| 162 |
+
await lol.delete()
|
| 163 |
+
|
| 164 |
+
await message.reply_photo(
|
| 165 |
+
START_IMG,
|
| 166 |
+
caption=PM_START_TEXT.format(first_name, BOT_NAME),
|
| 167 |
+
reply_markup=InlineKeyboardMarkup(buttons),
|
| 168 |
+
)
|
| 169 |
+
else:
|
| 170 |
+
await message.reply_photo(
|
| 171 |
+
START_IMG,
|
| 172 |
+
caption="ɪ ᴀᴍ ᴀʟɪᴠᴇ ʙᴀʙʏ !\n<b>ɪ ᴅɪᴅɴ'ᴛ sʟᴇᴘᴛ sɪɴᴄᴇ:</b> <code>{}</code>".format(uptime),
|
| 173 |
+
parse_mode=enums.ParseMode.HTML,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
@pbot.on_callback_query(filters.regex(r"^help_"))
|
| 177 |
+
async def help_button(client, query: CallbackQuery):
|
| 178 |
+
mod_match = re.match(r"help_module\((.+?)\)", query.data)
|
| 179 |
+
prev_match = re.match(r"help_prev\((.+?)\)", query.data)
|
| 180 |
+
next_match = re.match(r"help_next\((.+?)\)", query.data)
|
| 181 |
+
back_match = re.match(r"help_back", query.data)
|
| 182 |
+
|
| 183 |
+
try:
|
| 184 |
+
if mod_match:
|
| 185 |
+
module = mod_match.group(1)
|
| 186 |
+
text = (
|
| 187 |
+
"» *ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs ꜰᴏʀ* *{}* :\n".format(
|
| 188 |
+
HELPABLE[module].__mod_name__
|
| 189 |
+
)
|
| 190 |
+
+ HELPABLE[module].__help__
|
| 191 |
+
)
|
| 192 |
+
await query.message.edit_caption(text,
|
| 193 |
+
reply_markup=InlineKeyboardMarkup(
|
| 194 |
+
[[InlineKeyboardButton(text="ʙᴀᴄᴋ", callback_data="help_back"),
|
| 195 |
+
InlineKeyboardButton(text="sᴜᴘᴘᴏʀᴛ", callback_data="queennoxi_support")]]
|
| 196 |
+
),
|
| 197 |
+
)
|
| 198 |
+
elif prev_match:
|
| 199 |
+
curr_page = int(prev_match.group(1))
|
| 200 |
+
await query.message.edit_caption(HELP_STRINGS,
|
| 201 |
+
reply_markup=InlineKeyboardMarkup(
|
| 202 |
+
paginate_modules(curr_page - 1, HELPABLE, "help")
|
| 203 |
+
),
|
| 204 |
+
)
|
| 205 |
+
elif next_match:
|
| 206 |
+
next_page = int(next_match.group(1))
|
| 207 |
+
await query.message.edit_caption(HELP_STRINGS,
|
| 208 |
+
reply_markup=InlineKeyboardMarkup(
|
| 209 |
+
paginate_modules(next_page + 1, HELPABLE, "help")
|
| 210 |
+
),
|
| 211 |
+
)
|
| 212 |
+
elif back_match:
|
| 213 |
+
await query.message.edit_caption(HELP_STRINGS,
|
| 214 |
+
reply_markup=InlineKeyboardMarkup(
|
| 215 |
+
paginate_modules(0, HELPABLE, "help")
|
| 216 |
+
),
|
| 217 |
+
)
|
| 218 |
+
await query.answer()
|
| 219 |
+
except Exception:
|
| 220 |
+
pass
|
| 221 |
+
|
| 222 |
+
@pbot.on_callback_query(filters.regex(r"^queennoxi_"))
|
| 223 |
+
async def QueenNoxi_about_callback(client, query: CallbackQuery):
|
| 224 |
+
if query.data == "queennoxi_":
|
| 225 |
+
uptime = get_readable_time((time.time() - StartTime))
|
| 226 |
+
users = await get_served_users()
|
| 227 |
+
chats = await get_served_chats()
|
| 228 |
+
await query.message.edit_caption(
|
| 229 |
+
f"*ʜᴇʏ,*🥀\n *ᴛʜɪs ɪs {BOT_NAME}*"
|
| 230 |
+
"\n*ᴀ ᴘᴏᴡᴇʀꜰᴜʟ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ➕ ᴍᴜsɪᴄ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ ʙᴜɪʟᴛ ᴛᴏ ʜᴇʟᴘ ʏᴏᴜ ᴍᴀɴᴀɢᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴇᴀꜱɪʟʏ ᴀɴᴅ ᴛᴏ ᴘʀᴏᴛᴇᴄᴛ ʏᴏᴜʀ ɢʀᴏᴜᴘ ꜰʀᴏᴍ ꜱᴄᴀᴍᴍᴇʀꜱ ᴀɴᴅ ꜱᴘᴀᴍᴍᴇʀꜱ.*"
|
| 231 |
+
"\n*ᴡʀɪᴛᴛᴇɴ ɪɴ ᴩʏᴛʜᴏɴ ᴡɪᴛʜ sǫʟᴀʟᴄʜᴇᴍʏ ᴀɴᴅ ᴍᴏɴɢᴏᴅʙ ᴀs ᴅᴀᴛᴀʙᴀsᴇ.*"
|
| 232 |
+
"\n\n────────────────────"
|
| 233 |
+
f"\n*➻ ᴜᴩᴛɪᴍᴇ »* {uptime}"
|
| 234 |
+
f"\n* ᴜꜱᴇʀꜱ : {len(users)} "
|
| 235 |
+
f"\n* ᴄʜᴀᴛs : {len(chats)} "
|
| 236 |
+
"\n────────────────────"
|
| 237 |
+
"\n➲ ɪ ᴄᴀɴ ʀᴇꜱᴛʀɪᴄᴛ ᴜꜱᴇʀꜱ."
|
| 238 |
+
"\n➲ ɪ ʜᴀᴠᴇ ᴀɴ ᴀᴅᴠᴀɴᴄᴇᴅ ᴀɴᴛɪ-ꜰʟᴏᴏᴅ ꜱʏꜱᴛᴇᴍ."
|
| 239 |
+
"\n➲ ɪ ᴄᴀɴ ɢʀᴇᴇᴛ ᴜꜱᴇʀꜱ ᴡɪᴛʜ ᴄᴜꜱᴛᴏᴍɪᴢᴀʙʟᴇ ᴡᴇʟᴄᴏᴍᴇ ᴍᴇꜱꜱᴀɢᴇꜱ ᴀɴᴅ ᴇᴠᴇɴ ꜱᴇᴛ ᴀ ɢʀᴏᴜᴘ'ꜱ ʀᴜʟᴇꜱ."
|
| 240 |
+
f"\n\n➻ ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ʙᴜᴛᴛᴏɴs ɢɪᴠᴇɴ ʙᴇʟᴏᴡ ғᴏʀ ɢᴇᴛᴛɪɴɢ ʙᴀsɪᴄ ʜᴇʟᴩ ᴀɴᴅ ɪɴғᴏ ᴀʙᴏᴜᴛ {BOT_NAME}.",
|
| 241 |
+
reply_markup=InlineKeyboardMarkup(
|
| 242 |
+
[
|
| 243 |
+
[
|
| 244 |
+
InlineKeyboardButton(text="🏡", callback_data="queennoxi_back"),
|
| 245 |
+
InlineKeyboardButton(text="💳", callback_data="source_"),
|
| 246 |
+
InlineKeyboardButton(text="🧑💻", url=f"tg://user?id={OWNER_ID}"),
|
| 247 |
+
InlineKeyboardButton(text="🖥️", callback_data="Main_help"),
|
| 248 |
+
],
|
| 249 |
+
[
|
| 250 |
+
InlineKeyboardButton(text="🚩sᴜᴩᴩᴏʀᴛ", callback_data="queennoxi_support"),
|
| 251 |
+
InlineKeyboardButton(text="ᴄᴏᴍᴍᴀɴᴅs 💁", callback_data="Main_help"),
|
| 252 |
+
],
|
| 253 |
+
[
|
| 254 |
+
InlineKeyboardButton(text="👨💻ᴅᴇᴠᴇʟᴏᴩᴇʀ", url=f"tg://user?id={OWNER_ID}"),
|
| 255 |
+
],
|
| 256 |
+
]
|
| 257 |
+
),
|
| 258 |
+
)
|
| 259 |
+
elif query.data == "queennoxi_back":
|
| 260 |
+
await query.message.edit_caption(
|
| 261 |
+
PM_START_TEXT.format(query.from_user.first_name if query.from_user else "User", BOT_NAME),
|
| 262 |
+
reply_markup=InlineKeyboardMarkup(buttons),
|
| 263 |
+
)
|
| 264 |
+
elif query.data == "queennoxi_support":
|
| 265 |
+
await query.message.edit_caption(
|
| 266 |
+
"ʜᴇʏ 👋\nᴄʟɪᴄᴋ sᴜᴩᴩᴏʀᴛ ᴛᴏ ᴊᴏɪɴ sᴜᴩᴩᴏʀᴛ ɢʀᴏᴜᴩ.",
|
| 267 |
+
reply_markup=InlineKeyboardMarkup([
|
| 268 |
+
[InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", url=SUPPORT_CHAT_URL)],
|
| 269 |
+
[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="queennoxi_back")]
|
| 270 |
+
])
|
| 271 |
+
)
|
| 272 |
+
await query.answer()
|
| 273 |
+
|
| 274 |
+
@pbot.on_callback_query(filters.regex(r"^Main_help"))
|
| 275 |
+
async def Main_help_callback(client, query: CallbackQuery):
|
| 276 |
+
await query.message.edit_caption(
|
| 277 |
+
HELP_STRINGS,
|
| 278 |
+
reply_markup=InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help"))
|
| 279 |
+
)
|
| 280 |
+
await query.answer()
|
| 281 |
+
|
| 282 |
+
@pbot.on_callback_query(filters.regex(r"^source_"))
|
| 283 |
+
async def Source_about_callback(client, query: CallbackQuery):
|
| 284 |
+
if query.data == "source_":
|
| 285 |
+
await query.message.edit_caption(
|
| 286 |
+
f"*ʜᴇʏ,\n ᴛʜɪs ɪs {BOT_NAME}*\n\n"
|
| 287 |
+
"*ʜᴇʀᴇ ɪs ᴍʏ sᴏᴜʀᴄᴇ ᴄᴏᴅᴇ :* [ɢɪᴛʜᴜʙ](https://github.com/AlexaInc/queen-noxi)",
|
| 288 |
+
reply_markup=InlineKeyboardMarkup(
|
| 289 |
+
[
|
| 290 |
+
[InlineKeyboardButton(text="sᴏᴜʀᴄᴇ", url="https://github.com/AlexaInc/queen-noxi")],
|
| 291 |
+
[
|
| 292 |
+
InlineKeyboardButton(text="🏡", callback_data="queennoxi_back"),
|
| 293 |
+
InlineKeyboardButton(text="🛡️", callback_data="queennoxi_"),
|
| 294 |
+
InlineKeyboardButton(text="💳", callback_data="source_"),
|
| 295 |
+
InlineKeyboardButton(text="🖥️", callback_data="Main_help"),
|
| 296 |
+
],
|
| 297 |
+
[InlineKeyboardButton(text="◁", callback_data="source_back")]
|
| 298 |
+
]
|
| 299 |
+
),
|
| 300 |
+
)
|
| 301 |
+
elif query.data == "source_back":
|
| 302 |
+
await query.message.edit_caption(
|
| 303 |
+
PM_START_TEXT.format(query.from_user.first_name if query.from_user else "User", BOT_NAME),
|
| 304 |
+
reply_markup=InlineKeyboardMarkup(buttons),
|
| 305 |
+
)
|
| 306 |
+
await query.answer()
|
| 307 |
+
|
| 308 |
+
@pbot.on_callback_query(filters.regex(r"^owner_"))
|
| 309 |
+
async def owner_callback(client, query: CallbackQuery):
|
| 310 |
+
data = query.data
|
| 311 |
+
if data == "owner_main":
|
| 312 |
+
from QueenNoxi import OWNER_IDS
|
| 313 |
+
owner_buttons = []
|
| 314 |
+
for oid in OWNER_IDS:
|
| 315 |
+
try:
|
| 316 |
+
user = await client.get_users(oid)
|
| 317 |
+
name = user.first_name
|
| 318 |
+
# Use username if available for better redirection
|
| 319 |
+
if user.username:
|
| 320 |
+
url = f"https://t.me/{user.username}"
|
| 321 |
+
else:
|
| 322 |
+
# Fallback to tg://user?id= which works in most modern clients if used correctly
|
| 323 |
+
url = f"tg://user?id={oid}"
|
| 324 |
+
owner_buttons.append([InlineKeyboardButton(text=f"👤 {name}", url=url)])
|
| 325 |
+
except Exception:
|
| 326 |
+
owner_buttons.append([InlineKeyboardButton(text=f"👤 Owner {oid}", url=f"tg://user?id={oid}")])
|
| 327 |
+
|
| 328 |
+
if not owner_buttons:
|
| 329 |
+
owner_buttons.append([InlineKeyboardButton(text="👤 Main Owner", url=f"tg://user?id={OWNER_ID}")])
|
| 330 |
+
|
| 331 |
+
owner_buttons.append([InlineKeyboardButton(text="◁ Back", callback_data="queennoxi_back")])
|
| 332 |
+
|
| 333 |
+
await query.message.edit_caption(
|
| 334 |
+
"✨ **Owner Selection Menu**\n\nChoose an owner profile to view:",
|
| 335 |
+
reply_markup=InlineKeyboardMarkup(owner_buttons),
|
| 336 |
+
)
|
| 337 |
+
await query.answer()
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
@pbot.on_callback_query(filters.regex(r"^Music_"))
|
| 341 |
+
async def music_callback(client, query: CallbackQuery):
|
| 342 |
+
data = query.data
|
| 343 |
+
NAV = [
|
| 344 |
+
InlineKeyboardButton("🏡", callback_data="queennoxi_back"),
|
| 345 |
+
InlineKeyboardButton("🛡️", callback_data="queennoxi_"),
|
| 346 |
+
InlineKeyboardButton("💳", callback_data="source_"),
|
| 347 |
+
InlineKeyboardButton("🖥️", callback_data="Main_help"),
|
| 348 |
+
]
|
| 349 |
+
if data == "Music_":
|
| 350 |
+
await query.message.edit_caption(
|
| 351 |
+
"ʜᴇʀᴇ ɪꜱ ʜᴇʟᴘ ᴍᴇɴᴜ ꜰᴏʀ ᴍᴜꜱɪᴄ",
|
| 352 |
+
reply_markup=InlineKeyboardMarkup([
|
| 353 |
+
NAV,
|
| 354 |
+
[InlineKeyboardButton("⍟ ᴀᴅᴍɪɴ ⍟", callback_data="Music_admin"),
|
| 355 |
+
InlineKeyboardButton("⍟ ᴘʟᴀʏ ⍟", callback_data="Music_play")],
|
| 356 |
+
[InlineKeyboardButton("⍟ ʙᴏᴛ ⍟", callback_data="Music_bot"),
|
| 357 |
+
InlineKeyboardButton("⍟ ᴇxᴛʀᴀ ⍟", callback_data="Music_extra")],
|
| 358 |
+
[InlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="Main_help")],
|
| 359 |
+
]),
|
| 360 |
+
)
|
| 361 |
+
elif data == "Music_admin":
|
| 362 |
+
await query.message.edit_caption(
|
| 363 |
+
"*» ᴀᴅᴍɪɴ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n"
|
| 364 |
+
"/pause – ᴩᴀᴜsᴇ stream\n/resume – ʀᴇsᴜᴍᴇ\n"
|
| 365 |
+
"/skip – sᴋɪᴩ ᴄᴜʀʀᴇɴᴛ\n/end,/stop – sᴛᴏᴩ & ᴄʟᴇᴀʀ ǫᴜᴇᴜᴇ\n"
|
| 366 |
+
"/player – ɪɴᴛᴇʀᴀᴄᴛɪᴠᴇ ᴩᴀɴᴇʟ\n/queue – sʜᴏᴡ ǫᴜᴇᴜᴇ",
|
| 367 |
+
reply_markup=InlineKeyboardMarkup([[
|
| 368 |
+
InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="Music_"),
|
| 369 |
+
InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", url=SUPPORT_CHAT_URL),
|
| 370 |
+
]]),
|
| 371 |
+
)
|
| 372 |
+
elif data == "Music_play":
|
| 373 |
+
await query.message.edit_caption(
|
| 374 |
+
"*» ᴘʟᴀʏ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n"
|
| 375 |
+
"/play, /vplay, /cplay – ᴩʟᴀʏ ᴀᴜᴅɪᴏ/ᴠɪᴅᴇᴏ\n"
|
| 376 |
+
"/playforce – ғᴏʀᴄᴇ ᴩʟᴀʏ\n"
|
| 377 |
+
"/channelplay [id/disable] – ᴄʜᴀɴɴᴇʟ ᴩʟᴀʏ",
|
| 378 |
+
reply_markup=InlineKeyboardMarkup([[
|
| 379 |
+
InlineKeyboardButton("• ʙᴀᴄᴋ •", callback_data="Music_"),
|
| 380 |
+
InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", url=SUPPORT_CHAT_URL),
|
| 381 |
+
]]),
|
| 382 |
+
)
|
| 383 |
+
elif data == "Music_bot":
|
| 384 |
+
await query.message.edit_caption(
|
| 385 |
+
"*» ʙᴏᴛ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n"
|
| 386 |
+
"/stats – ɢʟᴏʙᴀʟ sᴛᴀᴛs\n/sudolist – sᴜᴅᴏ ᴜsᴇʀs\n"
|
| 387 |
+
"/lyrics [ɴᴀᴍᴇ] – ꜰᴇᴛᴄʜ ʟʏʀɪᴄs\n/song [ɴᴀᴍᴇ/ᴜʀʟ] – ᴅᴏᴡᴀɴʟᴏᴀᴅ",
|
| 388 |
+
reply_markup=InlineKeyboardMarkup([[
|
| 389 |
+
InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="Music_"),
|
| 390 |
+
InlineKeyboardButton("sᴜᴩᴩᴏʀᴛ", callback_data="queennoxi_support"),
|
| 391 |
+
]]),
|
| 392 |
+
)
|
| 393 |
+
elif data == "Music_extra":
|
| 394 |
+
await query.message.edit_caption(
|
| 395 |
+
"*» ᴇxᴛʀᴀ ᴄᴏᴍᴍᴀɴᴅꜱ «*\n"
|
| 396 |
+
"/mstart – sᴛᴀʀᴛ ᴍᴜsɪᴄ ʙᴏᴛ\n/mhelp – ᴍᴜsɪᴄ ʜᴇʟᴩ\n",
|
| 397 |
+
reply_markup=InlineKeyboardMarkup([[
|
| 398 |
+
InlineKeyboardButton("• sᴜᴩᴩᴏʀᴛ •", url=SUPPORT_CHAT_URL)
|
| 399 |
+
]])
|
| 400 |
+
)
|
| 401 |
+
await query.answer()
|
| 402 |
+
|
| 403 |
+
async def main():
|
| 404 |
+
import QueenNoxi
|
| 405 |
+
from aiohttp import ClientSession
|
| 406 |
+
QueenNoxi.aiohttpsession = ClientSession()
|
| 407 |
+
|
| 408 |
+
# --- Pyrogram Start ---
|
| 409 |
+
try:
|
| 410 |
+
await pbot.start()
|
| 411 |
+
LOGGER.info("[Pyrogram] Client started.")
|
| 412 |
+
|
| 413 |
+
# Export and save session string for persistence
|
| 414 |
+
try:
|
| 415 |
+
ss = await pbot.export_session_string()
|
| 416 |
+
save_session(BOT_ID, ss)
|
| 417 |
+
LOGGER.info("[Pyrogram] Saved session string for persistence.")
|
| 418 |
+
except Exception as e:
|
| 419 |
+
LOGGER.warning(f"Could not export/save session string: {e}")
|
| 420 |
+
|
| 421 |
+
await asyncio.sleep(2)
|
| 422 |
+
try:
|
| 423 |
+
if hasattr(pbot, 'delete_webhook'):
|
| 424 |
+
await pbot.delete_webhook(drop_pending_updates=True)
|
| 425 |
+
elif hasattr(pbot, 'delete_web_hook'):
|
| 426 |
+
await pbot.delete_web_hook(drop_pending_updates=True)
|
| 427 |
+
LOGGER.info("[Pyrogram] Webhook cleared and queue flushed.")
|
| 428 |
+
except FloodWait as e:
|
| 429 |
+
LOGGER.warning(f"[Pyrogram] FloodWait while clearing webhook: {e.value}s")
|
| 430 |
+
except FloodWait as e:
|
| 431 |
+
LOGGER.error(f"[Pyrogram] CRITICAL FloodWait on start: {e.value}s. Sleeping...")
|
| 432 |
+
await asyncio.sleep(e.value)
|
| 433 |
+
except Exception as e:
|
| 434 |
+
LOGGER.error(f"[Pyrogram] Failed to start client: {e}")
|
| 435 |
+
|
| 436 |
+
# --- Telethon Start ---
|
| 437 |
+
try:
|
| 438 |
+
await telethn.start(bot_token=TOKEN)
|
| 439 |
+
LOGGER.info("[Telethon] Client started.")
|
| 440 |
+
except TlFloodWait as e:
|
| 441 |
+
LOGGER.warning(f"[Telethon] FloodWait on start: {e.seconds}s.")
|
| 442 |
+
except Exception as e:
|
| 443 |
+
LOGGER.error(f"[Telethon] Failed to start client: {e}")
|
| 444 |
+
|
| 445 |
+
# --- Handoff ---
|
| 446 |
+
if pbot.is_connected:
|
| 447 |
+
me = await pbot.get_me()
|
| 448 |
+
LOGGER.info(f"[INFO] Bot running as @{me.username} | {me.first_name}")
|
| 449 |
+
|
| 450 |
+
if SUPPORT_CHAT and not SUPPORT_CHAT.startswith("http"):
|
| 451 |
+
try:
|
| 452 |
+
target = SUPPORT_CHAT if SUPPORT_CHAT.startswith("-100") else f"@{SUPPORT_CHAT}"
|
| 453 |
+
await pbot.send_photo(
|
| 454 |
+
target,
|
| 455 |
+
photo=START_IMG,
|
| 456 |
+
caption=f"✨ {BOT_NAME} ɪs ᴀʟɪᴠᴇ ʙᴀʙʏ.\n\n"
|
| 457 |
+
f"**ᴩʏᴛʜᴏɴ ᴠᴇʀsɪᴏɴ:** `{y()}`\n"
|
| 458 |
+
f"**ᴩʏʀᴏɢʀᴀᴍ ᴠᴇʀsɪᴏɴ:** `{pyrover}`\n",
|
| 459 |
+
reply_markup=InlineKeyboardMarkup([[
|
| 460 |
+
InlineKeyboardButton("➕ Aᴅᴅ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ᴄʜᴀᴛ ➕", url=f"https://t.me/{me.username}?startgroup=true")
|
| 461 |
+
]]),
|
| 462 |
+
)
|
| 463 |
+
except Exception as e:
|
| 464 |
+
LOGGER.warning(f"Could not announce to @{SUPPORT_CHAT}: {e}")
|
| 465 |
+
else:
|
| 466 |
+
LOGGER.error("[CRITICAL] Pyrogram client is NOT connected. Features will be disabled.")
|
| 467 |
+
|
| 468 |
+
LOGGER.info(f"Successfully loaded {len(ALL_MODULES)} modules.")
|
| 469 |
+
LOGGER.info("Bot is running. Press Ctrl+C to stop.")
|
| 470 |
+
|
| 471 |
+
await idle()
|
| 472 |
+
|
| 473 |
+
if pbot.is_connected:
|
| 474 |
+
await pbot.stop()
|
| 475 |
+
if telethn.is_connected():
|
| 476 |
+
await telethn.disconnect()
|
| 477 |
+
await QueenNoxi.aiohttpsession.close()
|
| 478 |
+
|
| 479 |
+
if __name__ == "__main__":
|
| 480 |
+
asyncio.run(main())
|
QueenNoxi/brain/data/brain.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:814980d5c0908529060fe6c7a74f95ab9482d4cd538f3ab050c5f454d832642d
|
| 3 |
+
size 12969712
|
QueenNoxi/brain/data/dictionary.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bc1b3253f324cb5ce968c269252e7cf7d383026fbd50aac1964c850bf3d165c0
|
| 3 |
+
size 8714062
|
QueenNoxi/brain/data/persona.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fa9fc7b188e0263b963b74e31a8e9c751f45282cb87a17e15cfbcbb58830f32b
|
| 3 |
+
size 869
|
QueenNoxi/brain/data/phonetic_map.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b9d3dbbadbfca3777d5f8e7269c1e691a594bb91473c324e572801321534655e
|
| 3 |
+
size 21295805
|
QueenNoxi/brain/data/slang.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8bc513452216e7f8a6c13324f09734de6d0973d610fc83b8aae52e7351898aa4
|
| 3 |
+
size 1248
|
QueenNoxi/brain/data/users.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:99245b13a40b590c2903a1b9ace0a07e55166ce6179a72fb4e6b6023c161c474
|
| 3 |
+
size 26626
|
QueenNoxi/brain/database/anon.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e7cb05c9ba971cf4cfe53cda4d0bd302d1c1254b4f2890ccd12828d09c850aa9
|
| 3 |
+
size 1456
|
QueenNoxi/brain/index.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const brain = require('./src/brain');
|
| 2 |
+
const processor = require('./src/processor');
|
| 3 |
+
const UserMemory = require('./src/memory');
|
| 4 |
+
const GroupSession = require('./src/group');
|
| 5 |
+
const nlu = require('./src/nlu');
|
| 6 |
+
const ContextWindow = require('./src/context');
|
| 7 |
+
const Dictionary = require('./src/dictionary');
|
| 8 |
+
const path = require('path');
|
| 9 |
+
|
| 10 |
+
class SinhalaHumanoid {
|
| 11 |
+
/**
|
| 12 |
+
* @param {Object} options
|
| 13 |
+
* @param {string} options.style - Chat style: 'chill' | 'professional' | 'sexy' | 'hot' | 'auto'
|
| 14 |
+
* @param {string} options.name - Bot's name (e.g. 'Kasun')
|
| 15 |
+
* @param {string} options.birthday - Bot's birthday (e.g. '1998-05-15')
|
| 16 |
+
* @param {string} options.residence - Bot's home city (e.g. 'Colombo')
|
| 17 |
+
* @param {string} options.job - Bot's job (e.g. 'Web Developer')
|
| 18 |
+
* @param {string} options.gender - Bot's gender ('Male'|'Female'|'Other')
|
| 19 |
+
* @param {string} options.usersDb - Path to users.json (optional)
|
| 20 |
+
*/
|
| 21 |
+
constructor(options = {}) {
|
| 22 |
+
const personaFields = {};
|
| 23 |
+
if (options.name) personaFields.name = options.name;
|
| 24 |
+
if (options.birthday) personaFields.birthday = options.birthday;
|
| 25 |
+
if (options.residence) personaFields.residence = options.residence;
|
| 26 |
+
if (options.job) personaFields.job = options.job;
|
| 27 |
+
if (options.gender) personaFields.gender = options.gender;
|
| 28 |
+
if (options.style) personaFields.style = options.style;
|
| 29 |
+
if (options.persona) Object.assign(personaFields, options.persona);
|
| 30 |
+
|
| 31 |
+
if (Object.keys(personaFields).length > 0) brain.setPersona(personaFields);
|
| 32 |
+
|
| 33 |
+
this.autoStyle = (options.style === 'auto');
|
| 34 |
+
|
| 35 |
+
// Priority: options.mongoUri -> process.env.CHATDB_URL -> options.usersDb -> default
|
| 36 |
+
this.mongoUri = options.mongoUri || process.env.CHATDB_URL || null;
|
| 37 |
+
this.usersDb = options.usersDb || path.join(process.cwd(), 'database');
|
| 38 |
+
|
| 39 |
+
const memoryPath = this.mongoUri || this.usersDb;
|
| 40 |
+
this.userMemory = new UserMemory(memoryPath);
|
| 41 |
+
|
| 42 |
+
const dictPath = options.dictionaryDb || path.join(__dirname, 'data', 'dictionary.json');
|
| 43 |
+
this.dictionary = new Dictionary(dictPath);
|
| 44 |
+
|
| 45 |
+
this.groups = {};
|
| 46 |
+
this.contexts = {};
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
async init() {
|
| 51 |
+
await this.userMemory.init();
|
| 52 |
+
return this;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
_getContext(userId) {
|
| 56 |
+
if (!this.contexts[userId]) this.contexts[userId] = new ContextWindow();
|
| 57 |
+
return this.contexts[userId];
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
_buildResponse(message, userId, ctx) {
|
| 61 |
+
const user = this.userMemory.getUser(userId);
|
| 62 |
+
const analysis = nlu.analyze(message);
|
| 63 |
+
ctx.addUserTurn(message, analysis);
|
| 64 |
+
|
| 65 |
+
const style = this.autoStyle
|
| 66 |
+
? analysis.detectedStyle
|
| 67 |
+
: ((brain.persona && brain.persona.style) || 'chill');
|
| 68 |
+
|
| 69 |
+
const prevStyle = brain.persona.style;
|
| 70 |
+
if (this.autoStyle) brain.setPersona({ style });
|
| 71 |
+
|
| 72 |
+
const extracted = this.userMemory.extractFromMessage(userId, message);
|
| 73 |
+
if (extracted.name) {
|
| 74 |
+
const resp = `Suba na ${user.name}! Oyatama ingathin ganna.`;
|
| 75 |
+
ctx.addBotTurn(resp);
|
| 76 |
+
return resp;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
const memQ = this.userMemory.memoryQuery(userId, message);
|
| 80 |
+
if (memQ) { ctx.addBotTurn(memQ); return memQ; }
|
| 81 |
+
|
| 82 |
+
const dictWord = this.dictionary.isDictionaryQuery(message);
|
| 83 |
+
if (dictWord) {
|
| 84 |
+
const entry = this.dictionary.lookup(dictWord);
|
| 85 |
+
if (entry) {
|
| 86 |
+
const resp = this.dictionary.formatResponse(dictWord, entry);
|
| 87 |
+
if (resp) { ctx.addBotTurn(resp); return resp; }
|
| 88 |
+
} else {
|
| 89 |
+
const notFound = {
|
| 90 |
+
chill: `Aiyo sry machan, "${dictWord}" kiyana eka gana mama danne na. Aluth vacanayak da?`,
|
| 91 |
+
professional: `Sathutu wenawa kiyanna, habai "${dictWord}" kiyana vacanaya mage dictionary eke na.`,
|
| 92 |
+
sexy: `hmmm "${dictWord}"... eka amuthu vacanayak ne? mama danne na eka gana...`,
|
| 93 |
+
hot: `"${dictWord}"? eka gana passe kiyannam. dan kiyanna ba.`
|
| 94 |
+
};
|
| 95 |
+
const resp = notFound[style] || notFound['chill'];
|
| 96 |
+
ctx.addBotTurn(resp);
|
| 97 |
+
return resp;
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
if (analysis.isGreeting && user.name) {
|
| 102 |
+
const timeSinceSeen = user.lastSeen ? Date.now() - user.lastSeen : Infinity;
|
| 103 |
+
if (timeSinceSeen > 5 * 60 * 1000) {
|
| 104 |
+
const greet = this.userMemory.greet(userId);
|
| 105 |
+
if (greet) { ctx.addBotTurn(greet); return greet; }
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
if (analysis.isFarewell) {
|
| 110 |
+
const byes = {
|
| 111 |
+
chill: ['ok yanawa machan, hodatama yanawa', 'bye bye, posuwa enna', 'hodatama yanawa, bye!'],
|
| 112 |
+
professional: ['Bye! Hoda dine ekak waewa.'],
|
| 113 |
+
sexy: ['aww bye baby, miss karannawa', 'bye, jld ohh laa'],
|
| 114 |
+
hot: ['ok bye, jld enna ha', 'missing already']
|
| 115 |
+
};
|
| 116 |
+
const pool = byes[style] || byes['chill'];
|
| 117 |
+
const resp = pool[Math.floor(Math.random() * pool.length)];
|
| 118 |
+
ctx.addBotTurn(resp);
|
| 119 |
+
return resp;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
const tonePrefix = nlu.toneReaction(analysis.tone);
|
| 123 |
+
const parts = [];
|
| 124 |
+
|
| 125 |
+
if (analysis.isLong) {
|
| 126 |
+
for (const topic of analysis.topics.slice(0, 2)) {
|
| 127 |
+
const topicResp = nlu.getTopicResponse(topic, style);
|
| 128 |
+
if (topicResp) parts.push(topicResp);
|
| 129 |
+
}
|
| 130 |
+
if (!parts.length) {
|
| 131 |
+
for (const sentence of analysis.sentences.slice(0, 2)) {
|
| 132 |
+
const r = brain.respond(sentence, { user });
|
| 133 |
+
if (r) parts.push(brain.fillTemplate(r, { user }));
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
if (!parts.length) {
|
| 139 |
+
for (const sentence of analysis.sentences) {
|
| 140 |
+
// 1. Brain match (most specific knowledge)
|
| 141 |
+
const r = brain.respond(sentence, { user });
|
| 142 |
+
if (r) {
|
| 143 |
+
parts.push(brain.fillTemplate(r, { user }));
|
| 144 |
+
break;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
// 2. Fallback to general topics
|
| 148 |
+
const topicHit = analysis.topics.find(t => nlu.getTopicResponse(t, style));
|
| 149 |
+
if (topicHit) {
|
| 150 |
+
parts.push(nlu.getTopicResponse(topicHit, style));
|
| 151 |
+
break;
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
if (ctx.isAnsweringBotQuestion() && !analysis.isQuestion && parts.length) {
|
| 157 |
+
const acks = ['ahh ok ok', 'oo arageina', 'ah supa', 'ok ok gotcha'];
|
| 158 |
+
parts.unshift(acks[Math.floor(Math.random() * acks.length)]);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
let finalParts = parts.filter(Boolean);
|
| 162 |
+
if (tonePrefix && finalParts.length) {
|
| 163 |
+
finalParts = [tonePrefix, ...finalParts];
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
if (!finalParts.length) {
|
| 167 |
+
finalParts = [brain.getFallback()];
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
if (Math.random() < 0.4 && !analysis.isQuestion) {
|
| 171 |
+
const followUps = {
|
| 172 |
+
chill: ['ekath?', 'oya kohomada?', 'moko kiyanne?', 'oya thiyenawada?'],
|
| 173 |
+
professional: ['Oba kohomada?', 'Meken help one da?'],
|
| 174 |
+
sexy: ['oya mokada karanne tawa?', 'oya miss una neda?'],
|
| 175 |
+
hot: ['oya kiyanne?', 'enna enna']
|
| 176 |
+
};
|
| 177 |
+
const pool = followUps[style] || followUps['chill'];
|
| 178 |
+
finalParts.push(pool[Math.floor(Math.random() * pool.length)]);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
const response = finalParts.join(' ');
|
| 182 |
+
ctx.addBotTurn(response);
|
| 183 |
+
if (this.autoStyle) brain.setPersona({ style: prevStyle });
|
| 184 |
+
return response;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
getResponse(message, userId = 'anon') {
|
| 188 |
+
this.userMemory.addHistory(userId, message, 'user');
|
| 189 |
+
const ctx = this._getContext(userId);
|
| 190 |
+
const response = this._buildResponse(message, userId, ctx);
|
| 191 |
+
this.userMemory.addHistory(userId, response, 'bot');
|
| 192 |
+
return response;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
async getGroupResponse(message, userId, groupId = 'default') {
|
| 196 |
+
if (!this.groups[groupId]) {
|
| 197 |
+
this.groups[groupId] = new GroupSession({
|
| 198 |
+
groupId,
|
| 199 |
+
memory: this.userMemory,
|
| 200 |
+
mongoUri: this.mongoUri,
|
| 201 |
+
usersDb: this.usersDb
|
| 202 |
+
});
|
| 203 |
+
await this.groups[groupId].init();
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
const group = this.groups[groupId];
|
| 207 |
+
await group.addMessage(userId, message, userId);
|
| 208 |
+
|
| 209 |
+
const memQuery = this.userMemory.memoryQuery(userId, message);
|
| 210 |
+
if (memQuery) {
|
| 211 |
+
await group.addMessage('bot', memQuery, 'bot');
|
| 212 |
+
return memQuery;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
this.userMemory.extractFromMessage(userId, message);
|
| 216 |
+
|
| 217 |
+
const botName = (brain.persona && brain.persona.name) || 'Kasun';
|
| 218 |
+
const analysis = nlu.analyze(message);
|
| 219 |
+
const addressed = message.toLowerCase().includes(botName.toLowerCase())
|
| 220 |
+
|| message.toLowerCase().includes('bot')
|
| 221 |
+
|| analysis.isGreeting;
|
| 222 |
+
const randomReply = Math.random() < 0.3;
|
| 223 |
+
|
| 224 |
+
if (!addressed && !randomReply) return null;
|
| 225 |
+
|
| 226 |
+
const ctx = this._getContext(`${groupId}:${userId}`);
|
| 227 |
+
const user = this.userMemory.getUser(userId);
|
| 228 |
+
const response = this._buildResponse(message, userId, ctx);
|
| 229 |
+
|
| 230 |
+
await group.addMessage('bot', response, 'bot');
|
| 231 |
+
return response;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/**
|
| 235 |
+
* Permanent Learning: Add a new pattern to the brain and save to disk.
|
| 236 |
+
*/
|
| 237 |
+
async learn(input, response, style = 'chill') {
|
| 238 |
+
const prevStyle = (brain.persona && brain.persona.style) || 'chill';
|
| 239 |
+
brain.setPersona({ style });
|
| 240 |
+
brain.learn(input, response, true);
|
| 241 |
+
brain.setPersona({ style: prevStyle });
|
| 242 |
+
return true;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
trainFromGroup(groupId) {
|
| 246 |
+
const group = this.groups[groupId];
|
| 247 |
+
if (group) group.autoTrainFromGroup(brain);
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
trainData(data, style) {
|
| 251 |
+
const Trainer = require('./src/trainer');
|
| 252 |
+
return Trainer.trainData(data, style);
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
autoTrain(history) {
|
| 256 |
+
for (let i = 0; i < history.length - 1; i++) {
|
| 257 |
+
const curr = history[i], next = history[i + 1];
|
| 258 |
+
if (curr.sender !== next.sender) brain.learn(curr.text, next.text);
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
setStyle(style) { brain.setPersona({ style }); }
|
| 263 |
+
setPersona(details) { brain.setPersona(details); }
|
| 264 |
+
getUser(userId) { return this.userMemory.getUser(userId); }
|
| 265 |
+
setUserData(u, f, v) { this.userMemory.setUserData(u, f, v); }
|
| 266 |
+
setUserFact(u, k, v) { this.userMemory.setFact(u, k, v); }
|
| 267 |
+
getUserFact(u, k) { return this.userMemory.getFact(u, k); }
|
| 268 |
+
resetContext(userId) { if (this.contexts[userId]) this.contexts[userId].reset(); }
|
| 269 |
+
analyze(message) { return nlu.analyze(message); }
|
| 270 |
+
detectLanguage(text) {
|
| 271 |
+
return /[\u0D80-\u0DFF]/.test(text) ? 'sinhala' : 'singlish';
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
const createBot = (options) => new SinhalaHumanoid(options);
|
| 276 |
+
|
| 277 |
+
module.exports = {
|
| 278 |
+
SinhalaHumanoid,
|
| 279 |
+
createBot
|
| 280 |
+
};
|
QueenNoxi/brain/query.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const { createBot } = require('./index.js');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
const fs = require('fs');
|
| 4 |
+
|
| 5 |
+
async function main() {
|
| 6 |
+
const args = process.argv.slice(2);
|
| 7 |
+
const command = args[0] || 'query';
|
| 8 |
+
|
| 9 |
+
try {
|
| 10 |
+
const databasePath = path.join(__dirname, 'database');
|
| 11 |
+
if (!fs.existsSync(databasePath)) {
|
| 12 |
+
fs.mkdirSync(databasePath, { recursive: true });
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
const bot = createBot({
|
| 16 |
+
name: 'Queen Noxi',
|
| 17 |
+
style: 'auto',
|
| 18 |
+
birthday: '2004-01-21',
|
| 19 |
+
gender: 'Female',
|
| 20 |
+
residence: 'Sri Lanka',
|
| 21 |
+
job: 'Student',
|
| 22 |
+
mongoUri: process.env.CHATDB_URL || null,
|
| 23 |
+
usersDb: databasePath,
|
| 24 |
+
dictionaryDb: path.join(__dirname, 'data', 'dictionary.json')
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
await bot.init();
|
| 28 |
+
|
| 29 |
+
if (command === 'learn') {
|
| 30 |
+
const input = args[1];
|
| 31 |
+
const response = args[2];
|
| 32 |
+
if (!input || !response) {
|
| 33 |
+
process.stderr.write('Usage: learn <input> <response>');
|
| 34 |
+
process.exit(1);
|
| 35 |
+
}
|
| 36 |
+
await bot.learn(input, response);
|
| 37 |
+
process.stdout.write('LEARNED');
|
| 38 |
+
} else {
|
| 39 |
+
// query command
|
| 40 |
+
const text = args[1];
|
| 41 |
+
const userId = args[2] || 'anon';
|
| 42 |
+
const groupId = args[3] || null;
|
| 43 |
+
|
| 44 |
+
if (!text) {
|
| 45 |
+
process.exit(1);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
let response;
|
| 49 |
+
if (groupId) {
|
| 50 |
+
response = await bot.getGroupResponse(text, userId, groupId);
|
| 51 |
+
} else {
|
| 52 |
+
response = bot.getResponse(text, userId);
|
| 53 |
+
}
|
| 54 |
+
process.stdout.write(response || '');
|
| 55 |
+
}
|
| 56 |
+
} catch (err) {
|
| 57 |
+
process.stderr.write(err.message || String(err));
|
| 58 |
+
process.exit(1);
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
main();
|
QueenNoxi/brain/src/brain.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
const processor = require('./processor');
|
| 4 |
+
|
| 5 |
+
class Brain {
|
| 6 |
+
constructor() {
|
| 7 |
+
this.memory = {};
|
| 8 |
+
this.persona = {};
|
| 9 |
+
this.loadData();
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
loadData() {
|
| 13 |
+
try {
|
| 14 |
+
const brainPath = path.join(__dirname, '..', 'data', 'brain.json');
|
| 15 |
+
if (fs.existsSync(brainPath)) this.memory = JSON.parse(fs.readFileSync(brainPath, 'utf8'));
|
| 16 |
+
|
| 17 |
+
const personaPath = path.join(__dirname, '..', 'data', 'persona.json');
|
| 18 |
+
if (fs.existsSync(personaPath)) this.persona = JSON.parse(fs.readFileSync(personaPath, 'utf8'));
|
| 19 |
+
} catch (e) { console.error('Error loading brain data:', e); }
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
saveData() {
|
| 23 |
+
try {
|
| 24 |
+
const brainPath = path.join(__dirname, '..', 'data', 'brain.json');
|
| 25 |
+
fs.writeFileSync(brainPath, JSON.stringify(this.memory, null, 2));
|
| 26 |
+
} catch (e) { console.error('Error saving brain data:', e); }
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
setPersona(data) {
|
| 30 |
+
this.persona = { ...this.persona, ...data };
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* Core respond logic. Accepts optional context (userId, userMemory, groupSession)
|
| 35 |
+
*/
|
| 36 |
+
respond(inputText, context = {}) {
|
| 37 |
+
const processedInput = processor.process(inputText);
|
| 38 |
+
if (!processedInput) return this.getFallback();
|
| 39 |
+
|
| 40 |
+
// 1. Direct match
|
| 41 |
+
if (this.memory[processedInput]) {
|
| 42 |
+
return this.fillTemplate(this.pickResponse(this.memory[processedInput]), context);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// 2. Persona questions (Identity should take precedence over fuzzy chat)
|
| 46 |
+
const personaResp = this.checkPersonaQuestions(processedInput, inputText);
|
| 47 |
+
if (personaResp) return this.fillTemplate(personaResp, context);
|
| 48 |
+
|
| 49 |
+
// 3. Fuzzy match
|
| 50 |
+
let bestMatch = null;
|
| 51 |
+
let highestScore = 0;
|
| 52 |
+
for (const [pattern, data] of Object.entries(this.memory)) {
|
| 53 |
+
const score = processor.calculateSimilarity(processedInput, pattern);
|
| 54 |
+
if (score > highestScore && score > 0.65) {
|
| 55 |
+
highestScore = score;
|
| 56 |
+
bestMatch = data;
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
if (bestMatch) {
|
| 60 |
+
return this.fillTemplate(this.pickResponse(bestMatch), context);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
return this.getFallback();
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
pickResponse(data) {
|
| 67 |
+
const style = this.persona.style || 'chill';
|
| 68 |
+
const pool = data[style] || data['chill'] || data['default'] || data;
|
| 69 |
+
if (Array.isArray(pool)) return pool[Math.floor(Math.random() * pool.length)];
|
| 70 |
+
return String(pool);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
/**
|
| 74 |
+
* Fill {name}, {job}, {birthday}, {age}, {residence}, {user_name} etc.
|
| 75 |
+
*/
|
| 76 |
+
fillTemplate(text, context = {}) {
|
| 77 |
+
if (!text) return this.getFallback();
|
| 78 |
+
const p = this.persona;
|
| 79 |
+
const u = context.user || {};
|
| 80 |
+
const now = new Date();
|
| 81 |
+
const birthYear = p.birthday ? new Date(p.birthday).getFullYear() : null;
|
| 82 |
+
|
| 83 |
+
return text
|
| 84 |
+
.replace(/\{name\}/g, p.name || 'Kasun')
|
| 85 |
+
.replace(/\{birthday\}/g, p.birthday || '?')
|
| 86 |
+
.replace(/\{job\}/g, p.job || 'web dev')
|
| 87 |
+
.replace(/\{residence\}/g, p.residence || 'Colombo')
|
| 88 |
+
.replace(/\{age\}/g, birthYear ? (now.getFullYear() - birthYear) : '?')
|
| 89 |
+
.replace(/\{user_name\}/g, u.name || 'machan')
|
| 90 |
+
.replace(/\{user_location\}/g, u.location || 'koheda?')
|
| 91 |
+
.replace(/\{user_job\}/g, u.job || '?');
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
checkPersonaQuestions(processedInput, rawInput = '') {
|
| 95 |
+
const text = processedInput.toLowerCase();
|
| 96 |
+
const raw = rawInput.toLowerCase();
|
| 97 |
+
|
| 98 |
+
// Helper: check either raw or processed text
|
| 99 |
+
const has = (...kws) => kws.some(k => text.includes(k) || raw.includes(k));
|
| 100 |
+
|
| 101 |
+
// Priority order matters — check most specific first
|
| 102 |
+
if (has('oyage gana', 'about you', 'introduce yourself', 'tell me about'))
|
| 103 |
+
return 'mama {name}. {job} kenek. {residence} wala inne.';
|
| 104 |
+
|
| 105 |
+
// Name — must have name-specific trigger word
|
| 106 |
+
if (has('nama', 'name', 'kauda oya', 'oyage kauda', 'oyage nam', 'oyage name'))
|
| 107 |
+
return 'mage nama {name} machan.';
|
| 108 |
+
|
| 109 |
+
// Age / Birthday
|
| 110 |
+
if (has('bday', 'birthday', 'ipaduna', 'wayasa', 'age'))
|
| 111 |
+
return 'mama {age} wiye inne. bday eka {birthday}.';
|
| 112 |
+
|
| 113 |
+
// Residence — must be explicit about location
|
| 114 |
+
if (has('koheda inne', 'live in', 'koheda', 'residence', 'wasi'))
|
| 115 |
+
return 'mama {residence} wala inne machan.';
|
| 116 |
+
|
| 117 |
+
// Job — only if there's a clear job-asking keyword, NOT general "karanne"
|
| 118 |
+
if (has('oyage job', 'what is your job', 'weda karanne mokakda', 'job eka', 'profession', 'oyage weda'))
|
| 119 |
+
return 'mama {job} kenek machan.';
|
| 120 |
+
|
| 121 |
+
return null;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
getFallback() {
|
| 125 |
+
const style = this.persona.style || 'chill';
|
| 126 |
+
const fallbacks = (this.persona.fallbacks && this.persona.fallbacks[style])
|
| 127 |
+
|| (this.persona.fallbacks && this.persona.fallbacks['chill'])
|
| 128 |
+
|| ['moko kiyanne?', 'poddak hitapan', 'kiyanna aney'];
|
| 129 |
+
return fallbacks[Math.floor(Math.random() * fallbacks.length)];
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
learn(input, response, shouldSave = true) {
|
| 133 |
+
const processedInput = processor.process(input);
|
| 134 |
+
if (!processedInput || !response) return;
|
| 135 |
+
|
| 136 |
+
const style = this.persona.style || 'chill';
|
| 137 |
+
if (!this.memory[processedInput]) {
|
| 138 |
+
this.memory[processedInput] = {};
|
| 139 |
+
}
|
| 140 |
+
if (!this.memory[processedInput][style]) {
|
| 141 |
+
this.memory[processedInput][style] = [];
|
| 142 |
+
}
|
| 143 |
+
if (!this.memory[processedInput][style].includes(response)) {
|
| 144 |
+
this.memory[processedInput][style].push(response);
|
| 145 |
+
}
|
| 146 |
+
if (shouldSave) this.saveData();
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
module.exports = new Brain();
|
QueenNoxi/brain/src/context.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* context.js — Conversation Context Window
|
| 3 |
+
*
|
| 4 |
+
* Tracks:
|
| 5 |
+
* - Recent turns (last N messages from user + bot)
|
| 6 |
+
* - Active topics (what is being discussed)
|
| 7 |
+
* - Active intents
|
| 8 |
+
* - Emotional trend
|
| 9 |
+
*/
|
| 10 |
+
|
| 11 |
+
class ContextWindow {
|
| 12 |
+
constructor(maxTurns = 10) {
|
| 13 |
+
this.maxTurns = maxTurns;
|
| 14 |
+
this.turns = []; // { role: 'user'|'bot', text, analysis?, ts }
|
| 15 |
+
this.activeTopics = []; // most recent topics discussed
|
| 16 |
+
this.activeIntents = []; // most recent intents
|
| 17 |
+
this.tone = 'neutral';
|
| 18 |
+
this.waitingForAnswer = null; // if bot asked a question, track what it asked
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Add a user turn (with NLU analysis attached)
|
| 23 |
+
*/
|
| 24 |
+
addUserTurn(text, analysis) {
|
| 25 |
+
this.turns.push({ role: 'user', text, analysis, ts: Date.now() });
|
| 26 |
+
if (this.turns.length > this.maxTurns * 2) this.turns.shift();
|
| 27 |
+
|
| 28 |
+
if (analysis) {
|
| 29 |
+
// Update active topics (prepend newest, keep last 3)
|
| 30 |
+
this.activeTopics = [...new Set([...analysis.topics, ...this.activeTopics])].slice(0, 3);
|
| 31 |
+
this.activeIntents = analysis.intents;
|
| 32 |
+
this.tone = analysis.tone;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
* Add a bot turn
|
| 38 |
+
*/
|
| 39 |
+
addBotTurn(text) {
|
| 40 |
+
this.turns.push({ role: 'bot', text, ts: Date.now() });
|
| 41 |
+
if (this.turns.length > this.maxTurns * 2) this.turns.shift();
|
| 42 |
+
|
| 43 |
+
// Track if bot asked a question (ends in ?)
|
| 44 |
+
if (text && text.includes('?')) {
|
| 45 |
+
this.waitingForAnswer = text;
|
| 46 |
+
} else {
|
| 47 |
+
this.waitingForAnswer = null;
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/**
|
| 52 |
+
* Get recent user messages (last N)
|
| 53 |
+
*/
|
| 54 |
+
getRecentUserMessages(n = 3) {
|
| 55 |
+
return this.turns.filter(t => t.role === 'user').slice(-n).map(t => t.text);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/**
|
| 59 |
+
* Get last bot message
|
| 60 |
+
*/
|
| 61 |
+
getLastBotMessage() {
|
| 62 |
+
const botTurns = this.turns.filter(t => t.role === 'bot');
|
| 63 |
+
return botTurns.length ? botTurns[botTurns.length - 1].text : null;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/**
|
| 67 |
+
* Check if current message is likely a follow-up to an active topic
|
| 68 |
+
*/
|
| 69 |
+
isFollowUp(analysis) {
|
| 70 |
+
if (!analysis || !this.activeTopics.length) return false;
|
| 71 |
+
return analysis.topics.some(t => this.activeTopics.includes(t));
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* Check if the bot recently asked a question and is waiting for an answer
|
| 76 |
+
*/
|
| 77 |
+
isAnsweringBotQuestion() {
|
| 78 |
+
return !!this.waitingForAnswer;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* Wipe context (for fresh conversation)
|
| 83 |
+
*/
|
| 84 |
+
reset() {
|
| 85 |
+
this.turns = [];
|
| 86 |
+
this.activeTopics = [];
|
| 87 |
+
this.activeIntents = [];
|
| 88 |
+
this.tone = 'neutral';
|
| 89 |
+
this.waitingForAnswer = null;
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
module.exports = ContextWindow;
|
QueenNoxi/brain/src/dictionary.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
const processor = require('./processor');
|
| 4 |
+
|
| 5 |
+
class Dictionary {
|
| 6 |
+
constructor(dbPath) {
|
| 7 |
+
this.dbPath = dbPath || path.join(__dirname, '..', 'data', 'dictionary.json');
|
| 8 |
+
this.data = {};
|
| 9 |
+
this.load();
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
load() {
|
| 13 |
+
try {
|
| 14 |
+
if (fs.existsSync(this.dbPath)) {
|
| 15 |
+
this.data = JSON.parse(fs.readFileSync(this.dbPath, 'utf8'));
|
| 16 |
+
// Create a separate index for normalized keys
|
| 17 |
+
this.normalizedIndex = {};
|
| 18 |
+
for (const key of Object.keys(this.data)) {
|
| 19 |
+
this.normalizedIndex[processor.process(key)] = key;
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
} catch (e) {
|
| 23 |
+
console.error('Dictionary load error:', e);
|
| 24 |
+
this.data = {};
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/**
|
| 29 |
+
* Look up a word in the dictionary.
|
| 30 |
+
* Supports Sinhala script and Singlish (via internal normalization).
|
| 31 |
+
*/
|
| 32 |
+
lookup(word) {
|
| 33 |
+
if (!word) return null;
|
| 34 |
+
|
| 35 |
+
const lower = word.toLowerCase().trim();
|
| 36 |
+
const normalized = processor.process(word);
|
| 37 |
+
|
| 38 |
+
// 1. Direct match (exact)
|
| 39 |
+
if (this.data[lower]) return this.data[lower];
|
| 40 |
+
if (this.data[normalized]) return this.data[normalized];
|
| 41 |
+
|
| 42 |
+
// 2. Normalized index match (e.g. කොම්පියුටර් -> kompiutar -> computer)
|
| 43 |
+
if (this.normalizedIndex[normalized]) {
|
| 44 |
+
return this.data[this.normalizedIndex[normalized]];
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// 3. Fuzzy search for similar words
|
| 48 |
+
let bestMatch = null;
|
| 49 |
+
let highestScore = 0;
|
| 50 |
+
|
| 51 |
+
for (const [key, value] of Object.entries(this.data)) {
|
| 52 |
+
const score = processor.calculateSimilarity(normalized, processor.process(key));
|
| 53 |
+
if (score > highestScore && score > 0.8) {
|
| 54 |
+
highestScore = score;
|
| 55 |
+
bestMatch = value;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
return bestMatch;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
/**
|
| 63 |
+
* Check if the message is a "what is X" type question
|
| 64 |
+
*/
|
| 65 |
+
isDictionaryQuery(text) {
|
| 66 |
+
const lower = text.toLowerCase();
|
| 67 |
+
// Common dictionary query patterns
|
| 68 |
+
const patterns = [
|
| 69 |
+
/mokakda (.*) kiyanne/i,
|
| 70 |
+
/what is (.*)/i,
|
| 71 |
+
/meaning of (.*)/i,
|
| 72 |
+
/arthaya (.*)/i,
|
| 73 |
+
/(.*) kiyanne mokakda/i,
|
| 74 |
+
/(.*) meaning/i
|
| 75 |
+
];
|
| 76 |
+
|
| 77 |
+
for (const pat of patterns) {
|
| 78 |
+
const match = lower.match(pat);
|
| 79 |
+
if (match) {
|
| 80 |
+
// Clean punctuation from the extracted word
|
| 81 |
+
return match[1].trim().replace(/[?!,.]$/, '');
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
return null;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/**
|
| 88 |
+
* Format a dictionary entry into a human-like response
|
| 89 |
+
*/
|
| 90 |
+
formatResponse(word, entry) {
|
| 91 |
+
if (!entry) return null;
|
| 92 |
+
|
| 93 |
+
if (typeof entry === 'string') {
|
| 94 |
+
return `"${word}" kiyanne: ${entry}`;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
let resp = `"${word}" gana mama hoyala beluva... `;
|
| 98 |
+
|
| 99 |
+
if (entry.sin) resp += `Sinhala: ${entry.sin}. `;
|
| 100 |
+
if (entry.en) resp += `English: ${entry.en}. `;
|
| 101 |
+
if (entry.def) resp += `Definition: ${entry.def}. `;
|
| 102 |
+
if (entry.synonyms && entry.synonyms.length) {
|
| 103 |
+
resp += `Synonyms: ${entry.synonyms.slice(0, 3).join(', ')}.`;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
return resp;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
module.exports = Dictionary;
|
QueenNoxi/brain/src/group.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const UserMemory = require('./memory');
|
| 2 |
+
const StorageAdapter = require('./storage');
|
| 3 |
+
const path = require('path');
|
| 4 |
+
|
| 5 |
+
class GroupSession {
|
| 6 |
+
constructor(options = {}) {
|
| 7 |
+
this.groupId = options.groupId || 'default_group';
|
| 8 |
+
this.memory = options.memory; // global UserMemory instance for user lookup
|
| 9 |
+
this.members = new Set();
|
| 10 |
+
this.recentMessages = [];
|
| 11 |
+
this.maxHistory = options.maxHistory || 50;
|
| 12 |
+
|
| 13 |
+
const storagePath = options.mongoUri || (options.usersDb ? path.join(options.usersDb, `${this.groupId}.json`) : path.join(process.cwd(), 'database', `${this.groupId}.json`));
|
| 14 |
+
this.storage = new StorageAdapter(storagePath, 'groups');
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
async init() {
|
| 18 |
+
await this.storage.init();
|
| 19 |
+
const data = await this.storage.loadAll();
|
| 20 |
+
this.recentMessages = data.history || [];
|
| 21 |
+
if (data.members) this.members = new Set(data.members);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
async save() {
|
| 25 |
+
await this.storage.saveAll({
|
| 26 |
+
groupId: this.groupId,
|
| 27 |
+
members: [...this.members],
|
| 28 |
+
history: this.recentMessages,
|
| 29 |
+
lastUpdate: Date.now()
|
| 30 |
+
});
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* Register a member in this group
|
| 35 |
+
*/
|
| 36 |
+
addMember(userId) {
|
| 37 |
+
this.members.add(userId);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Record a message in group context
|
| 42 |
+
*/
|
| 43 |
+
async addMessage(userId, text, from = null) {
|
| 44 |
+
this.addMember(userId);
|
| 45 |
+
const sender = from || userId;
|
| 46 |
+
const msg = { from: sender, text, ts: Date.now() };
|
| 47 |
+
this.recentMessages.push(msg);
|
| 48 |
+
if (this.recentMessages.length > this.maxHistory) {
|
| 49 |
+
this.recentMessages.shift();
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// Also update the global user memory if they are in this group
|
| 53 |
+
if (this.memory && sender !== 'bot') {
|
| 54 |
+
await this.memory.addHistory(sender, text, 'user');
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
await this.save();
|
| 58 |
+
return msg;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/**
|
| 62 |
+
* Get recent group conversation (last N messages)
|
| 63 |
+
*/
|
| 64 |
+
getContext(n = 10) {
|
| 65 |
+
return this.recentMessages.slice(-n);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/**
|
| 69 |
+
* Check if a user was recently active in this group
|
| 70 |
+
*/
|
| 71 |
+
isRecentlyActive(userId, withinMs = 300000) { // 5 min
|
| 72 |
+
const now = Date.now();
|
| 73 |
+
for (let i = this.recentMessages.length - 1; i >= 0; i--) {
|
| 74 |
+
const m = this.recentMessages[i];
|
| 75 |
+
if (m.userId === userId && now - m.ts < withinMs) return true;
|
| 76 |
+
}
|
| 77 |
+
return false;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/**
|
| 81 |
+
* Get all members the bot has seen in this group
|
| 82 |
+
*/
|
| 83 |
+
getMembers() {
|
| 84 |
+
return [...this.members];
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/**
|
| 88 |
+
* Get the last message from a specific user
|
| 89 |
+
*/
|
| 90 |
+
getLastMessageFrom(userId) {
|
| 91 |
+
for (let i = this.recentMessages.length - 1; i >= 0; i--) {
|
| 92 |
+
if (this.recentMessages[i].userId === userId) {
|
| 93 |
+
return this.recentMessages[i];
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
return null;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/**
|
| 100 |
+
* Auto-train the brain from recent group messages
|
| 101 |
+
* (pass in a brain instance)
|
| 102 |
+
*/
|
| 103 |
+
autoTrainFromGroup(brain) {
|
| 104 |
+
const msgs = this.recentMessages;
|
| 105 |
+
for (let i = 0; i < msgs.length - 1; i++) {
|
| 106 |
+
const curr = msgs[i];
|
| 107 |
+
const next = msgs[i + 1];
|
| 108 |
+
if (curr.userId !== next.userId) {
|
| 109 |
+
brain.learn(curr.text, next.text);
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
module.exports = GroupSession;
|
QueenNoxi/brain/src/memory.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const StorageAdapter = require('./storage');
|
| 2 |
+
|
| 3 |
+
class UserMemory {
|
| 4 |
+
constructor(dbPath) {
|
| 5 |
+
this.storage = new StorageAdapter(dbPath, 'users');
|
| 6 |
+
this.users = {};
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
async init() {
|
| 10 |
+
await this.storage.init();
|
| 11 |
+
this.users = await this.storage.loadAll();
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
async save() {
|
| 15 |
+
await this.storage.saveAll(this.users);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Get a user's memory object. Creates one if it doesn't exist.
|
| 20 |
+
* @param {string} userId
|
| 21 |
+
*/
|
| 22 |
+
getUser(userId) {
|
| 23 |
+
if (!this.users[userId]) {
|
| 24 |
+
this.users[userId] = {
|
| 25 |
+
id: userId,
|
| 26 |
+
name: null,
|
| 27 |
+
age: null,
|
| 28 |
+
location: null,
|
| 29 |
+
job: null,
|
| 30 |
+
gender: null,
|
| 31 |
+
nickname: null,
|
| 32 |
+
facts: {}, // Free-form key-value facts
|
| 33 |
+
history: [], // Last N messages
|
| 34 |
+
lastSeen: null,
|
| 35 |
+
seenCount: 0
|
| 36 |
+
};
|
| 37 |
+
} else {
|
| 38 |
+
// Safety check for legacy or malformed files
|
| 39 |
+
if (!this.users[userId].facts) this.users[userId].facts = {};
|
| 40 |
+
if (!this.users[userId].history) this.users[userId].history = [];
|
| 41 |
+
}
|
| 42 |
+
return this.users[userId];
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* Set a specific field on a user
|
| 47 |
+
*/
|
| 48 |
+
async setUserData(userId, field, value) {
|
| 49 |
+
const user = this.getUser(userId);
|
| 50 |
+
user[field] = value;
|
| 51 |
+
await this.save();
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/**
|
| 55 |
+
* Set a free-form fact about a user
|
| 56 |
+
*/
|
| 57 |
+
async setFact(userId, key, value) {
|
| 58 |
+
const user = this.getUser(userId);
|
| 59 |
+
user.facts[key] = value;
|
| 60 |
+
await this.save();
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* Get a fact about a user
|
| 65 |
+
*/
|
| 66 |
+
getFact(userId, key) {
|
| 67 |
+
const user = this.getUser(userId);
|
| 68 |
+
return user.facts[key] || null;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/**
|
| 72 |
+
* Record a message in the user's history (keep last 30)
|
| 73 |
+
*/
|
| 74 |
+
async addHistory(userId, text, from = 'user') {
|
| 75 |
+
const user = this.getUser(userId);
|
| 76 |
+
user.history.push({ text, from, ts: Date.now() });
|
| 77 |
+
if (user.history.length > 30) user.history.shift();
|
| 78 |
+
user.lastSeen = Date.now();
|
| 79 |
+
user.seenCount = (user.seenCount || 0) + 1;
|
| 80 |
+
await this.save();
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* Auto-extract user info from message text (name, location, job, age)
|
| 85 |
+
* Returns any fields that were updated
|
| 86 |
+
*/
|
| 87 |
+
extractFromMessage(userId, text) {
|
| 88 |
+
const lower = text.toLowerCase().trim();
|
| 89 |
+
const extracted = {};
|
| 90 |
+
|
| 91 |
+
// BLACKLIST: common Singlish words that look like a "match" but are NOT names/locations
|
| 92 |
+
const blacklistNames = new Set(['mama', 'machan', 'godak', 'poddak', 'hodai', 'supa', 'maru', 'ada',
|
| 93 |
+
'kalin', 'dan', 'eka', 'api', 'ado', 'ayye', 'aiyo', 'ekath', 'kohomada', 'tani', 'ganna',
|
| 94 |
+
'karanna', 'kiyanna', 'ne', 'da', 'ba', 'ok', 'lol', 'haha', 'bye', 'hi', 'hai', 'hello',
|
| 95 |
+
'sorry', 'thanks', 'library', 'school', 'office', 'bus', 'train', 'gedara', 'weda', 'giia',
|
| 96 |
+
'apahu', 'pass', 'fail', 'suba', 'subha', 'aiya', 'akka', 'nangi', 'malli', 'amma', 'thaththa']);
|
| 97 |
+
|
| 98 |
+
// Name: ONLY on explicit "my name is X" or "mage nama X" (not bare "mama X")
|
| 99 |
+
const nameMatch = lower.match(/(?:my name is|mage nama|namayi)\s+([a-z]{2,20})(?:\s|$|,|\.)/i);
|
| 100 |
+
if (nameMatch && !blacklistNames.has(nameMatch[1].toLowerCase())) {
|
| 101 |
+
const name = this._capitalize(nameMatch[1]);
|
| 102 |
+
this.setUserData(userId, 'name', name); // Note: this is async but we don't await here to keep it non-blocking in NLU
|
| 103 |
+
extracted.name = name;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// Age: "mage wayasa 22", "age 22", "i am 22 years"
|
| 107 |
+
const ageMatch = lower.match(/(?:mage wayasa|wayasa|age)\s+(\d{1,3})/i)
|
| 108 |
+
|| lower.match(/i(?:'| a)m (\d{1,3})\s*y/i);
|
| 109 |
+
if (ageMatch) {
|
| 110 |
+
const age = parseInt(ageMatch[1]);
|
| 111 |
+
if (age > 5 && age < 100) {
|
| 112 |
+
this.setUserData(userId, 'age', age); // Background save
|
| 113 |
+
extracted.age = age;
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// Location: explicit "i live in X", "stay in X" — NOT bare "inne"
|
| 118 |
+
const locMatch = lower.match(/(?:i live in|stay in|wasi)\s+([a-z]{3,20})(?:\s|$|,)/i);
|
| 119 |
+
if (locMatch && !blacklistNames.has(locMatch[1].toLowerCase())) {
|
| 120 |
+
const loc = this._capitalize(locMatch[1].trim());
|
| 121 |
+
this.setUserData(userId, 'location', loc); // Background save
|
| 122 |
+
extracted.location = loc;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
// Job: "i work as X", "weda karanne X", "i am a X"
|
| 126 |
+
const jobMatch = lower.match(/(?:i work as|weda karanne|i'm a|i am a)\s+([a-z\s]{3,30})(?:\s|$|,|\.)/i);
|
| 127 |
+
if (jobMatch) {
|
| 128 |
+
const job = this._capitalize(jobMatch[1].trim());
|
| 129 |
+
this.setUserData(userId, 'job', job); // Background save
|
| 130 |
+
extracted.job = job;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
// Favorites: "my favorite X is Y", "mama kemati X walata"
|
| 134 |
+
// Improved regex with non-greedy match for the 'thing'
|
| 135 |
+
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);
|
| 136 |
+
if (favMatch) {
|
| 137 |
+
const thing = favMatch[1].trim().replace(/\s+/g, '_');
|
| 138 |
+
const value = favMatch[2].trim();
|
| 139 |
+
this.setFact(userId, `favorite_${thing}`, value);
|
| 140 |
+
extracted[`favorite_${thing}`] = value;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Likes: "I like X", "mama X walata kamati"
|
| 144 |
+
const likeMatch = lower.match(/(?:i like|mama)\s+([a-z\s]{2,20})\s+(?:kamathi|kemati|like)/i)
|
| 145 |
+
|| lower.match(/(?:i like)\s+([a-z\s]{2,20})/i);
|
| 146 |
+
if (likeMatch && !blacklistNames.has(likeMatch[1].trim())) {
|
| 147 |
+
const like = likeMatch[1].trim();
|
| 148 |
+
this.setFact(userId, `likes_${like.replace(/\s+/g, '_')}`, true);
|
| 149 |
+
extracted.likes = like;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
return extracted;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
_capitalize(str) {
|
| 156 |
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
/**
|
| 160 |
+
* Compose a greeting using remembered data
|
| 161 |
+
*/
|
| 162 |
+
greet(userId) {
|
| 163 |
+
const user = this.getUser(userId);
|
| 164 |
+
if (user.name) {
|
| 165 |
+
const greetings = [
|
| 166 |
+
`${user.name} machan, kohomada?`,
|
| 167 |
+
`Aiyo ${user.name}, ata ne?`,
|
| 168 |
+
`${user.name}! Oya thiyanawada?`,
|
| 169 |
+
`Oya ${user.name} ne! Kohomada?`
|
| 170 |
+
];
|
| 171 |
+
return greetings[Math.floor(Math.random() * greetings.length)];
|
| 172 |
+
}
|
| 173 |
+
return null;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/**
|
| 177 |
+
* Check if the user is asking the bot about what it knows of them
|
| 178 |
+
*/
|
| 179 |
+
memoryQuery(userId, text) {
|
| 180 |
+
const lower = text.toLowerCase();
|
| 181 |
+
const user = this.getUser(userId);
|
| 182 |
+
if (!user) return null;
|
| 183 |
+
|
| 184 |
+
if (lower.match(/mage nama|my name|oya thiyanawd|do you know me|oyata mata danne|mama gana|about me/)) {
|
| 185 |
+
const parts = [];
|
| 186 |
+
if (user.name) parts.push(`Oba ${user.name}.`);
|
| 187 |
+
if (user.age) parts.push(`Wayasa ${user.age}.`);
|
| 188 |
+
if (user.location) parts.push(`${user.location} wala inne.`);
|
| 189 |
+
if (user.job) parts.push(`${user.job} kenek.`);
|
| 190 |
+
|
| 191 |
+
// Add facts/favorites
|
| 192 |
+
const favs = Object.keys(user.facts).filter(k => k.startsWith('favorite_'));
|
| 193 |
+
if (favs.length) {
|
| 194 |
+
const f = favs[0];
|
| 195 |
+
parts.push(`${f.replace('favorite_', '').replace('_', ' ')} eka ${user.facts[f]}.`);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
if (parts.length) return parts.join(' ');
|
| 199 |
+
}
|
| 200 |
+
return null;
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
module.exports = UserMemory;
|
QueenNoxi/brain/src/nlu.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* nlu.js — Natural Language Understanding Engine
|
| 3 |
+
*
|
| 4 |
+
* Makes the bot understand:
|
| 5 |
+
* 1. Long paragraphs (segmented into individual thoughts)
|
| 6 |
+
* 2. Intent detection (question, greeting, complaint, story, tease, etc.)
|
| 7 |
+
* 3. Keyword/topic extraction (what is the user REALLY talking about?)
|
| 8 |
+
* 4. Emotional tone detection (happy, sad, angry, excited)
|
| 9 |
+
* 5. Context window (what was just talked about?)
|
| 10 |
+
*/
|
| 11 |
+
|
| 12 |
+
// ────────────────────────────────────────────────────────────────────────────
|
| 13 |
+
// KNOWLEDGE MAPS
|
| 14 |
+
// ────────────────────────────────────────────────────────────────────────────
|
| 15 |
+
|
| 16 |
+
const INTENT_PATTERNS = {
|
| 17 |
+
greeting: [/^(hi|hello|hai|hey|ata ne|kohomada|ayye|subha udesanak|good morning|gm)\b/i],
|
| 18 |
+
farewell: [/\b(bye|goodnight|subha rathriyak|gn|yanawa|later|ttyl|cya)\b/i],
|
| 19 |
+
question: [/\b(mokakda|kohomada|kauda|kawda|monada|koheda|kiia|when|why|how|what|who|where|when|da\?|neda\?|da$)\b/i, /\?/],
|
| 20 |
+
complaint: [/\b(dukei|duk|problem|issue|kapuna|asathutak|baya|adura|roga|hondatama na|hodatma na|barei)\b/i],
|
| 21 |
+
happy: [/\b(supa|ela|elatama|maru|hodai|santhosai|love|like|enjoy|ado hehe|haha|lol|:d|😂|😍|❤️|🔥)\b/i],
|
| 22 |
+
sad: [/\b(duk|sad|cry|neth|balanna bari|tani|ekai|adare na|alone|miss|😢|💔|😞)\b/i],
|
| 23 |
+
angry: [/\b(eka nan|apahu|nonsense|baena|bae|budukarapu|waeda na|kelawera|okama|angry|mad|😠|🤬)\b/i],
|
| 24 |
+
excited: [/\b(!{2,}|omg|aiyoo|wait|seriously|ow ow|neda|😱|🤩|elaaaaaa|supaaa)\b/i],
|
| 25 |
+
love: [/\b(love|adare|miss|baby|bae|heart|❤️|😘|💕|💖|xoxo)\b/i],
|
| 26 |
+
story: [/^(ada|kalin|ekl|eka dawas|meka une|eeka giia|eka thamath|api giia|api gaththee|api hitiye)\b/i],
|
| 27 |
+
thanks: [/\b(thanks|thank you|sthuthi|stuthi|awlak na|np|🙏)\b/i],
|
| 28 |
+
request: [/\b(karanna puluwan|help|support|explain|kiyanna|denna|ganna|hadanna)\b/i],
|
| 29 |
+
selfIntro: [/\b(my name is|mama|mage nama|i am|i'm|iam)\b/i],
|
| 30 |
+
teasing: [/\b(hehe|hihi|🤭|😜|😏|;|patta|wela|joke|haha)\b/i]
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
const TOPIC_KEYWORDS = {
|
| 34 |
+
relationship: ['love', 'adare', 'gf', 'bf', 'girlfriend', 'boyfriend', 'miss', 'marry', 'nikaya', 'prema', 'date', 'kiss'],
|
| 35 |
+
food: ['kema', 'rice', 'food', 'eat', 'restaurant', 'kanne', 'bathi', 'curry', 'pizza', 'kottu', 'hoppers', 'string hoppers'],
|
| 36 |
+
work: ['job', 'weda', 'office', 'project', 'deadline', 'meeting', 'boss', 'salary', 'work', 'weda karanne', 'interview'],
|
| 37 |
+
school: ['school', 'university', 'exam', 'pariksha', 'class', 'lecture', 'teacher', 'student', 'ol', 'al', 'degree'],
|
| 38 |
+
games: ['game', 'play', 'pubg', 'fifa', 'cricket', 'sport', 'win', 'lose', 'match'],
|
| 39 |
+
weather: ['rain', 'vasse', 'hot', 'climate', 'weather', 'udawa', 'ginithapu', 'thawath vasse'],
|
| 40 |
+
transport: ['bus', 'train', 'uber', 'taxi', 'bike', 'car', 'traffic', 'jam', 'yanna', 'enna'],
|
| 41 |
+
money: ['money', 'salary', 'loan', 'borrow', 'spend', 'expensive', 'cheap', 'hari ganan', 'pisa', 'rata'],
|
| 42 |
+
health: ['sick', 'roga', 'hospital', 'doctor', 'medicine', 'adura', 'pain', 'headache', 'fever', 'gedara inne'],
|
| 43 |
+
tech: ['phone', 'laptop', 'computer', 'internet', 'data', 'app', 'code', 'website', 'software', 'hack'],
|
| 44 |
+
family: ['amma', 'thaththa', 'mom', 'dad', 'akka', 'malli', 'nangi', 'brother', 'sister', 'family', 'gedara'],
|
| 45 |
+
social: ['party', 'meet', 'hangout', 'friend', 'machan', 'api', 'mall', 'trip', 'outing', 'picnic'],
|
| 46 |
+
srilanka: ['sl', 'srilanka', 'colombo', 'kandy', 'galle', 'jaffna', 'ratnapura', 'lanka'],
|
| 47 |
+
feelings: ['hithenne', 'feel', 'think', 'baya', 'happy', 'sad', 'cry', 'emotion'],
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
const TOPIC_RESPONSES = {
|
| 51 |
+
relationship: {
|
| 52 |
+
chill: ['ada relationship gana dan hodatama katha karanna epa machan hehe', 'adare gana kiyannako, mokada?', 'ow ow, gf/bf gana da?'],
|
| 53 |
+
sexy: ['ooh relationship gana da? oya kauda lucky one?', 'adare gana oya hithanawa da?'],
|
| 54 |
+
hot: ['adare gana da? oyata special kenek innawada?', 'ooh oya kiyanne relationship gana da?']
|
| 55 |
+
},
|
| 56 |
+
food: {
|
| 57 |
+
chill: ['ada kema supa da?!', 'mama dan godak kapannawa, kottu gana kiyanne?', 'ae machan, mama godak hungry', 'abaa kema kiyannako!'],
|
| 58 |
+
professional: ['Hoda keme irunawada?']
|
| 59 |
+
},
|
| 60 |
+
work: {
|
| 61 |
+
chill: ['aiyo weda wada, poddak relax karaganna machan', 'deadline gana stress wena epa', 'weda wenawata kalin api katha kaapuwa na?'],
|
| 62 |
+
professional: ['Project eka kohomada giya? Help ona nam kiyanna.']
|
| 63 |
+
},
|
| 64 |
+
school: {
|
| 65 |
+
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'],
|
| 66 |
+
professional: ['Study karala hodatama exams passweida?']
|
| 67 |
+
},
|
| 68 |
+
games: {
|
| 69 |
+
chill: ['ado PUBG da?! mama oka ban khelannawa', 'game gana kiyannako, api multiplayer kaamu', 'cricket match baluwada? maru giia na?'],
|
| 70 |
+
hot: ['game karala jadi wunasama prize karannako chat eka']
|
| 71 |
+
},
|
| 72 |
+
weather: {
|
| 73 |
+
chill: ['ada vasse da koheda?', 'aiyo ginithapu, AC eka dala inne', 'ae machan vasse aiye gedara hitapu', 'Colombo wala ginithapu, koheda oya?'],
|
| 74 |
+
},
|
| 75 |
+
transport: {
|
| 76 |
+
chill: ['bus eke traffic jam da? ae ae normal', 'Uber gahanna, bus pain wena epaa machan'],
|
| 77 |
+
},
|
| 78 |
+
money: {
|
| 79 |
+
chill: ['aiyo machan pisa gana duku', 'salary eka waidi wenna one', 'mae ithin savings poddak', 'hari ganan aney life'],
|
| 80 |
+
},
|
| 81 |
+
health: {
|
| 82 |
+
chill: ['aiyo machan! hondatama hoddaganna', 'roga nathnam santhosai', 'hospital giiewda? kohomada?', 'adura na?! gedara hitapan'],
|
| 83 |
+
},
|
| 84 |
+
tech: {
|
| 85 |
+
chill: ['tech gana naththam mama kiyannawa machan', 'code gahala trouble da? kiyanna', 'phone da laptop da?'],
|
| 86 |
+
professional: ['Tech stack eka mokakda use karanne?']
|
| 87 |
+
},
|
| 88 |
+
family: {
|
| 89 |
+
chill: ['family gana hari supa machan', 'amma thaththa kohomada?', 'gedara gihilla aawada?'],
|
| 90 |
+
},
|
| 91 |
+
social: {
|
| 92 |
+
chill: ['outing gamu! koheda?', 'party ekak?! enna enna', 'machan ahala hitiye api meet weida'],
|
| 93 |
+
hot: ['outing gamu, just ape dokomath']
|
| 94 |
+
},
|
| 95 |
+
srilanka: {
|
| 96 |
+
chill: ['mama SL eke podi balla', 'SL game elatama', 'koheda inne SL wala?'],
|
| 97 |
+
},
|
| 98 |
+
feelings: {
|
| 99 |
+
chill: ['mokakda hithenne? kiyanna machan', 'awl na ado, mama innawa ne?', 'mama thiyanawa obage kotaswa'],
|
| 100 |
+
sexy: ['kiyanna aney, mama ahannawa', 'oya feel wenne mama danne na ne?'],
|
| 101 |
+
}
|
| 102 |
+
};
|
| 103 |
+
|
| 104 |
+
// ────────────────────────────────────────────────────────────────────────────
|
| 105 |
+
// AUTO-STYLE SIGNAL MAPS
|
| 106 |
+
// ────────────────────────────────────────────────────────────────────────────
|
| 107 |
+
|
| 108 |
+
// Each style has signal words/patterns. Weighted scoring picks the winner.
|
| 109 |
+
const STYLE_SIGNALS = {
|
| 110 |
+
professional: {
|
| 111 |
+
weight: 1,
|
| 112 |
+
patterns: [
|
| 113 |
+
/\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
|
| 114 |
+
]
|
| 115 |
+
},
|
| 116 |
+
sexy: {
|
| 117 |
+
weight: 1,
|
| 118 |
+
patterns: [
|
| 119 |
+
/\b(baby|babe|honey|cutie|beautiful|gorgeous|hot|sexy|miss you|come over|alone|cuddle|kiss|hug|adore|😍|😘|💋|🥵|😏|😉|flirt|date|special|crush)\b/i
|
| 120 |
+
]
|
| 121 |
+
},
|
| 122 |
+
hot: {
|
| 123 |
+
weight: 1,
|
| 124 |
+
patterns: [
|
| 125 |
+
/\b(!!|come|enna|langata|tonight|now|asap|jld|fast|quick|urgent|secret|naughty|dark|🔥|😈|🥵|💦|need you|want you|right now)\b/i,
|
| 126 |
+
/!{2,}/
|
| 127 |
+
]
|
| 128 |
+
},
|
| 129 |
+
chill: {
|
| 130 |
+
weight: 0.5, // default — lower weight so it loses to specific signals
|
| 131 |
+
patterns: [
|
| 132 |
+
/\b(machan|ado|ayye|poddak|nikang|hehe|lol|supa|ela|maru|chilling|relax|nothing|nothing much|nikan)\b/i
|
| 133 |
+
]
|
| 134 |
+
}
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
// ────────────────────────────────────────────────────────────────────────────
|
| 138 |
+
// NLU CLASS
|
| 139 |
+
// ────────────────────────────────────────────────────────────────────────────
|
| 140 |
+
|
| 141 |
+
class NLU {
|
| 142 |
+
|
| 143 |
+
/**
|
| 144 |
+
* Segment a long paragraph into individual thought-sentences
|
| 145 |
+
* Splits on: . ! ? newlines, and Sinhala sentence enders
|
| 146 |
+
*/
|
| 147 |
+
segment(text) {
|
| 148 |
+
// Split on sentence terminators
|
| 149 |
+
const raw = text.split(/[.!?\n\r।ෙ]+/).map(s => s.trim()).filter(s => s.length > 1);
|
| 150 |
+
// Also split on conjunctions that indicate a change of subject
|
| 151 |
+
const sentences = [];
|
| 152 |
+
for (const s of raw) {
|
| 153 |
+
// If sentence has comma + subject change indicator, further split
|
| 154 |
+
const subParts = s.split(/\s*,\s*(?=(?:mama|oya|api|ekka|eka|dan|kalin|ekath|ado|machan)\b)/i);
|
| 155 |
+
for (const p of subParts) {
|
| 156 |
+
if (p.trim().length > 1) sentences.push(p.trim());
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
return sentences.length ? sentences : [text.trim()];
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
/**
|
| 163 |
+
* Detect intents from a sentence
|
| 164 |
+
* @returns {string[]} list of matched intents
|
| 165 |
+
*/
|
| 166 |
+
detectIntents(text) {
|
| 167 |
+
const found = [];
|
| 168 |
+
for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) {
|
| 169 |
+
for (const pat of patterns) {
|
| 170 |
+
if (pat.test(text)) { found.push(intent); break; }
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
return found.length ? found : ['unknown'];
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/**
|
| 177 |
+
* Extract topics from text
|
| 178 |
+
* @returns {string[]} matched topic names
|
| 179 |
+
*/
|
| 180 |
+
extractTopics(text) {
|
| 181 |
+
const lower = text.toLowerCase();
|
| 182 |
+
const found = [];
|
| 183 |
+
for (const [topic, keywords] of Object.entries(TOPIC_KEYWORDS)) {
|
| 184 |
+
if (keywords.some(kw => lower.includes(kw))) found.push(topic);
|
| 185 |
+
}
|
| 186 |
+
return found;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
/**
|
| 190 |
+
* Get a topic-based response
|
| 191 |
+
*/
|
| 192 |
+
getTopicResponse(topic, style = 'chill') {
|
| 193 |
+
const bank = TOPIC_RESPONSES[topic];
|
| 194 |
+
if (!bank) return null;
|
| 195 |
+
const pool = bank[style] || bank['chill'] || null;
|
| 196 |
+
if (!pool || !pool.length) return null;
|
| 197 |
+
return pool[Math.floor(Math.random() * pool.length)];
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/**
|
| 201 |
+
* Detect the emotional tone of a message
|
| 202 |
+
*/
|
| 203 |
+
detectTone(text) {
|
| 204 |
+
if (INTENT_PATTERNS.happy.some(p => p.test(text))) return 'happy';
|
| 205 |
+
if (INTENT_PATTERNS.sad.some(p => p.test(text))) return 'sad';
|
| 206 |
+
if (INTENT_PATTERNS.angry.some(p => p.test(text))) return 'angry';
|
| 207 |
+
if (INTENT_PATTERNS.excited.some(p => p.test(text))) return 'excited';
|
| 208 |
+
if (INTENT_PATTERNS.love.some(p => p.test(text))) return 'love';
|
| 209 |
+
return 'neutral';
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
/**
|
| 213 |
+
* Generate a tone-aware reaction prefix
|
| 214 |
+
*/
|
| 215 |
+
toneReaction(tone) {
|
| 216 |
+
const reactions = {
|
| 217 |
+
happy: ['supa aney!', 'elatama!', 'maru!', 'hodai!'],
|
| 218 |
+
sad: ['aiyo duka aney...', 'awl na machan...', 'duk gannaka eppa ne?'],
|
| 219 |
+
angry: ['ok ok, chill down machan', 'relax ado...', 'aiyo mokada wune?'],
|
| 220 |
+
excited: ['ado!', 'wait wait!', 'seriously?!'],
|
| 221 |
+
love: ['aww hehe', 'ooh ooh', '😊'],
|
| 222 |
+
neutral: []
|
| 223 |
+
};
|
| 224 |
+
const pool = reactions[tone] || [];
|
| 225 |
+
return pool.length ? pool[Math.floor(Math.random() * pool.length)] : '';
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
/**
|
| 229 |
+
* Auto-detect the best response style based on message content.
|
| 230 |
+
* Scores each style by how many signals match, returns the top scorer.
|
| 231 |
+
* @param {string} text
|
| 232 |
+
* @returns {string} style name
|
| 233 |
+
*/
|
| 234 |
+
detectAutoStyle(text) {
|
| 235 |
+
const scores = {};
|
| 236 |
+
for (const [style, config] of Object.entries(STYLE_SIGNALS)) {
|
| 237 |
+
let score = 0;
|
| 238 |
+
for (const pat of config.patterns) {
|
| 239 |
+
const matches = text.match(pat);
|
| 240 |
+
if (matches) score += matches.length * config.weight;
|
| 241 |
+
}
|
| 242 |
+
scores[style] = score;
|
| 243 |
+
}
|
| 244 |
+
// Return style with highest score; default to chill
|
| 245 |
+
const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0];
|
| 246 |
+
return (best && best[1] > 0) ? best[0] : 'chill';
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
/**
|
| 250 |
+
* Full analysis of any message (short or long paragraph)
|
| 251 |
+
* Returns a structured analysis object
|
| 252 |
+
*/
|
| 253 |
+
analyze(text) {
|
| 254 |
+
const sentences = this.segment(text);
|
| 255 |
+
const intents = new Set();
|
| 256 |
+
const topics = new Set();
|
| 257 |
+
const tone = this.detectTone(text);
|
| 258 |
+
|
| 259 |
+
for (const s of sentences) {
|
| 260 |
+
this.detectIntents(s).forEach(i => intents.add(i));
|
| 261 |
+
this.extractTopics(s).forEach(t => topics.add(t));
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
return {
|
| 265 |
+
original: text,
|
| 266 |
+
sentences,
|
| 267 |
+
intents: [...intents],
|
| 268 |
+
topics: [...topics],
|
| 269 |
+
tone,
|
| 270 |
+
detectedStyle: this.detectAutoStyle(text),
|
| 271 |
+
isLong: sentences.length > 1,
|
| 272 |
+
isQuestion: intents.has('question'),
|
| 273 |
+
isGreeting: intents.has('greeting'),
|
| 274 |
+
isFarewell: intents.has('farewell')
|
| 275 |
+
};
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
module.exports = new NLU();
|
QueenNoxi/brain/src/processor.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
|
| 4 |
+
class LanguageProcessor {
|
| 5 |
+
constructor() {
|
| 6 |
+
this.slang = {};
|
| 7 |
+
this.phoneticMap = {}; // Custom word mappings (sinhala script -> singlish)
|
| 8 |
+
this.loadSlang();
|
| 9 |
+
this.loadPhoneticMap();
|
| 10 |
+
this.initTransliteration();
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
loadSlang() {
|
| 14 |
+
try {
|
| 15 |
+
const slangPath = path.join(__dirname, '..', 'data', 'slang.json');
|
| 16 |
+
if (fs.existsSync(slangPath)) {
|
| 17 |
+
this.slang = JSON.parse(fs.readFileSync(slangPath, 'utf8'));
|
| 18 |
+
}
|
| 19 |
+
} catch (e) { }
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
loadPhoneticMap() {
|
| 23 |
+
try {
|
| 24 |
+
const mapPath = path.join(__dirname, '..', 'data', 'phonetic_map.json');
|
| 25 |
+
if (fs.existsSync(mapPath)) {
|
| 26 |
+
this.phoneticMap = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
| 27 |
+
}
|
| 28 |
+
} catch (e) { }
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
savePhoneticMap() {
|
| 32 |
+
try {
|
| 33 |
+
const mapPath = path.join(__dirname, '..', 'data', 'phonetic_map.json');
|
| 34 |
+
const dir = path.dirname(mapPath);
|
| 35 |
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
| 36 |
+
fs.writeFileSync(mapPath, JSON.stringify(this.phoneticMap, null, 2), 'utf8');
|
| 37 |
+
} catch (e) {
|
| 38 |
+
console.error('Error saving phonetic map:', e);
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
initTransliteration() {
|
| 43 |
+
this.vowels = {
|
| 44 |
+
'අ': 'a', 'ආ': 'aa', 'ඇ': 'ae', 'ඈ': 'aee', 'ඉ': 'i', 'ඊ': 'ii',
|
| 45 |
+
'උ': 'u', 'ඌ': 'uu', 'එ': 'e', 'ඒ': 'ee', 'ඔ': 'o', 'ඕ': 'oo',
|
| 46 |
+
'ඓ': 'ai', 'ඖ': 'au'
|
| 47 |
+
};
|
| 48 |
+
this.consonants = {
|
| 49 |
+
'ක': 'k', 'ඛ': 'kh', 'ග': 'g', 'ඝ': 'gh', 'ඞ': 'ng', 'ඟ': 'ng',
|
| 50 |
+
'ච': 'c', 'ඡ': 'ch', 'ජ': 'j', 'ඣ': 'jh', 'ඤ': 'ny', 'ඥ': 'gn',
|
| 51 |
+
'ට': 't', 'ඨ': 'th', 'ඩ': 'd', 'ඪ': 'dh', 'ණ': 'n', 'ඬ': 'nd',
|
| 52 |
+
'ත': 't', 'ථ': 'th', 'ද': 'd', 'ධ': 'dh', 'න': 'n', 'ඳ': 'nd',
|
| 53 |
+
'ප': 'p', 'ඵ': 'ph', 'බ': 'b', 'භ': 'bh', 'ම': 'm', 'ඹ': 'mb',
|
| 54 |
+
'ය': 'y', 'ර': 'r', 'ල': 'l', 'ව': 'v', 'ශ': 'sh', 'ෂ': 'sh',
|
| 55 |
+
'ස': 's', 'හ': 'h', 'ළ': 'l', 'ෆ': 'f'
|
| 56 |
+
};
|
| 57 |
+
this.pilli = {
|
| 58 |
+
'්': '', 'ා': 'aa', 'ැ': 'ae', 'ෑ': 'aee', 'ි': 'i', 'ී': 'ii',
|
| 59 |
+
'ු': 'u', 'ූ': 'uu', 'ෘ': 'ru', 'ෙ': 'e', 'ේ': 'ee', 'ෛ': 'ai',
|
| 60 |
+
'ො': 'o', 'ෝ': 'oo', 'ෞ': 'au', 'ං': 'n'
|
| 61 |
+
};
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
sinhalaToSinglish(text) {
|
| 65 |
+
let result = '';
|
| 66 |
+
for (let i = 0; i < text.length; i++) {
|
| 67 |
+
const char = text[i];
|
| 68 |
+
|
| 69 |
+
if (this.vowels[char]) {
|
| 70 |
+
result += this.vowels[char];
|
| 71 |
+
} else if (this.consonants[char]) {
|
| 72 |
+
let next = text[i + 1];
|
| 73 |
+
let singlish = this.consonants[char];
|
| 74 |
+
if (next && this.pilli[next] !== undefined) {
|
| 75 |
+
singlish += this.pilli[next];
|
| 76 |
+
i++; // skip pilla
|
| 77 |
+
} else {
|
| 78 |
+
singlish += 'a'; // inherent vowel
|
| 79 |
+
}
|
| 80 |
+
result += singlish;
|
| 81 |
+
} else {
|
| 82 |
+
result += char;
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
return result;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/**
|
| 89 |
+
* Normalizes text for matching.
|
| 90 |
+
* Handles mixed Sinhala/Singlish, slang expansion, and phonetic cleanup.
|
| 91 |
+
*/
|
| 92 |
+
process(text) {
|
| 93 |
+
if (!text) return '';
|
| 94 |
+
|
| 95 |
+
// 1. Convert Sinhala script to Singlish (using map if available)
|
| 96 |
+
const words = text.split(/\s+/);
|
| 97 |
+
const processedWords = words.map(word => {
|
| 98 |
+
// Handle phonetic map (Sinhala -> Singlish)
|
| 99 |
+
if (this.phoneticMap[word]) return this.phoneticMap[word];
|
| 100 |
+
|
| 101 |
+
// Handle rule-based translit
|
| 102 |
+
return this.sinhalaToSinglish(word);
|
| 103 |
+
});
|
| 104 |
+
|
| 105 |
+
let processed = processedWords.join(' ').toLowerCase().trim();
|
| 106 |
+
|
| 107 |
+
// 2. Expand common slang (Singlish -> Real Concept)
|
| 108 |
+
const finalWords = processed.split(/\s+/);
|
| 109 |
+
processed = finalWords.map(word => this.slang[word] || word).join(' ');
|
| 110 |
+
|
| 111 |
+
// 3. Simple phonetic normalization
|
| 112 |
+
processed = this.normalizePhonetics(processed);
|
| 113 |
+
|
| 114 |
+
return processed;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
normalizePhonetics(text) {
|
| 118 |
+
return text
|
| 119 |
+
.replace(/sh/g, 's')
|
| 120 |
+
.replace(/th/g, 't')
|
| 121 |
+
.replace(/dh/g, 'd')
|
| 122 |
+
.replace(/aa/g, 'a')
|
| 123 |
+
.replace(/ee/g, 'i')
|
| 124 |
+
.replace(/oo/g, 'u')
|
| 125 |
+
.replace(/y/g, 'i')
|
| 126 |
+
.replace(/w/g, 'v')
|
| 127 |
+
.replace(/([a-z])\1+/g, '$1') // remove double letters
|
| 128 |
+
.replace(/[^a-z0-9\s]/g, ''); // remove non-alphanumeric
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/**
|
| 132 |
+
* Calculates similarity between two strings (Levenshtein Distance)
|
| 133 |
+
*/
|
| 134 |
+
calculateSimilarity(s1, s2) {
|
| 135 |
+
if (s1 === s2) return 1.0;
|
| 136 |
+
|
| 137 |
+
const len1 = s1.length;
|
| 138 |
+
const len2 = s2.length;
|
| 139 |
+
if (len1 === 0) return 0.0;
|
| 140 |
+
if (len2 === 0) return 0.0;
|
| 141 |
+
|
| 142 |
+
const matrix = [];
|
| 143 |
+
for (let i = 0; i <= len1; i++) matrix[i] = [i];
|
| 144 |
+
for (let j = 0; j <= len2; j++) matrix[0][j] = j;
|
| 145 |
+
|
| 146 |
+
for (let i = 1; i <= len1; i++) {
|
| 147 |
+
for (let j = 1; j <= len2; j++) {
|
| 148 |
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
| 149 |
+
matrix[i][j] = Math.min(
|
| 150 |
+
matrix[i - 1][j] + 1,
|
| 151 |
+
matrix[i][j - 1] + 1,
|
| 152 |
+
matrix[i - 1][j - 1] + cost
|
| 153 |
+
);
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
const distance = matrix[len1][len2];
|
| 158 |
+
return 1.0 - (distance / Math.max(len1, len2));
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
module.exports = new LanguageProcessor();
|
QueenNoxi/brain/src/storage.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* StorageAdapter provides a unified interface for local JSON and MongoDB.
|
| 6 |
+
* For local 'users' context, it saves individual files per user.
|
| 7 |
+
*/
|
| 8 |
+
class StorageAdapter {
|
| 9 |
+
/**
|
| 10 |
+
* @param {string} connectionStr - Local path (file or dir) or MongoDB URI
|
| 11 |
+
* @param {string} context - 'users' | 'brain' | 'common'
|
| 12 |
+
*/
|
| 13 |
+
constructor(connectionStr, context = 'common') {
|
| 14 |
+
this.connectionStr = connectionStr;
|
| 15 |
+
this.context = context;
|
| 16 |
+
this.isMongo = connectionStr.startsWith('mongodb');
|
| 17 |
+
this.client = null;
|
| 18 |
+
this.db = null;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
async init() {
|
| 22 |
+
if (this.isMongo) {
|
| 23 |
+
try {
|
| 24 |
+
const { MongoClient } = require('mongodb');
|
| 25 |
+
this.client = new MongoClient(this.connectionStr);
|
| 26 |
+
await this.client.connect();
|
| 27 |
+
this.db = this.client.db();
|
| 28 |
+
console.log(`📡 Storage: Connected to MongoDB [${this.context}]`);
|
| 29 |
+
} catch (e) {
|
| 30 |
+
console.error(`❌ Storage: Failed to connect to MongoDB. Falling back to local storage.`);
|
| 31 |
+
this.isMongo = false;
|
| 32 |
+
}
|
| 33 |
+
} else if (this.context === 'users') {
|
| 34 |
+
// Local Users should use a directory
|
| 35 |
+
if (!fs.existsSync(this.connectionStr)) {
|
| 36 |
+
fs.mkdirSync(this.connectionStr, { recursive: true });
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
return this;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/**
|
| 43 |
+
* Load all data for this context
|
| 44 |
+
*/
|
| 45 |
+
async loadAll() {
|
| 46 |
+
if (this.isMongo && this.db) {
|
| 47 |
+
const collection = this.db.collection(this.context);
|
| 48 |
+
if (this.context === 'users') {
|
| 49 |
+
const results = await collection.find({}).toArray();
|
| 50 |
+
const out = {};
|
| 51 |
+
results.forEach(r => {
|
| 52 |
+
const { _id, ...data } = r;
|
| 53 |
+
out[_id] = data;
|
| 54 |
+
});
|
| 55 |
+
return out;
|
| 56 |
+
} else {
|
| 57 |
+
const doc = await collection.findOne({ _id: 'master' });
|
| 58 |
+
return doc ? doc.data : {};
|
| 59 |
+
}
|
| 60 |
+
} else {
|
| 61 |
+
// Local FS
|
| 62 |
+
if (this.context === 'users') {
|
| 63 |
+
const dir = this.connectionStr;
|
| 64 |
+
if (!fs.existsSync(dir)) return {};
|
| 65 |
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
| 66 |
+
const out = {};
|
| 67 |
+
files.forEach(f => {
|
| 68 |
+
try {
|
| 69 |
+
const content = fs.readFileSync(path.join(dir, f), 'utf8');
|
| 70 |
+
out[f.replace('.json', '')] = JSON.parse(content);
|
| 71 |
+
} catch (e) { }
|
| 72 |
+
});
|
| 73 |
+
return out;
|
| 74 |
+
} else {
|
| 75 |
+
try {
|
| 76 |
+
if (fs.existsSync(this.connectionStr)) {
|
| 77 |
+
return JSON.parse(fs.readFileSync(this.connectionStr, 'utf8'));
|
| 78 |
+
}
|
| 79 |
+
} catch (e) { }
|
| 80 |
+
return {};
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* Save all data (Bulk)
|
| 87 |
+
*/
|
| 88 |
+
async saveAll(data) {
|
| 89 |
+
if (this.isMongo && this.db) {
|
| 90 |
+
const collection = this.db.collection(this.context);
|
| 91 |
+
if (this.context === 'users') {
|
| 92 |
+
for (const userId in data) {
|
| 93 |
+
await collection.updateOne({ _id: userId }, { $set: data[userId] }, { upsert: true });
|
| 94 |
+
}
|
| 95 |
+
} else {
|
| 96 |
+
await collection.updateOne({ _id: 'master' }, { $set: { data, updatedAt: Date.now() } }, { upsert: true });
|
| 97 |
+
}
|
| 98 |
+
} else {
|
| 99 |
+
if (this.context === 'users') {
|
| 100 |
+
const dir = this.connectionStr;
|
| 101 |
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
| 102 |
+
for (const userId in data) {
|
| 103 |
+
const filePath = path.join(dir, `${userId}.json`);
|
| 104 |
+
fs.writeFileSync(filePath, JSON.stringify(data[userId], null, 2), 'utf8');
|
| 105 |
+
}
|
| 106 |
+
} else {
|
| 107 |
+
const dir = path.dirname(this.connectionStr);
|
| 108 |
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
| 109 |
+
fs.writeFileSync(this.connectionStr, JSON.stringify(data, null, 2), 'utf8');
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/**
|
| 115 |
+
* Save a single item (highly optimized for users context)
|
| 116 |
+
*/
|
| 117 |
+
async saveItem(id, itemData) {
|
| 118 |
+
if (this.isMongo && this.db) {
|
| 119 |
+
const collection = this.db.collection(this.context);
|
| 120 |
+
await collection.updateOne({ _id: id }, { $set: itemData }, { upsert: true });
|
| 121 |
+
} else {
|
| 122 |
+
if (this.context === 'users') {
|
| 123 |
+
const dir = this.connectionStr;
|
| 124 |
+
const filePath = path.join(dir, `${id}.json`);
|
| 125 |
+
fs.writeFileSync(filePath, JSON.stringify(itemData, null, 2), 'utf8');
|
| 126 |
+
} else {
|
| 127 |
+
const all = await this.loadAll();
|
| 128 |
+
all[id] = itemData;
|
| 129 |
+
await this.saveAll(all);
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
module.exports = StorageAdapter;
|
QueenNoxi/brain/src/trainer.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
const readline = require('readline');
|
| 4 |
+
const Brain = require('./brain');
|
| 5 |
+
|
| 6 |
+
/**
|
| 7 |
+
* Universal training tool for Sinhala dataset ingestion.
|
| 8 |
+
* Supports JSON, TSV, CSV, Parallel Text, and Large-Scale Streaming.
|
| 9 |
+
*/
|
| 10 |
+
class Trainer {
|
| 11 |
+
/**
|
| 12 |
+
* Train from an array of objects
|
| 13 |
+
*/
|
| 14 |
+
static trainData(data, defaultStyle = 'chill') {
|
| 15 |
+
if (!Array.isArray(data)) return 0;
|
| 16 |
+
const oldStyle = (Brain.persona && Brain.persona.style) || 'chill';
|
| 17 |
+
let count = 0;
|
| 18 |
+
|
| 19 |
+
console.log(`🧠 Processing ${data.length} patterns...`);
|
| 20 |
+
|
| 21 |
+
for (const item of data) {
|
| 22 |
+
const input = item.input || item.q || item.question || item.text || item.Phrase;
|
| 23 |
+
const response = item.response || item.a || item.answer || item.reply || item.Response;
|
| 24 |
+
const style = item.style || defaultStyle;
|
| 25 |
+
|
| 26 |
+
if (input && response) {
|
| 27 |
+
const s = ['chill', 'professional', 'sexy', 'hot'].includes(style) ? style : defaultStyle;
|
| 28 |
+
Brain.setPersona({ style: s });
|
| 29 |
+
// Bulk training: disable auto-save
|
| 30 |
+
Brain.learn(input.toString(), response.toString(), false);
|
| 31 |
+
count++;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
if (count > 0 && count % 10000 === 0) {
|
| 35 |
+
console.log(`...ingested ${count} patterns`);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Final Save for bulk data
|
| 40 |
+
console.log('💾 Saving brain to disk (this may take a moment)...');
|
| 41 |
+
Brain.saveData();
|
| 42 |
+
|
| 43 |
+
Brain.setPersona({ style: oldStyle });
|
| 44 |
+
return count;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* Train from a raw string
|
| 49 |
+
*/
|
| 50 |
+
static trainString(content, format = 'json', defaultStyle = 'chill') {
|
| 51 |
+
const fmt = format.toLowerCase().replace('.', '');
|
| 52 |
+
if (fmt === 'json') {
|
| 53 |
+
try { return this.trainData(JSON.parse(content), defaultStyle); }
|
| 54 |
+
catch (e) { return 0; }
|
| 55 |
+
} else if (fmt === 'tsv' || fmt === 'csv' || fmt === 'txt') {
|
| 56 |
+
const lines = content.split(/\r?\n/);
|
| 57 |
+
let count = 0;
|
| 58 |
+
const delimiter = (fmt === 'tsv' || fmt === 'txt') ? /\t+/ : ',';
|
| 59 |
+
const data = [];
|
| 60 |
+
|
| 61 |
+
for (const line of lines) {
|
| 62 |
+
if (!line.trim()) continue;
|
| 63 |
+
const columns = line.split(delimiter);
|
| 64 |
+
if (columns.length >= 2) {
|
| 65 |
+
const col1 = columns[0].trim().replace(/^"|"$/g, '');
|
| 66 |
+
const col2 = columns[1].trim().replace(/^"|"$/g, '');
|
| 67 |
+
if (col1.toLowerCase() === 'question' || col1.toLowerCase() === 'phrase' || col1.toLowerCase() === 'singlish') continue;
|
| 68 |
+
data.push({ q: col1, a: col2, style: defaultStyle });
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
return this.trainData(data, defaultStyle);
|
| 72 |
+
}
|
| 73 |
+
return 0;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/**
|
| 77 |
+
* Streaming Trainer for LARGE files (100MB+)
|
| 78 |
+
* Supports Tab, Comma, and custom WSD format.
|
| 79 |
+
*/
|
| 80 |
+
static async trainLarge(filePath, defaultStyle = 'chill') {
|
| 81 |
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
|
| 82 |
+
if (!fs.existsSync(absPath)) return 0;
|
| 83 |
+
|
| 84 |
+
// Detect encoding
|
| 85 |
+
const buffer = fs.readFileSync(absPath, { length: 500 });
|
| 86 |
+
let encoding = 'utf8';
|
| 87 |
+
if (buffer[0] === 0xFF && buffer[1] === 0xFE) encoding = 'utf16le';
|
| 88 |
+
else if (buffer[0] === 0xFE && buffer[1] === 0xFF) encoding = 'utf16be';
|
| 89 |
+
else if (buffer.some(b => b === 0)) encoding = 'utf16le';
|
| 90 |
+
|
| 91 |
+
const processor = require('./processor');
|
| 92 |
+
const fileStream = fs.createReadStream(absPath, { encoding });
|
| 93 |
+
|
| 94 |
+
const rl = readline.createInterface({
|
| 95 |
+
input: fileStream,
|
| 96 |
+
crlfDelay: Infinity
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
console.log(`🚀 Streaming training from ${path.basename(filePath)} (${encoding})...`);
|
| 100 |
+
let count = 0;
|
| 101 |
+
|
| 102 |
+
for await (const line of rl) {
|
| 103 |
+
if (!line.trim()) continue;
|
| 104 |
+
|
| 105 |
+
let sinhala, singlish;
|
| 106 |
+
|
| 107 |
+
// Pattern 1: WSD Format
|
| 108 |
+
const wsdMatch = line.match(/^Word:\s*([^,]+),\s*Sinhala\s*Words:\s*\[(.*)\]/i);
|
| 109 |
+
if (wsdMatch) {
|
| 110 |
+
singlish = wsdMatch[1].trim();
|
| 111 |
+
const variants = wsdMatch[2].split(',').map(v => v.trim().replace(/['"\[\]]/g, ''));
|
| 112 |
+
variants.forEach(v => {
|
| 113 |
+
if (v && /[\u0D80-\u0DFF]/.test(v)) {
|
| 114 |
+
processor.phoneticMap[v] = singlish;
|
| 115 |
+
count++;
|
| 116 |
+
}
|
| 117 |
+
});
|
| 118 |
+
continue;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
// Pattern 2: Swa Bhasha / test1
|
| 122 |
+
const parts = line.split(/[\t,/]/);
|
| 123 |
+
if (parts.length >= 2) {
|
| 124 |
+
const p1 = parts[0].trim().replace(/^"|"$/g, '');
|
| 125 |
+
const p2 = parts[1].trim().replace(/^"|"$/g, '');
|
| 126 |
+
|
| 127 |
+
const isP1S = /[\u0D80-\u0DFF]/.test(p1);
|
| 128 |
+
const isP2S = /[\u0D80-\u0DFF]/.test(p2);
|
| 129 |
+
|
| 130 |
+
if (isP1S && !isP2S) { sinhala = p1; singlish = p2; }
|
| 131 |
+
else if (!isP1S && isP2S) { sinhala = p2; singlish = p1; }
|
| 132 |
+
else if (!isP1S && !isP2S) { Brain.learn(p1, p2, false); count++; }
|
| 133 |
+
|
| 134 |
+
if (sinhala && singlish) {
|
| 135 |
+
processor.phoneticMap[sinhala] = singlish;
|
| 136 |
+
count++;
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
if (count > 0 && count % 50000 === 0) {
|
| 141 |
+
console.log(`...memorized ${count} phonetic patterns`);
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
processor.savePhoneticMap();
|
| 146 |
+
Brain.saveData(); // Save brain after large stream as well
|
| 147 |
+
return count;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
/**
|
| 151 |
+
* Legacy File Trainer
|
| 152 |
+
*/
|
| 153 |
+
static trainFile(filePath, defaultStyle = 'chill') {
|
| 154 |
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
|
| 155 |
+
if (!fs.existsSync(absPath)) return 0;
|
| 156 |
+
const buffer = fs.readFileSync(absPath);
|
| 157 |
+
let content;
|
| 158 |
+
if (buffer[0] === 0xFF && buffer[1] === 0xFE) content = buffer.toString('utf16le');
|
| 159 |
+
else if (buffer[0] === 0xFE && buffer[1] === 0xFF) content = buffer.toString('utf16be');
|
| 160 |
+
else {
|
| 161 |
+
const isUtf16 = buffer.slice(0, 500).some(b => b === 0);
|
| 162 |
+
content = isUtf16 ? buffer.toString('utf16le') : buffer.toString('utf8');
|
| 163 |
+
}
|
| 164 |
+
return this.trainString(content, path.extname(absPath).replace('.', ''), defaultStyle);
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
module.exports = Trainer;
|
QueenNoxi/config.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def _int_list(env_key: str, default: str = "") -> list:
|
| 5 |
+
"""Parse a space-separated list of integers from an env var, ignoring comments."""
|
| 6 |
+
raw = os.environ.get(env_key, default).strip()
|
| 7 |
+
if not raw:
|
| 8 |
+
return []
|
| 9 |
+
# Remove comments starting with #
|
| 10 |
+
raw = raw.split("#")[0].strip()
|
| 11 |
+
if not raw:
|
| 12 |
+
return []
|
| 13 |
+
|
| 14 |
+
result = []
|
| 15 |
+
for x in raw.split():
|
| 16 |
+
try:
|
| 17 |
+
result.append(int(x))
|
| 18 |
+
except ValueError:
|
| 19 |
+
continue
|
| 20 |
+
return result
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _bool(env_key: str, default: bool = False) -> bool:
|
| 24 |
+
val = os.environ.get(env_key, str(default)).strip().lower()
|
| 25 |
+
return val in ("1", "true", "yes")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _str_list(env_key: str, default: str = "") -> list:
|
| 29 |
+
"""Parse a space-separated list of strings from an env var, ignoring comments."""
|
| 30 |
+
raw = os.environ.get(env_key, default).strip()
|
| 31 |
+
if not raw:
|
| 32 |
+
return []
|
| 33 |
+
# Remove comments starting with #
|
| 34 |
+
raw = raw.split("#")[0].strip()
|
| 35 |
+
if not raw:
|
| 36 |
+
return []
|
| 37 |
+
return raw.split()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class Config:
|
| 41 |
+
LOGGER = True
|
| 42 |
+
|
| 43 |
+
# ── Telegram API credentials ──────────────────────────────────────────
|
| 44 |
+
API_ID = int(os.environ.get("API_ID", 0)) or None
|
| 45 |
+
API_HASH = os.environ.get("API_HASH", "")
|
| 46 |
+
TOKEN = os.environ.get("TOKEN", "")
|
| 47 |
+
|
| 48 |
+
# ── Owner / Privileged users ──────────────────────────────────────────
|
| 49 |
+
# OWNER_IDS: space-separated Telegram user IDs (all will have owner access)
|
| 50 |
+
# Example: OWNER_IDS="123456 789012"
|
| 51 |
+
OWNER_IDS = _int_list("OWNER_IDS")
|
| 52 |
+
|
| 53 |
+
# Sudo / dragons (space-separated IDs)
|
| 54 |
+
DRAGONS = _int_list("DRAGONS")
|
| 55 |
+
DEV_USERS = _int_list("DEV_USERS", "2145093972")
|
| 56 |
+
DEMONS = _int_list("DEMONS")
|
| 57 |
+
TIGERS = _int_list("TIGERS")
|
| 58 |
+
WOLVES = _int_list("WOLVES")
|
| 59 |
+
|
| 60 |
+
# ── Bot identity ──────────────────────────────────────────────────────
|
| 61 |
+
# Set these explicitly on HuggingFace (fetched from Telegram API if blank)
|
| 62 |
+
BOT_NAME = os.environ.get("BOT_NAME", "QueenNoxi")
|
| 63 |
+
BOT_USERNAME = os.environ.get("BOT_USERNAME", "") # without @
|
| 64 |
+
|
| 65 |
+
# ── Chats / channels ──────────────────────────────────────────────────
|
| 66 |
+
SUPPORT_CHAT = os.environ.get("SUPPORT_CHAT", "QueenNoxiSupport") # without @
|
| 67 |
+
EVENT_LOGS = os.environ.get("EVENT_LOGS", "") # chat ID for log channel
|
| 68 |
+
BL_CHATS = _int_list("BL_CHATS") # blacklisted chat IDs
|
| 69 |
+
|
| 70 |
+
# ── Media ─────────────────────────────────────────────────────────────
|
| 71 |
+
START_IMG = os.environ.get("START_IMG", "")
|
| 72 |
+
|
| 73 |
+
# ── Databases ─────────────────────────────────────────────────────────
|
| 74 |
+
MONGO_DB_URI = os.environ.get("MONGO_DB_URI", "")
|
| 75 |
+
CHATDB_URL = os.environ.get("CHATDB_URL", "")
|
| 76 |
+
|
| 77 |
+
DATABASE_URL = os.environ.get("DATABASE_URL", "") # PostgreSQL / elephantsql
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# ── API keys ──────────────────────────────────────────────────────────
|
| 81 |
+
CASH_API_KEY = os.environ.get("CASH_API_KEY", "")
|
| 82 |
+
TIME_API_KEY = os.environ.get("TIME_API_KEY", "")
|
| 83 |
+
TENOR_API_KEY = os.environ.get("TENOR_API_KEY", "")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# ── Behaviour flags ───────────────────────────────────────────────────
|
| 87 |
+
ALLOW_CHATS = _bool("ALLOW_CHATS", True)
|
| 88 |
+
ALLOW_EXCL = _bool("ALLOW_EXCL", False)
|
| 89 |
+
DEL_CMDS = _bool("DEL_CMDS", False)
|
| 90 |
+
INFOPIC = _bool("INFOPIC", True)
|
| 91 |
+
STRICT_GBAN = _bool("STRICT_GBAN", True)
|
| 92 |
+
|
| 93 |
+
# ── Module loading ────────────────────────────────────────────────────
|
| 94 |
+
LOAD = _str_list("LOAD")
|
| 95 |
+
NO_LOAD = _str_list("NO_LOAD")
|
| 96 |
+
|
| 97 |
+
# ── Performance ───────────────────────────────────────────────────────
|
| 98 |
+
WORKERS = int(os.environ.get("WORKERS", 8))
|
| 99 |
+
TEMP_DOWNLOAD_DIRECTORY = os.environ.get("TEMP_DOWNLOAD_DIRECTORY", "./")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class Production(Config):
|
| 103 |
+
LOGGER = True
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class Development(Config):
|
| 107 |
+
LOGGER = True
|
| 108 |
+
# Override any value here for local dev without touching env
|
| 109 |
+
# Example:
|
| 110 |
+
# API_ID = 12345
|
| 111 |
+
# API_HASH = "abcdef..."
|
| 112 |
+
# TOKEN = "bot_token_here"
|
| 113 |
+
# OWNER_IDS = [123456789]
|
QueenNoxi/events.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import inspect
|
| 2 |
+
import re
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
from pymongo import MongoClient
|
| 6 |
+
from telethon import events
|
| 7 |
+
|
| 8 |
+
from QueenNoxi import MONGO_DB_URI, telethn
|
| 9 |
+
|
| 10 |
+
client = MongoClient()
|
| 11 |
+
client = MongoClient(MONGO_DB_URI)
|
| 12 |
+
db = client["QueenNoxi"]
|
| 13 |
+
gbanned = db.gban
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def register(**args):
|
| 17 |
+
"""Registers a new message."""
|
| 18 |
+
pattern = args.get("pattern", None)
|
| 19 |
+
|
| 20 |
+
r_pattern = r"^[/!.]"
|
| 21 |
+
|
| 22 |
+
if pattern is not None and not pattern.startswith("(?i)"):
|
| 23 |
+
args["pattern"] = "(?i)" + pattern
|
| 24 |
+
|
| 25 |
+
args["pattern"] = pattern.replace("^/", r_pattern, 1)
|
| 26 |
+
|
| 27 |
+
def decorator(func):
|
| 28 |
+
telethn.add_event_handler(func, events.NewMessage(**args))
|
| 29 |
+
return func
|
| 30 |
+
|
| 31 |
+
return decorator
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def chataction(**args):
|
| 35 |
+
"""Registers chat actions."""
|
| 36 |
+
|
| 37 |
+
def decorator(func):
|
| 38 |
+
telethn.add_event_handler(func, events.ChatAction(**args))
|
| 39 |
+
return func
|
| 40 |
+
|
| 41 |
+
return decorator
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def userupdate(**args):
|
| 45 |
+
"""Registers user updates."""
|
| 46 |
+
|
| 47 |
+
def decorator(func):
|
| 48 |
+
telethn.add_event_handler(func, events.UserUpdate(**args))
|
| 49 |
+
return func
|
| 50 |
+
|
| 51 |
+
return decorator
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def inlinequery(**args):
|
| 55 |
+
"""Registers inline query."""
|
| 56 |
+
pattern = args.get("pattern", None)
|
| 57 |
+
|
| 58 |
+
if pattern is not None and not pattern.startswith("(?i)"):
|
| 59 |
+
args["pattern"] = "(?i)" + pattern
|
| 60 |
+
|
| 61 |
+
def decorator(func):
|
| 62 |
+
telethn.add_event_handler(func, events.InlineQuery(**args))
|
| 63 |
+
return func
|
| 64 |
+
|
| 65 |
+
return decorator
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def callbackquery(**args):
|
| 69 |
+
"""Registers inline query."""
|
| 70 |
+
|
| 71 |
+
def decorator(func):
|
| 72 |
+
telethn.add_event_handler(func, events.CallbackQuery(**args))
|
| 73 |
+
return func
|
| 74 |
+
|
| 75 |
+
return decorator
|
| 76 |
+
|
| 77 |
+
def QueenNoxiinline(**args):
|
| 78 |
+
def decorator(func):
|
| 79 |
+
telethn.add_event_handler(func, events.CallbackQuery(**args))
|
| 80 |
+
return func
|
| 81 |
+
|
| 82 |
+
return decorator
|
| 83 |
+
def bot(**args):
|
| 84 |
+
pattern = args.get("pattern")
|
| 85 |
+
r_pattern = r"^[/]"
|
| 86 |
+
|
| 87 |
+
if pattern is not None and not pattern.startswith("(?i)"):
|
| 88 |
+
args["pattern"] = "(?i)" + pattern
|
| 89 |
+
|
| 90 |
+
args["pattern"] = pattern.replace("^/", r_pattern, 1)
|
| 91 |
+
stack = inspect.stack()
|
| 92 |
+
previous_stack_frame = stack[1]
|
| 93 |
+
file_test = Path(previous_stack_frame.filename)
|
| 94 |
+
file_test = file_test.stem.replace(".py", "")
|
| 95 |
+
reg = re.compile("(.*)")
|
| 96 |
+
|
| 97 |
+
if pattern is not None:
|
| 98 |
+
try:
|
| 99 |
+
cmd = re.search(reg, pattern)
|
| 100 |
+
try:
|
| 101 |
+
cmd = cmd.group(1).replace("$", "").replace("\\", "").replace("^", "")
|
| 102 |
+
except BaseException:
|
| 103 |
+
pass
|
| 104 |
+
|
| 105 |
+
try:
|
| 106 |
+
FUN_LIST[file_test].append(cmd)
|
| 107 |
+
except BaseException:
|
| 108 |
+
FUN_LIST.update({file_test: [cmd]})
|
| 109 |
+
except BaseException:
|
| 110 |
+
pass
|
| 111 |
+
|
| 112 |
+
def decorator(func):
|
| 113 |
+
async def wrapper(check):
|
| 114 |
+
if check.edit_date:
|
| 115 |
+
return
|
| 116 |
+
if check.fwd_from:
|
| 117 |
+
return
|
| 118 |
+
if check.is_group or check.is_private:
|
| 119 |
+
pass
|
| 120 |
+
else:
|
| 121 |
+
print("i don't work in channels")
|
| 122 |
+
return
|
| 123 |
+
if check.is_group:
|
| 124 |
+
if check.chat.megagroup:
|
| 125 |
+
pass
|
| 126 |
+
else:
|
| 127 |
+
print("i don't work in small chats")
|
| 128 |
+
return
|
| 129 |
+
|
| 130 |
+
users = gbanned.find({})
|
| 131 |
+
for c in users:
|
| 132 |
+
if check.sender_id == c["user"]:
|
| 133 |
+
return
|
| 134 |
+
try:
|
| 135 |
+
await func(check)
|
| 136 |
+
try:
|
| 137 |
+
LOAD_PLUG[file_test].append(func)
|
| 138 |
+
except Exception:
|
| 139 |
+
LOAD_PLUG.update({file_test: [func]})
|
| 140 |
+
except BaseException:
|
| 141 |
+
return
|
| 142 |
+
else:
|
| 143 |
+
pass
|
| 144 |
+
|
| 145 |
+
telethn.add_event_handler(wrapper, events.NewMessage(**args))
|
| 146 |
+
return wrapper
|
| 147 |
+
|
| 148 |
+
return decorator
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def queennoxi(**args):
|
| 152 |
+
pattern = args.get("pattern", None)
|
| 153 |
+
args.get("disable_edited", False)
|
| 154 |
+
ignore_unsafe = args.get("ignore_unsafe", False)
|
| 155 |
+
unsafe_pattern = r"^[^/!#@\$A-Za-z]"
|
| 156 |
+
args.get("group_only", False)
|
| 157 |
+
args.get("disable_errors", False)
|
| 158 |
+
args.get("insecure", False)
|
| 159 |
+
if pattern is not None and not pattern.startswith("(?i)"):
|
| 160 |
+
args["pattern"] = "(?i)" + pattern
|
| 161 |
+
|
| 162 |
+
if "disable_edited" in args:
|
| 163 |
+
del args["disable_edited"]
|
| 164 |
+
|
| 165 |
+
if "ignore_unsafe" in args:
|
| 166 |
+
del args["ignore_unsafe"]
|
| 167 |
+
|
| 168 |
+
if "group_only" in args:
|
| 169 |
+
del args["group_only"]
|
| 170 |
+
|
| 171 |
+
if "disable_errors" in args:
|
| 172 |
+
del args["disable_errors"]
|
| 173 |
+
|
| 174 |
+
if "insecure" in args:
|
| 175 |
+
del args["insecure"]
|
| 176 |
+
|
| 177 |
+
if pattern:
|
| 178 |
+
if not ignore_unsafe:
|
| 179 |
+
args["pattern"] = args["pattern"].replace("^.", unsafe_pattern, 1)
|
QueenNoxi/modules/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from QueenNoxi import LOAD, LOGGER, NO_LOAD
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def __list_all_modules():
|
| 5 |
+
import glob
|
| 6 |
+
from os.path import basename, dirname, isfile
|
| 7 |
+
|
| 8 |
+
# This generates a list of modules in this folder for the * in __main__ to work.
|
| 9 |
+
mod_paths = glob.glob(dirname(__file__) + "/*.py")
|
| 10 |
+
to_load = []
|
| 11 |
+
for f in mod_paths:
|
| 12 |
+
if isfile(f) and f.endswith(".py") and not f.endswith("__init__.py"):
|
| 13 |
+
mod_name = basename(f)[:-3]
|
| 14 |
+
try:
|
| 15 |
+
with open(f, "r", encoding="utf-8") as file:
|
| 16 |
+
content = file.read()
|
| 17 |
+
if "import telegram" in content or "from telegram" in content or "dispatcher." in content:
|
| 18 |
+
if "from QueenNoxi import pbot" not in content and "@pbot.on_" not in content:
|
| 19 |
+
LOGGER.info(f"Skipping legacy module: {mod_name}")
|
| 20 |
+
continue
|
| 21 |
+
to_load.append(mod_name)
|
| 22 |
+
except Exception as e:
|
| 23 |
+
LOGGER.error(f"Error reading module {mod_name}: {e}")
|
| 24 |
+
continue
|
| 25 |
+
|
| 26 |
+
if LOAD or NO_LOAD:
|
| 27 |
+
all_modules = to_load
|
| 28 |
+
to_load = LOAD
|
| 29 |
+
if to_load:
|
| 30 |
+
if not all(
|
| 31 |
+
any(mod == module_name for module_name in all_modules)
|
| 32 |
+
for mod in to_load
|
| 33 |
+
):
|
| 34 |
+
LOGGER.error("Invalid loadorder names. Quitting.")
|
| 35 |
+
quit(1)
|
| 36 |
+
|
| 37 |
+
all_modules = sorted(set(all_modules) - set(to_load))
|
| 38 |
+
to_load = list(all_modules) + to_load
|
| 39 |
+
|
| 40 |
+
else:
|
| 41 |
+
to_load = all_modules
|
| 42 |
+
|
| 43 |
+
if NO_LOAD:
|
| 44 |
+
LOGGER.info("Not loading: {}".format(NO_LOAD))
|
| 45 |
+
return [item for item in to_load if item not in NO_LOAD]
|
| 46 |
+
|
| 47 |
+
return to_load
|
| 48 |
+
|
| 49 |
+
return to_load
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
ALL_MODULES = __list_all_modules()
|
| 53 |
+
LOGGER.info("Modules to load: %s", str(ALL_MODULES))
|
| 54 |
+
__all__ = ALL_MODULES + ["ALL_MODULES"]
|
QueenNoxi/modules/admin.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import html
|
| 2 |
+
import os
|
| 3 |
+
from pyrogram import filters, Client, enums
|
| 4 |
+
from pyrogram.raw import functions, types as raw_types
|
| 5 |
+
from pyrogram.types import (
|
| 6 |
+
Message,
|
| 7 |
+
InlineKeyboardMarkup,
|
| 8 |
+
InlineKeyboardButton,
|
| 9 |
+
ChatPrivileges
|
| 10 |
+
)
|
| 11 |
+
from pyrogram.errors import RPCError
|
| 12 |
+
|
| 13 |
+
from QueenNoxi import DRAGONS, pbot, BOT_ID
|
| 14 |
+
from QueenNoxi.modules.disable import DisableAbleCommandHandler
|
| 15 |
+
from QueenNoxi.modules.helper_funcs.admin_rights import user_can_changeinfo
|
| 16 |
+
from QueenNoxi.modules.helper_funcs.chat_status import (
|
| 17 |
+
bot_admin,
|
| 18 |
+
can_pin,
|
| 19 |
+
connection_status,
|
| 20 |
+
user_admin,
|
| 21 |
+
can_promote,
|
| 22 |
+
is_user_admin
|
| 23 |
+
)
|
| 24 |
+
from QueenNoxi.modules.helper_funcs.extraction import (
|
| 25 |
+
extract_user,
|
| 26 |
+
extract_user_and_text,
|
| 27 |
+
)
|
| 28 |
+
from QueenNoxi.modules.log_channel import loggable
|
| 29 |
+
|
| 30 |
+
@pbot.on_message(filters.command("setsticker") & filters.group)
|
| 31 |
+
@bot_admin
|
| 32 |
+
@user_admin
|
| 33 |
+
async def set_sticker(client: Client, message: Message):
|
| 34 |
+
chat = message.chat
|
| 35 |
+
user = message.from_user
|
| 36 |
+
if not await user_can_changeinfo(chat.id, user.id):
|
| 37 |
+
return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !")
|
| 38 |
+
|
| 39 |
+
if message.reply_to_message and message.reply_to_message.sticker:
|
| 40 |
+
stkr = message.reply_to_message.sticker.set_name
|
| 41 |
+
try:
|
| 42 |
+
await client.set_chat_sticker_set(chat.id, stkr)
|
| 43 |
+
await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ɢʀᴏᴜᴩ sᴛɪᴄᴋᴇʀs ɪɴ {chat.title}!")
|
| 44 |
+
except RPCError as e:
|
| 45 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 46 |
+
else:
|
| 47 |
+
await message.reply_text("» ʀᴇᴩʟʏ ᴛᴏ ᴀ sᴛɪᴄᴋᴇʀ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɢʀᴏᴜᴩ sᴛɪᴄᴋᴇʀ ᴩᴀᴄᴋ !")
|
| 48 |
+
|
| 49 |
+
@pbot.on_message(filters.command("setgpic") & filters.group)
|
| 50 |
+
@bot_admin
|
| 51 |
+
@user_admin
|
| 52 |
+
async def setchatpic(client: Client, message: Message):
|
| 53 |
+
chat = message.chat
|
| 54 |
+
user = message.from_user
|
| 55 |
+
if not await user_can_changeinfo(chat.id, user.id):
|
| 56 |
+
return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !")
|
| 57 |
+
|
| 58 |
+
if message.reply_to_message and (message.reply_to_message.photo or message.reply_to_message.document):
|
| 59 |
+
dlmsg = await message.reply_text("» ᴄʜᴀɴɢɪɴɢ ɢʀᴏᴜᴩ's ᴩʀᴏғɪʟᴇ ᴩɪᴄ...")
|
| 60 |
+
img = await message.reply_to_message.download("gpic.png")
|
| 61 |
+
try:
|
| 62 |
+
await client.set_chat_photo(chat.id, photo=img)
|
| 63 |
+
await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ɢʀᴏᴜᴩ ᴩʀᴏғɪʟᴇ ᴩɪᴄ !")
|
| 64 |
+
except RPCError as e:
|
| 65 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 66 |
+
finally:
|
| 67 |
+
await dlmsg.delete()
|
| 68 |
+
if os.path.exists("gpic.png"):
|
| 69 |
+
os.remove("gpic.png")
|
| 70 |
+
else:
|
| 71 |
+
await message.reply_text("» ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴩʜᴏᴛᴏ ᴏʀ ғɪʟᴇ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɢʀᴏᴜᴩ ᴩʀᴏғɪʟᴇ ᴩɪᴄ !")
|
| 72 |
+
|
| 73 |
+
@pbot.on_message(filters.command("delgpic") & filters.group)
|
| 74 |
+
@bot_admin
|
| 75 |
+
@user_admin
|
| 76 |
+
async def rmchatpic(client: Client, message: Message):
|
| 77 |
+
chat = message.chat
|
| 78 |
+
user = message.from_user
|
| 79 |
+
if not await user_can_changeinfo(chat.id, user.id):
|
| 80 |
+
return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏUtᴩ ɪɴғᴏ ʙᴀʙʏ !")
|
| 81 |
+
try:
|
| 82 |
+
await client.delete_chat_photo(chat.id)
|
| 83 |
+
await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ᴅᴇʟᴇᴛᴇᴅ ɢʀᴏᴜᴩ's ᴅᴇғᴀᴜʟᴛ ᴩʀᴏғɪʟᴇ ᴩɪᴄ !")
|
| 84 |
+
except RPCError as e:
|
| 85 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 86 |
+
|
| 87 |
+
@pbot.on_message(filters.command("setdesc") & filters.group)
|
| 88 |
+
@bot_admin
|
| 89 |
+
@user_admin
|
| 90 |
+
async def set_desc(client: Client, message: Message):
|
| 91 |
+
chat = message.chat
|
| 92 |
+
user = message.from_user
|
| 93 |
+
if not await user_can_changeinfo(chat.id, user.id):
|
| 94 |
+
return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !")
|
| 95 |
+
|
| 96 |
+
desc = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 97 |
+
if not desc:
|
| 98 |
+
return await message.reply_text("» ᴡᴛғ, ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ sᴇᴛ ᴀɴ ᴇᴍᴩᴛʏ ᴅᴇsᴄʀɪᴩᴛɪᴏɴ !")
|
| 99 |
+
try:
|
| 100 |
+
await client.set_chat_description(chat.id, desc[:255])
|
| 101 |
+
await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ ᴜᴩᴅᴀᴛᴇᴅ ᴄʜᴀᴛ ᴅᴇsᴄʀɪᴩᴛɪᴏɴ ɪɴ {chat.title}!")
|
| 102 |
+
except RPCError as e:
|
| 103 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 104 |
+
|
| 105 |
+
@pbot.on_message(filters.command("setgtitle") & filters.group)
|
| 106 |
+
@bot_admin
|
| 107 |
+
@user_admin
|
| 108 |
+
async def setchat_title(client: Client, message: Message):
|
| 109 |
+
chat = message.chat
|
| 110 |
+
user = message.from_user
|
| 111 |
+
if not await user_can_changeinfo(chat.id, user.id):
|
| 112 |
+
return await message.reply_text("» ʏᴏᴜ ᴅᴏɴ'ᴛ ʜᴀᴠᴇ ᴩᴇʀᴍɪssɪᴏɴs ᴛᴏ ᴄʜᴀɴɢᴇ ɢʀᴏᴜᴩ ɪɴғᴏ ʙᴀʙʏ !")
|
| 113 |
+
|
| 114 |
+
title = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 115 |
+
if not title:
|
| 116 |
+
return await message.reply_text("» ᴇɴᴛᴇʀ sᴏᴍᴇ ᴛᴇxᴛ ᴛᴏ sᴇᴛ ɪᴛ ᴀs ɴᴇᴡ ᴄʜᴀᴛ ᴛɪᴛʟᴇ !")
|
| 117 |
+
try:
|
| 118 |
+
await client.set_chat_title(chat.id, title)
|
| 119 |
+
await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ <b>{html.escape(title)}</b> ᴀs ɴᴇᴡ ᴄʜᴀᴛ ᴛɪᴛʟᴇ !")
|
| 120 |
+
except RPCError as e:
|
| 121 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 122 |
+
|
| 123 |
+
@DisableAbleCommandHandler("promote", admin_ok=True)
|
| 124 |
+
@connection_status
|
| 125 |
+
@bot_admin
|
| 126 |
+
@can_promote
|
| 127 |
+
@user_admin
|
| 128 |
+
@loggable
|
| 129 |
+
async def promote(client: Client, message: Message) -> str:
|
| 130 |
+
chat = message.chat
|
| 131 |
+
user = message.from_user
|
| 132 |
+
user_id = await extract_user(message, message.command[1:])
|
| 133 |
+
if not user_id:
|
| 134 |
+
await message.reply_text("» ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ's ᴛʜᴀᴛ ᴜsᴇʀ.")
|
| 135 |
+
return
|
| 136 |
+
try:
|
| 137 |
+
user_member = await chat.get_member(user_id)
|
| 138 |
+
except:
|
| 139 |
+
return
|
| 140 |
+
if user_member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER):
|
| 141 |
+
await message.reply_text("» ᴀᴄᴄᴏʀᴅɪɴɢ ᴛᴏ ᴍᴇ ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴀʟʀᴇᴀᴅʏ ᴀɴ ᴀᴅᴍɪɴ ʜᴇʀᴇ !")
|
| 142 |
+
return
|
| 143 |
+
if user_id == BOT_ID:
|
| 144 |
+
await message.reply_text("» ɪ ᴄᴀɴ'ᴛ ᴩʀᴏᴍᴏᴛᴇ ᴍʏsᴇʟғ.")
|
| 145 |
+
return
|
| 146 |
+
|
| 147 |
+
bot_member = await chat.get_member(BOT_ID)
|
| 148 |
+
try:
|
| 149 |
+
await client.promote_chat_member(chat.id, user_id, privileges=bot_member.privileges)
|
| 150 |
+
await message.reply_text(f"» ᴩʀᴏᴍᴏᴛɪɴɢ ᴀ ᴜsᴇʀ ɪɴ {chat.title}")
|
| 151 |
+
return f"<b>{html.escape(chat.title)}:</b>\n#ᴩʀᴏᴍᴏᴛᴇᴅ\n<b>ᴩʀᴏᴍᴏᴛᴇʀ :</b> {user.mention}\n<b>ᴜsᴇʀ :</b> {user_member.user.mention}"
|
| 152 |
+
except RPCError as e:
|
| 153 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 154 |
+
|
| 155 |
+
@DisableAbleCommandHandler("demote", admin_ok=True)
|
| 156 |
+
@connection_status
|
| 157 |
+
@bot_admin
|
| 158 |
+
@can_promote
|
| 159 |
+
@user_admin
|
| 160 |
+
@loggable
|
| 161 |
+
async def demote(client: Client, message: Message) -> str:
|
| 162 |
+
chat = message.chat
|
| 163 |
+
user = message.from_user
|
| 164 |
+
user_id = await extract_user(message, message.command[1:])
|
| 165 |
+
if not user_id:
|
| 166 |
+
await message.reply_text("» ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ's ᴛʜᴀᴛ ᴜsᴇʀ.")
|
| 167 |
+
return
|
| 168 |
+
try:
|
| 169 |
+
user_member = await chat.get_member(user_id)
|
| 170 |
+
except:
|
| 171 |
+
return
|
| 172 |
+
if user_member.status == enums.ChatMemberStatus.OWNER:
|
| 173 |
+
return await message.reply_text("» ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴏᴡɴᴇʀ !")
|
| 174 |
+
if user_id == BOT_ID:
|
| 175 |
+
return await message.reply_text("» ɪ ᴄᴀɴ'ᴛ ᴅᴇᴍᴏᴛᴇ ᴍʏsᴇʟғ.")
|
| 176 |
+
|
| 177 |
+
try:
|
| 178 |
+
# Use raw API to fully strip admin — promote_chat_member with all False
|
| 179 |
+
# keeps the admin badge in Telegram; raw EditAdmin actually removes it.
|
| 180 |
+
channel = await client.resolve_peer(chat.id)
|
| 181 |
+
target = await client.resolve_peer(user_id)
|
| 182 |
+
await client.invoke(
|
| 183 |
+
functions.channels.EditAdmin(
|
| 184 |
+
channel=channel,
|
| 185 |
+
user_id=target,
|
| 186 |
+
admin_rights=raw_types.ChatAdminRights(
|
| 187 |
+
change_info=False,
|
| 188 |
+
post_messages=False,
|
| 189 |
+
edit_messages=False,
|
| 190 |
+
delete_messages=False,
|
| 191 |
+
ban_users=False,
|
| 192 |
+
invite_users=False,
|
| 193 |
+
pin_messages=False,
|
| 194 |
+
add_admins=False,
|
| 195 |
+
manage_call=False,
|
| 196 |
+
anonymous=False,
|
| 197 |
+
manage_topics=False,
|
| 198 |
+
post_stories=False,
|
| 199 |
+
edit_stories=False,
|
| 200 |
+
delete_stories=False,
|
| 201 |
+
),
|
| 202 |
+
rank=""
|
| 203 |
+
)
|
| 204 |
+
)
|
| 205 |
+
await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ ᴅᴇᴍᴏᴛᴇᴅ ɪɴ {chat.title}")
|
| 206 |
+
return f"<b>{html.escape(chat.title)}:</b>\n#ᴅᴇᴍᴏᴛᴇᴅ\n<b>ᴅᴇᴍᴏᴛᴇʀ :</b> {user.mention}\n<b>ᴅᴇᴍᴏᴛᴇᴅ :</b> {user_member.user.mention}"
|
| 207 |
+
except RPCError as e:
|
| 208 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
@pbot.on_message(filters.command(["admincache", "reload", "refresh"]) & filters.group)
|
| 214 |
+
@user_admin
|
| 215 |
+
async def refresh_admin(client: Client, message: Message):
|
| 216 |
+
from QueenNoxi.modules.helper_funcs.chat_status import ADMIN_CACHE
|
| 217 |
+
try:
|
| 218 |
+
ADMIN_CACHE.pop(message.chat.id)
|
| 219 |
+
except KeyError:
|
| 220 |
+
pass
|
| 221 |
+
await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ʀᴇғʀᴇsʜᴇᴅ ᴀᴅᴍɪɴ ᴄᴀᴄʜᴇ !")
|
| 222 |
+
|
| 223 |
+
@pbot.on_message(filters.command("pin") & filters.group)
|
| 224 |
+
@bot_admin
|
| 225 |
+
@can_pin
|
| 226 |
+
@user_admin
|
| 227 |
+
@loggable
|
| 228 |
+
async def pin(client: Client, message: Message) -> str:
|
| 229 |
+
args = message.command[1:]
|
| 230 |
+
chat = message.chat
|
| 231 |
+
user = message.from_user
|
| 232 |
+
if not message.reply_to_message:
|
| 233 |
+
return await message.reply_text("» ʀᴇᴩʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ᴛᴏ ᴩɪɴ ɪᴛ !")
|
| 234 |
+
|
| 235 |
+
is_silent = True
|
| 236 |
+
if len(args) >= 1:
|
| 237 |
+
is_silent = args[0].lower() not in ["notify", "loud", "violent"]
|
| 238 |
+
try:
|
| 239 |
+
await client.pin_chat_message(chat.id, message.reply_to_message.id, disable_notification=is_silent)
|
| 240 |
+
await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ᴩɪɴɴᴇᴅ ᴛʜᴀᴛ ᴍᴇssᴀɢᴇ.")
|
| 241 |
+
return f"<b>{html.escape(chat.title)}:</b>\nᴩɪɴɴᴇᴅ-ᴀ-ᴍᴇssᴀɢᴇ\n<b>ᴩɪɴɴᴇᴅ ʙʏ :</b> {user.mention}"
|
| 242 |
+
except RPCError as e:
|
| 243 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 244 |
+
|
| 245 |
+
@pbot.on_message(filters.command("unpin") & filters.group)
|
| 246 |
+
@bot_admin
|
| 247 |
+
@can_pin
|
| 248 |
+
@user_admin
|
| 249 |
+
@loggable
|
| 250 |
+
async def unpin(client: Client, message: Message) -> str:
|
| 251 |
+
chat = message.chat
|
| 252 |
+
user = message.from_user
|
| 253 |
+
try:
|
| 254 |
+
if message.reply_to_message:
|
| 255 |
+
await client.unpin_chat_message(chat.id, message.reply_to_message.id)
|
| 256 |
+
else:
|
| 257 |
+
await client.unpin_chat_message(chat.id)
|
| 258 |
+
await message.reply_text("» sᴜᴄᴄᴇssғᴜʟʟʏ ᴜɴᴩɪɴɴᴇᴅ.")
|
| 259 |
+
return f"<b>{html.escape(chat.title)}:</b>\nᴜɴᴩɪɴɴᴇᴅ-ᴀ-ᴍᴇssᴀɢᴇ\n<b>ᴜɴᴩɪɴɴᴇᴅ ʙʏ :</b> {user.mention}"
|
| 260 |
+
except RPCError as e:
|
| 261 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 262 |
+
|
| 263 |
+
@DisableAbleCommandHandler("invitelink", admin_ok=True)
|
| 264 |
+
@connection_status
|
| 265 |
+
@bot_admin
|
| 266 |
+
async def invite(client: Client, message: Message):
|
| 267 |
+
chat = message.chat
|
| 268 |
+
if chat.username:
|
| 269 |
+
return await message.reply_text(f"https://t.me/{chat.username}")
|
| 270 |
+
try:
|
| 271 |
+
invitelink = await client.export_chat_invite_link(chat.id)
|
| 272 |
+
await message.reply_text(invitelink)
|
| 273 |
+
except RPCError as e:
|
| 274 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 275 |
+
|
| 276 |
+
@DisableAbleCommandHandler("title", admin_ok=True)
|
| 277 |
+
@connection_status
|
| 278 |
+
@bot_admin
|
| 279 |
+
@user_admin
|
| 280 |
+
async def set_admin_title(client: Client, message: Message):
|
| 281 |
+
chat = message.chat
|
| 282 |
+
user_id, title = await extract_user_and_text(message, message.command[1:])
|
| 283 |
+
if not user_id:
|
| 284 |
+
return await message.reply_text("» ɪ ᴅᴏɴ'ᴛ ᴋɴᴏᴡ ᴡʜᴏ's ᴛʜᴀᴛ ᴜsᴇʀ.")
|
| 285 |
+
try:
|
| 286 |
+
user_member = await chat.get_member(user_id)
|
| 287 |
+
except:
|
| 288 |
+
return
|
| 289 |
+
if user_member.status == enums.ChatMemberStatus.OWNER:
|
| 290 |
+
return await message.reply_text("» ᴛʜᴀᴛ ᴜsᴇʀ ɪs ᴏᴡɴᴇʀ !")
|
| 291 |
+
if user_member.status != enums.ChatMemberStatus.ADMINISTRATOR:
|
| 292 |
+
return await message.reply_text("» ɪ ᴄᴀɴ ᴏɴʟʏ sᴇᴛ ᴛɪᴛʟᴇ ғᴏʀ ᴀᴅᴍɪɴs !")
|
| 293 |
+
if not title:
|
| 294 |
+
return await message.reply_text("» sᴇᴛ ᴀ ᴛɪᴛʟᴇ ʙᴀʙʏ !")
|
| 295 |
+
|
| 296 |
+
try:
|
| 297 |
+
await client.set_administrator_custom_title(chat.id, user_id, title[:16])
|
| 298 |
+
await message.reply_text(f"» sᴜᴄᴄᴇssғᴜʟʟʏ sᴇᴛ ᴛɪᴛʟᴇ ғᴏʀ <code>{user_member.user.first_name}</code>")
|
| 299 |
+
except RPCError as e:
|
| 300 |
+
await message.reply_text(f"Error: {e.MESSAGE}")
|
| 301 |
+
|
| 302 |
+
@pbot.on_message(filters.command("pinned") & filters.group)
|
| 303 |
+
@bot_admin
|
| 304 |
+
async def pinned_msg(client: Client, message: Message):
|
| 305 |
+
chat = await client.get_chat(message.chat.id)
|
| 306 |
+
if chat.pinned_message:
|
| 307 |
+
pinned_id = chat.pinned_message.id
|
| 308 |
+
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}"
|
| 309 |
+
await message.reply_text(f"ᴩɪɴɴᴇᴅ ᴏɴ {html.escape(chat.title)}.", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ᴍᴇssᴀɢᴇ", url=link)]]), disable_web_page_preview=True)
|
| 310 |
+
else:
|
| 311 |
+
await message.reply_text(f"» ᴛʜᴇʀᴇ's ɴᴏ ᴩɪɴɴᴇᴅ ᴍᴇssᴀɢᴇ ɪɴ <b>{html.escape(chat.title)}!</b>")
|
| 312 |
+
|
| 313 |
+
@DisableAbleCommandHandler(["admins", "staff"])
|
| 314 |
+
@connection_status
|
| 315 |
+
async def adminlist(client: Client, message: Message):
|
| 316 |
+
if message.chat.type == enums.ChatType.PRIVATE:
|
| 317 |
+
return await message.reply_text("» ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴄᴀɴ ᴏɴʟʏ ʙᴇ ᴜsᴇᴅ ɪɴ ɢʀᴏᴜᴩ's.")
|
| 318 |
+
msg = await message.reply_text("» ғᴇᴛᴄʜɪɴɢ ᴀᴅᴍɪɴs ʟɪsᴛ...")
|
| 319 |
+
try:
|
| 320 |
+
administrators = []
|
| 321 |
+
async for m in client.get_chat_members(message.chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS):
|
| 322 |
+
administrators.append(m)
|
| 323 |
+
text = "ᴀᴅᴍɪɴs ɪɴ <b>{}</b>:".format(html.escape(message.chat.title))
|
| 324 |
+
creator = None; admins = []; bots = []
|
| 325 |
+
for admin in administrators:
|
| 326 |
+
if admin.status == enums.ChatMemberStatus.OWNER: creator = admin
|
| 327 |
+
elif admin.user.is_bot: bots.append(admin)
|
| 328 |
+
else: admins.append(admin)
|
| 329 |
+
if creator:
|
| 330 |
+
text += f"\n\n🥀 ᴏᴡɴᴇʀ :\n<code> • </code>{creator.user.mention}"
|
| 331 |
+
if creator.custom_title: text += f"\n<code> ┗━ {html.escape(creator.custom_title)}</code>"
|
| 332 |
+
if admins:
|
| 333 |
+
text += "\n\n💫 ᴀᴅᴍɪɴs :"
|
| 334 |
+
for a in admins:
|
| 335 |
+
text += f"\n<code> • </code>{a.user.mention}"
|
| 336 |
+
if a.custom_title: text += f" | <code>{html.escape(a.custom_title)}</code>"
|
| 337 |
+
if bots:
|
| 338 |
+
text += "\n\n🤖 ʙᴏᴛs :"
|
| 339 |
+
for b in bots: text += f"\n<code> • </code>{b.user.mention}"
|
| 340 |
+
await msg.edit_text(text)
|
| 341 |
+
except RPCError as e:
|
| 342 |
+
await msg.edit_text(f"Error: {e.MESSAGE}")
|
| 343 |
+
|
| 344 |
+
__mod_name__ = "Admins"
|
| 345 |
+
__help__ = """
|
| 346 |
+
*User Commands*:
|
| 347 |
+
» /admins: List of admins in the chat
|
| 348 |
+
» /pinned: To get the current pinned message.
|
| 349 |
+
|
| 350 |
+
*Admins only:*
|
| 351 |
+
» /promote: Promotes the user replied to
|
| 352 |
+
» /demote: Demotes the user replied to
|
| 353 |
+
» /title <title here>: Sets a custom title
|
| 354 |
+
» /admincache: Force refresh the admins list
|
| 355 |
+
» /setgtitle <text>: Set group title
|
| 356 |
+
» /setgpic: Reply to an image to set as group photo
|
| 357 |
+
» /delgpic: Delete group photo
|
| 358 |
+
» /setdesc: Set group description
|
| 359 |
+
» /setsticker: Set group sticker
|
| 360 |
+
» /pin: Silently pins the message
|
| 361 |
+
» /unpin: Unpins the currently pinned message
|
| 362 |
+
» /invitelink: Gets invitelink
|
| 363 |
+
"""
|
QueenNoxi/modules/aiimage.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023-24 Noob-QueenNoxi
|
| 4 |
+
|
| 5 |
+
GITHUB: NOOB-MUKESH
|
| 6 |
+
TELEGRAM: @MR_SUKKUN
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
|
| 18 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 19 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 20 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 21 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 22 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 23 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 24 |
+
SOFTWARE."""
|
| 25 |
+
from pyrogram import filters
|
| 26 |
+
from pyrogram.types import Message
|
| 27 |
+
from pyrogram.types import InputMediaPhoto
|
| 28 |
+
from .. import pbot as QueenNoxi,BOT_USERNAME
|
| 29 |
+
from MukeshAPI import api
|
| 30 |
+
from pyrogram.enums import ChatAction,ParseMode
|
| 31 |
+
|
| 32 |
+
@QueenNoxi.on_message(filters.command("imagine"))
|
| 33 |
+
async def imagine_(b, message: Message):
|
| 34 |
+
if message.reply_to_message:
|
| 35 |
+
text = message.reply_to_message.text
|
| 36 |
+
else:
|
| 37 |
+
|
| 38 |
+
text =message.text.split(None, 1)[1]
|
| 39 |
+
queennoxi=await message.reply_text( "`Please wait...,\n\nGenerating prompt .. ...`")
|
| 40 |
+
try:
|
| 41 |
+
await b.send_chat_action(message.chat.id, ChatAction.UPLOAD_PHOTO)
|
| 42 |
+
x=api.ai_image(text)
|
| 43 |
+
with open("queennoxi.jpg", 'wb') as f:
|
| 44 |
+
f.write(x)
|
| 45 |
+
caption = f"""
|
| 46 |
+
💘sᴜᴄᴇssғᴜʟʟʏ ɢᴇɴᴇʀᴀᴛᴇᴅ : {text}
|
| 47 |
+
✨ɢᴇɴᴇʀᴀᴛᴇᴅ ʙʏ : @{BOT_USERNAME}
|
| 48 |
+
🥀ʀᴇǫᴜᴇsᴛᴇᴅ ʙʏ : {message.from_user.mention}
|
| 49 |
+
"""
|
| 50 |
+
await queennoxi.delete()
|
| 51 |
+
await message.reply_photo("queennoxi.jpg",caption=caption,quote=True)
|
| 52 |
+
except Exception as e:
|
| 53 |
+
await queennoxi.edit_text(f"error {e}")
|
| 54 |
+
|
| 55 |
+
# -----------CREDITS -----------
|
| 56 |
+
# telegram : @legend_coder
|
| 57 |
+
# github : Alexainc
|
| 58 |
+
__mod_name__ = "Aɪ ɪᴍᴀɢᴇ"
|
| 59 |
+
__help__ = """
|
| 60 |
+
➻ /imagine : ɢᴇɴᴇʀᴀᴛᴇ Aɪ ɪᴍᴀɢᴇ ғʀᴏᴍ ᴛᴇxᴛ
|
| 61 |
+
"""
|
QueenNoxi/modules/alive.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
from platform import python_version as pyver
|
| 3 |
+
from pyrogram.enums import ChatType
|
| 4 |
+
from pyrogram import __version__ as pver
|
| 5 |
+
from pyrogram import filters
|
| 6 |
+
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
|
| 7 |
+
from telethon import __version__ as tver
|
| 8 |
+
from QueenNoxi.modules.no_sql.chats_db import add_served_chat
|
| 9 |
+
from QueenNoxi.modules.no_sql.users_db import save_id
|
| 10 |
+
from QueenNoxi import SUPPORT_CHAT, SUPPORT_CHAT_URL, pbot, BOT_USERNAME, OWNER_ID, BOT_NAME, START_IMG
|
| 11 |
+
|
| 12 |
+
PHOTO = [
|
| 13 |
+
"https://telegra.ph/file/d2a23fbe48129a7957887.jpg",
|
| 14 |
+
"https://telegra.ph/file/ddf30888de58d77911ee1.jpg",
|
| 15 |
+
"https://telegra.ph/file/268d66cad42dc92ec65ca.jpg",
|
| 16 |
+
"https://telegra.ph/file/13a0cbbff8f429e2c59ee.jpg",
|
| 17 |
+
"https://telegra.ph/file/bdfd86195221e979e6b20.jpg",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
QueenNoxi = [
|
| 21 |
+
[
|
| 22 |
+
InlineKeyboardButton(text="ᴏᴡɴᴇʀ", user_id=OWNER_ID),
|
| 23 |
+
InlineKeyboardButton(text="ꜱᴜᴘᴘᴏʀᴛ", url=SUPPORT_CHAT_URL),
|
| 24 |
+
],
|
| 25 |
+
[
|
| 26 |
+
InlineKeyboardButton(
|
| 27 |
+
text="➕ᴀᴅᴅ ᴍᴇ ᴇʟsᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ➕",
|
| 28 |
+
url=f"https://t.me/{BOT_USERNAME}?startgroup=true",
|
| 29 |
+
),
|
| 30 |
+
],
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
@pbot.on_message(filters.command("alive"))
|
| 34 |
+
async def alive(client, m: Message):
|
| 35 |
+
await m.delete()
|
| 36 |
+
accha = await m.reply("⚡")
|
| 37 |
+
await asyncio.sleep(0.2)
|
| 38 |
+
await accha.edit("ᴅɪɴɢ ᴅᴏɴɢ ꨄ︎ ᴀʟɪᴠɪɴɢ..")
|
| 39 |
+
await accha.delete()
|
| 40 |
+
await asyncio.sleep(0.3)
|
| 41 |
+
|
| 42 |
+
umm = await m.reply_sticker(
|
| 43 |
+
"CAACAgUAAxkDAAJHbmLuy2NEfrfh6lZSohacEGrVjd5wAAIOBAACl42QVKnra4sdzC_uKQQ"
|
| 44 |
+
)
|
| 45 |
+
await umm.delete()
|
| 46 |
+
owner = await client.get_users(OWNER_ID)
|
| 47 |
+
await m.reply_photo(
|
| 48 |
+
START_IMG,
|
| 49 |
+
caption=f"""**ʜᴇʏ, ɪ ᴀᴍ 『[{BOT_NAME}](t.me/{BOT_USERNAME})』**
|
| 50 |
+
━━━━━━━━━━━━━━━━━━━
|
| 51 |
+
» **ᴍʏ ᴏᴡɴᴇʀ :** {owner.mention}
|
| 52 |
+
|
| 53 |
+
» **ᴛᴇʟᴇᴛʜᴏɴ :** `{tver}`
|
| 54 |
+
|
| 55 |
+
» **ᴘʏʀᴏɢʀᴀᴍ :** `{pver}`
|
| 56 |
+
|
| 57 |
+
» **ᴘʏᴛʜᴏɴ :** `{pyver()}`
|
| 58 |
+
━━━━━━━━━━━━━━━━━━━""",
|
| 59 |
+
reply_markup=InlineKeyboardMarkup(QueenNoxi)
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
@pbot.on_message(group=1)
|
| 63 |
+
async def save_statss(_, m: Message):
|
| 64 |
+
try:
|
| 65 |
+
if m.chat.type == ChatType.PRIVATE:
|
| 66 |
+
await save_id(m.from_user.id)
|
| 67 |
+
else:
|
| 68 |
+
await add_served_chat(m.chat.id)
|
| 69 |
+
except Exception:
|
| 70 |
+
pass
|
QueenNoxi/modules/animation.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import aiohttp
|
| 3 |
+
import os
|
| 4 |
+
import random
|
| 5 |
+
from pyrogram import filters, Client
|
| 6 |
+
from pyrogram.errors import FloodWait, RPCError
|
| 7 |
+
|
| 8 |
+
from pyrogram.types import Message
|
| 9 |
+
from QueenNoxi import pbot, LOGGER
|
| 10 |
+
from QueenNoxi.config import Config
|
| 11 |
+
TENOR_API_KEY = Config.TENOR_API_KEY
|
| 12 |
+
DRAGONS = Config.DRAGONS
|
| 13 |
+
OWNER_IDS = Config.OWNER_IDS
|
| 14 |
+
SUDOERS = list(set(DRAGONS + OWNER_IDS))
|
| 15 |
+
|
| 16 |
+
import zipfile
|
| 17 |
+
import shutil
|
| 18 |
+
from QueenNoxi.modules.disable import DisableAbleCommandHandler
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
from QueenNoxi.modules.helper_funcs.chat_status import user_admin
|
| 22 |
+
|
| 23 |
+
# ── Setup Caching ────────────────────────────────────────────────────────────
|
| 24 |
+
CACHE_DIR = "QueenNoxi/resources/animation_cache"
|
| 25 |
+
if not os.path.exists(CACHE_DIR):
|
| 26 |
+
os.makedirs(CACHE_DIR)
|
| 27 |
+
|
| 28 |
+
async def get_random_gif(category: str):
|
| 29 |
+
"""Fetch a random GIF URL from Tenor or local cache."""
|
| 30 |
+
tenor_map = {
|
| 31 |
+
"kill": "kill", "love": "love", "hug": "hug", "slap": "slap",
|
| 32 |
+
"pat": "pat", "kiss": "kiss", "brain": "think", "moon": "sleep",
|
| 33 |
+
"hack": "hacker", "police": "police", "bombs": "explosion", "clock": "clock"
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
action = tenor_map.get(category, category)
|
| 37 |
+
cat_dir = os.path.join(CACHE_DIR, category)
|
| 38 |
+
if not os.path.exists(cat_dir):
|
| 39 |
+
os.makedirs(cat_dir)
|
| 40 |
+
|
| 41 |
+
# 1. Try Tenor API
|
| 42 |
+
if TENOR_API_KEY:
|
| 43 |
+
try:
|
| 44 |
+
query = f"{action} anime".replace(" ", "%20")
|
| 45 |
+
url = f"https://tenor.googleapis.com/v2/search?q={query}&key={TENOR_API_KEY}&limit=5&random=true"
|
| 46 |
+
async with aiohttp.ClientSession() as session:
|
| 47 |
+
async with session.get(url, timeout=5) as resp:
|
| 48 |
+
if resp.status == 200:
|
| 49 |
+
data = await resp.json()
|
| 50 |
+
if data.get('results'):
|
| 51 |
+
chosen = random.choice(data['results'])
|
| 52 |
+
gif_url = chosen['media_formats']['gif']['url']
|
| 53 |
+
|
| 54 |
+
# Async Background Download for Cache (best effort)
|
| 55 |
+
asyncio.create_task(cache_gif(category, gif_url))
|
| 56 |
+
return gif_url
|
| 57 |
+
except Exception as e:
|
| 58 |
+
LOGGER.warning(f"Tenor API error for {category}: {e}")
|
| 59 |
+
|
| 60 |
+
# 2. Fallback to Local Cache
|
| 61 |
+
cached_files = [f for f in os.listdir(cat_dir) if f.endswith(".gif")]
|
| 62 |
+
if cached_files:
|
| 63 |
+
local_file = random.choice(cached_files)
|
| 64 |
+
return os.path.join(cat_dir, local_file)
|
| 65 |
+
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
async def cache_gif(category: str, url: str):
|
| 69 |
+
"""Download and save a GIF to the local cache if it doesn't exist."""
|
| 70 |
+
cat_dir = os.path.join(CACHE_DIR, category)
|
| 71 |
+
# Limit cache size per category (e.g. 10 files)
|
| 72 |
+
if len(os.listdir(cat_dir)) >= 15:
|
| 73 |
+
return
|
| 74 |
+
|
| 75 |
+
file_id = url.split("/")[-2] if "/" in url else str(random.randint(1000, 9999))
|
| 76 |
+
file_path = os.path.join(cat_dir, f"{file_id}.gif")
|
| 77 |
+
if os.path.exists(file_path):
|
| 78 |
+
return
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
async with aiohttp.ClientSession() as session:
|
| 82 |
+
async with session.get(url, timeout=10) as resp:
|
| 83 |
+
if resp.status == 200:
|
| 84 |
+
data = await resp.read()
|
| 85 |
+
with open(file_path, "wb") as f:
|
| 86 |
+
f.write(data)
|
| 87 |
+
except Exception:
|
| 88 |
+
pass
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# ── Animation lists ───────────────────────────────────────────────────────────
|
| 98 |
+
brain_chain = [
|
| 99 |
+
"🧠", "🧠💥", "💭🧠", "🤯", "🧠🔥", "💡🧠", "🤔🧠",
|
| 100 |
+
"💥🧠💥", "🧠📤", "📤🗑️", "🗑️💨", "🧠❌", "🗑️", "✅"
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
clock_ani = [
|
| 104 |
+
"🕛", "🕧", "🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔"
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
police_ani = [
|
| 108 |
+
"🚓", "🚓💨", "🚔", "🚔💨", "🚨", "🚨🚨", "👮", "👮♂️🚓",
|
| 109 |
+
"🚓🚨👮", "🚨🚨👮♂️", "👮♂️🔦"
|
| 110 |
+
]
|
| 111 |
+
|
| 112 |
+
moon_ani = [
|
| 113 |
+
"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑", "🌒",
|
| 114 |
+
"🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑", "🌒", "🌓", "🌔",
|
| 115 |
+
"🌕", "🌕✨", "🌕🌟", "🌕💫", "🌕⭐", "🌕🌟✨", "🌔", "🌓",
|
| 116 |
+
"🌒", "🌑", "🌙", "🌙✨"
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
bomb_ettu = [
|
| 120 |
+
"💣", "💣💣", "💣💣💣", "💣💣💣💣", "💥", "💥💥", "💥💥💥", "🔥💥", "☠️"
|
| 121 |
+
]
|
| 122 |
+
|
| 123 |
+
hack_you = [
|
| 124 |
+
"🖥️ Booting...", "🔍 Scanning target...", "🔓 Bypassing firewall...",
|
| 125 |
+
"💻 Injecting payload...", "📡 Connecting...", "🔑 Cracking password...",
|
| 126 |
+
"📂 Accessing files...", "📊 Downloading data...", "🗄️ Extracting...",
|
| 127 |
+
"📤 Uploading backdoor...", "🔐 Locking access...", "🎭 Covering tracks...",
|
| 128 |
+
"🧹 Cleaning logs...", "⚙️ Finalizing...", "✅ Access granted!",
|
| 129 |
+
"📁 All data secured.", "🏴☠️ Mission complete.", "😎 You've been hacked!"
|
| 130 |
+
]
|
| 131 |
+
|
| 132 |
+
love_siren = [
|
| 133 |
+
"❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "❤️🔥",
|
| 134 |
+
"💗", "💓", "💞", "💕", "💝", "💖", "💘", "💟", "❣️", "❤️",
|
| 135 |
+
"💑", "👫", "💏", "💞", "💕❤️", "💖💖", "💗💗", "💓❤️💓",
|
| 136 |
+
"❤️🔥❤️", "💘💘", "💞💞💞", "💖✨", "💝🌹", "🌹❤️", "True Love💞"
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
kill_you = [
|
| 140 |
+
"🔫", "🔫💨", "😵", "😵💫", "💀", "⚰️🕯️", "🪦", "😱💀",
|
| 141 |
+
"🔫😵", "💀☠️", "⚰️", "☠️"
|
| 142 |
+
]
|
| 143 |
+
|
| 144 |
+
slap_ani = ["Slapping...", "SLAP! 👋", "Ouch! 💥", "👋💥", "😵"]
|
| 145 |
+
pat_ani = ["Patting...", "Pat pat... ✨", "Good job! 💖", "✋✨", "😊"]
|
| 146 |
+
hug_ani = ["Hugging...", "HUG! 🤗", "Warm hugs! ❤️", "🫂❤️", "✨"]
|
| 147 |
+
kiss_ani = ["Kissing...", "KISS! 💋", "Muah! 💘", "😘💋", "🔥"]
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
| 151 |
+
|
| 152 |
+
async def send_gif_with_caption(client: Client, chat_id: int, gif_key: str, caption: str):
|
| 153 |
+
"""Attempt to send a GIF with a caption."""
|
| 154 |
+
url = await get_random_gif(gif_key)
|
| 155 |
+
if not url:
|
| 156 |
+
return None
|
| 157 |
+
|
| 158 |
+
try:
|
| 159 |
+
# Try sending by URL first
|
| 160 |
+
return await client.send_animation(chat_id, url, caption=caption)
|
| 161 |
+
except Exception as e:
|
| 162 |
+
# Fallback: Download and send
|
| 163 |
+
try:
|
| 164 |
+
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
|
| 165 |
+
async with aiohttp.ClientSession(headers=headers) as session:
|
| 166 |
+
async with session.get(url, timeout=20) as resp:
|
| 167 |
+
if resp.status == 200:
|
| 168 |
+
data = await resp.read()
|
| 169 |
+
temp_name = f"temp_{gif_key}_{random.randint(100,999)}.gif"
|
| 170 |
+
with open(temp_name, "wb") as f:
|
| 171 |
+
f.write(data)
|
| 172 |
+
|
| 173 |
+
sent = await client.send_animation(chat_id, temp_name, caption=caption)
|
| 174 |
+
os.remove(temp_name)
|
| 175 |
+
return sent
|
| 176 |
+
else:
|
| 177 |
+
LOGGER.warning(f"GIF Download failed for {gif_key} ({url}): Status {resp.status}")
|
| 178 |
+
except Exception as e2:
|
| 179 |
+
LOGGER.warning(f"GIF Fallback failed for {gif_key} ({url}): {e2}")
|
| 180 |
+
return None
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
async def animate(client: Client, message: Message, gif_key: str, frames: list, action_verb: str, sleep: float = 0.5):
|
| 184 |
+
"""Perform text animation, then send GIF with caption."""
|
| 185 |
+
if not message.reply_to_message:
|
| 186 |
+
await message.reply_text(f"❗ Please reply to a user to {gif_key} them!")
|
| 187 |
+
return
|
| 188 |
+
|
| 189 |
+
sender = message.from_user.mention
|
| 190 |
+
target = message.reply_to_message.from_user.mention
|
| 191 |
+
caption = f"✨ {sender} {action_verb} {target}! ✨"
|
| 192 |
+
|
| 193 |
+
# Start text animation
|
| 194 |
+
msg = await message.reply_text(frames[0])
|
| 195 |
+
|
| 196 |
+
# Avoid FloodWait: Limit to ~8 edits if the list is long
|
| 197 |
+
total_frames = len(frames)
|
| 198 |
+
step = 1
|
| 199 |
+
if total_frames > 8:
|
| 200 |
+
step = total_frames // 7
|
| 201 |
+
if step == 0: step = 1
|
| 202 |
+
|
| 203 |
+
for x in range(step, total_frames, step):
|
| 204 |
+
try:
|
| 205 |
+
await msg.edit_text(frames[x])
|
| 206 |
+
await asyncio.sleep(sleep)
|
| 207 |
+
except FloodWait as e:
|
| 208 |
+
await asyncio.sleep(e.value)
|
| 209 |
+
except RPCError:
|
| 210 |
+
break
|
| 211 |
+
|
| 212 |
+
# Send the GIF with caption
|
| 213 |
+
gif_msg = await send_gif_with_caption(client, message.chat.id, gif_key, caption)
|
| 214 |
+
|
| 215 |
+
# Only delete the temporary text animation message if GIF succeeded
|
| 216 |
+
# Otherwise, edit it to keep the result visible
|
| 217 |
+
if gif_msg:
|
| 218 |
+
try:
|
| 219 |
+
await msg.delete()
|
| 220 |
+
except:
|
| 221 |
+
pass
|
| 222 |
+
else:
|
| 223 |
+
try:
|
| 224 |
+
await msg.edit_text(caption)
|
| 225 |
+
except:
|
| 226 |
+
pass
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
# ── Commands ──────────────────────────────────────────────────────────────────
|
| 232 |
+
@pbot.on_message(filters.command("dlanimech"))
|
| 233 |
+
async def dlanimech(client: Client, message: Message):
|
| 234 |
+
"""Zip and send animation cache to Sudoers in PM."""
|
| 235 |
+
if message.from_user.id not in SUDOERS:
|
| 236 |
+
await message.reply_text("❌ This command is only for Sudoers/Owners!")
|
| 237 |
+
return
|
| 238 |
+
|
| 239 |
+
msg = await message.reply_text("📦 Zipping animation cache...")
|
| 240 |
+
|
| 241 |
+
zip_path = "anim_cache.zip"
|
| 242 |
+
try:
|
| 243 |
+
# Create zip
|
| 244 |
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
| 245 |
+
for root, dirs, files in os.walk(CACHE_DIR):
|
| 246 |
+
for file in files:
|
| 247 |
+
zipf.write(
|
| 248 |
+
os.path.join(root, file),
|
| 249 |
+
os.path.relpath(os.path.join(root, file), os.path.join(CACHE_DIR, '..'))
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
# Send in PM
|
| 253 |
+
await client.send_document(
|
| 254 |
+
chat_id=message.from_user.id,
|
| 255 |
+
document=zip_path,
|
| 256 |
+
caption="📂 Here is the current Animation Cache!",
|
| 257 |
+
file_name="animation_cache.zip"
|
| 258 |
+
)
|
| 259 |
+
await msg.edit_text("✅ Sent to your PM!")
|
| 260 |
+
except Exception as e:
|
| 261 |
+
await msg.edit_text(f"❌ Error: {e}")
|
| 262 |
+
finally:
|
| 263 |
+
if os.path.exists(zip_path):
|
| 264 |
+
os.remove(zip_path)
|
| 265 |
+
|
| 266 |
+
@pbot.on_message(filters.command("brain") & filters.group)
|
| 267 |
+
@DisableAbleCommandHandler("brain")
|
| 268 |
+
async def brainanimation(client: Client, message: Message):
|
| 269 |
+
await animate(client, message, "brain", brain_chain, "put brain in dustbin for")
|
| 270 |
+
|
| 271 |
+
@pbot.on_message(filters.command("clock") & filters.group)
|
| 272 |
+
@DisableAbleCommandHandler("clock")
|
| 273 |
+
async def clockanimation(client: Client, message: Message):
|
| 274 |
+
await animate(client, message, "clock", clock_ani, "reminded time to")
|
| 275 |
+
|
| 276 |
+
@pbot.on_message(filters.command("police") & filters.group)
|
| 277 |
+
@DisableAbleCommandHandler("police")
|
| 278 |
+
async def policeanimation(client: Client, message: Message):
|
| 279 |
+
await animate(client, message, "police", police_ani, "called police for")
|
| 280 |
+
|
| 281 |
+
@pbot.on_message(filters.command("moon") & filters.group)
|
| 282 |
+
@DisableAbleCommandHandler("moon")
|
| 283 |
+
async def moonanimation(client: Client, message: Message):
|
| 284 |
+
await animate(client, message, "moon", moon_ani, "wished good night to")
|
| 285 |
+
|
| 286 |
+
@pbot.on_message(filters.command("bombs") & filters.group)
|
| 287 |
+
@DisableAbleCommandHandler("bombs")
|
| 288 |
+
async def bombs(client: Client, message: Message):
|
| 289 |
+
await animate(client, message, "bombs", bomb_ettu, "bombed")
|
| 290 |
+
|
| 291 |
+
@pbot.on_message(filters.command("hack") & filters.group)
|
| 292 |
+
@DisableAbleCommandHandler("hack")
|
| 293 |
+
async def hack(client: Client, message: Message):
|
| 294 |
+
await animate(client, message, "hack", hack_you, "hacked")
|
| 295 |
+
|
| 296 |
+
@pbot.on_message(filters.command("love") & filters.group)
|
| 297 |
+
@DisableAbleCommandHandler("love")
|
| 298 |
+
async def love(client: Client, message: Message):
|
| 299 |
+
await animate(client, message, "love", love_siren, "is expressing love to")
|
| 300 |
+
|
| 301 |
+
@pbot.on_message(filters.command("kill") & filters.group)
|
| 302 |
+
@DisableAbleCommandHandler("kill")
|
| 303 |
+
async def kill(client: Client, message: Message):
|
| 304 |
+
await animate(client, message, "kill", kill_you, "killed")
|
| 305 |
+
|
| 306 |
+
@pbot.on_message(filters.command("slap") & filters.group)
|
| 307 |
+
@DisableAbleCommandHandler("slap")
|
| 308 |
+
async def slap(client: Client, message: Message):
|
| 309 |
+
await animate(client, message, "slap", slap_ani, "slapped")
|
| 310 |
+
|
| 311 |
+
@pbot.on_message(filters.command("pat") & filters.group)
|
| 312 |
+
@DisableAbleCommandHandler("pat")
|
| 313 |
+
async def pat(client: Client, message: Message):
|
| 314 |
+
await animate(client, message, "pat", pat_ani, "patted")
|
| 315 |
+
|
| 316 |
+
@pbot.on_message(filters.command("hug") & filters.group)
|
| 317 |
+
@DisableAbleCommandHandler("hug")
|
| 318 |
+
async def hug(client: Client, message: Message):
|
| 319 |
+
await animate(client, message, "hug", hug_ani, "hugged")
|
| 320 |
+
|
| 321 |
+
@pbot.on_message(filters.command("kiss") & filters.group)
|
| 322 |
+
@DisableAbleCommandHandler("kiss")
|
| 323 |
+
async def kiss(client: Client, message: Message):
|
| 324 |
+
await animate(client, message, "kiss", kiss_ani, "kissed")
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
__mod_name__ = "Animation"
|
| 329 |
+
__help__ = """
|
| 330 |
+
*ғᴀᴋᴇ ᴀɴɪᴍᴀᴛɪᴏɴ ᴄᴏᴍᴍᴀɴᴅs*
|
| 331 |
+
• `/love` — ʟᴏᴠᴇ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 332 |
+
• `/hack` — ʜᴀᴄᴋ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 333 |
+
• `/moon` — ᴍᴏᴏɴ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 334 |
+
• `/kill` — ᴋɪʟʟ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 335 |
+
• `/slap` — sʟᴀᴩ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 336 |
+
• `/pat` — ᴩᴀᴛ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 337 |
+
• `/hug` — ʜᴜɢ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 338 |
+
• `/kiss` — ᴋɪss ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 339 |
+
• `/bombs` — ʙᴏᴍʙ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 340 |
+
• `/police` — ᴩᴏʟɪᴄᴇ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 341 |
+
• `/brain` — ʙʀᴀɪɴ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 342 |
+
• `/clock` — ᴄʟᴏᴄᴋ ᴀɴɪᴍᴀᴛɪᴏɴ
|
| 343 |
+
"""
|
| 344 |
+
|
QueenNoxi/modules/anime.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import random
|
| 3 |
+
import aiohttp
|
| 4 |
+
from pyrogram import filters, Client, enums
|
| 5 |
+
from pyrogram.types import (
|
| 6 |
+
Message,
|
| 7 |
+
InlineKeyboardMarkup,
|
| 8 |
+
InlineKeyboardButton,
|
| 9 |
+
CallbackQuery
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
from QueenNoxi import pbot, OWNER_ID
|
| 13 |
+
from QueenNoxi.modules.disable import DisableAbleCommandHandler
|
| 14 |
+
|
| 15 |
+
QUOTES_IMG = [
|
| 16 |
+
"https://i.imgur.com/Iub4RYj.jpg",
|
| 17 |
+
"https://i.imgur.com/uvNMdIl.jpg",
|
| 18 |
+
"https://i.imgur.com/YOBOntg.jpg",
|
| 19 |
+
"https://i.imgur.com/fFpO2ZQ.jpg",
|
| 20 |
+
"https://i.imgur.com/f0xZceK.jpg",
|
| 21 |
+
"https://i.imgur.com/RlVcCip.jpg",
|
| 22 |
+
"https://i.imgur.com/CjpqLRF.jpg",
|
| 23 |
+
"https://i.imgur.com/8BHZDk6.jpg",
|
| 24 |
+
"https://i.imgur.com/8bHeMgy.jpg",
|
| 25 |
+
"https://i.imgur.com/5K3lMvr.jpg",
|
| 26 |
+
"https://i.imgur.com/NTzw4RN.jpg",
|
| 27 |
+
"https://i.imgur.com/wJxryAn.jpg",
|
| 28 |
+
"https://i.imgur.com/9L0DWzC.jpg",
|
| 29 |
+
"https://i.imgur.com/sBe8TTs.jpg",
|
| 30 |
+
"https://i.imgur.com/1Au8gdf.jpg",
|
| 31 |
+
"https://i.imgur.com/28hFQeU.jpg",
|
| 32 |
+
"https://i.imgur.com/Qvc03JY.jpg",
|
| 33 |
+
"https://i.imgur.com/gSX6Xlf.jpg",
|
| 34 |
+
"https://i.imgur.com/iP26Hwa.jpg",
|
| 35 |
+
"https://i.imgur.com/uSsJoX8.jpg",
|
| 36 |
+
"https://i.imgur.com/OvX3oHB.jpg",
|
| 37 |
+
"https://i.imgur.com/JMWuksm.jpg",
|
| 38 |
+
"https://i.imgur.com/lhM3fib.jpg",
|
| 39 |
+
"https://i.imgur.com/64IYKkw.jpg",
|
| 40 |
+
"https://i.imgur.com/nMbyA3J.jpg",
|
| 41 |
+
"https://i.imgur.com/7KFQhY3.jpg",
|
| 42 |
+
"https://i.imgur.com/mlKb7zt.jpg",
|
| 43 |
+
"https://i.imgur.com/JCQGJVw.jpg",
|
| 44 |
+
"https://i.imgur.com/hSFYDEz.jpg",
|
| 45 |
+
"https://i.imgur.com/PQRjAgl.jpg",
|
| 46 |
+
"https://i.imgur.com/ot9624U.jpg",
|
| 47 |
+
"https://i.imgur.com/iXmqN9y.jpg",
|
| 48 |
+
"https://i.imgur.com/RhNBeGr.jpg",
|
| 49 |
+
"https://i.imgur.com/tcMVNa8.jpg",
|
| 50 |
+
"https://i.imgur.com/LrVg810.jpg",
|
| 51 |
+
"https://i.imgur.com/TcWfQlz.jpg",
|
| 52 |
+
"https://i.imgur.com/muAUdvJ.jpg",
|
| 53 |
+
"https://i.imgur.com/AtC7ZRV.jpg",
|
| 54 |
+
"https://i.imgur.com/sCObQCQ.jpg",
|
| 55 |
+
"https://i.imgur.com/AJFDI1r.jpg",
|
| 56 |
+
"https://i.imgur.com/TCgmRrH.jpg",
|
| 57 |
+
"https://i.imgur.com/LMdmhJU.jpg",
|
| 58 |
+
"https://i.imgur.com/eyyax0N.jpg",
|
| 59 |
+
"https://i.imgur.com/YtYxV66.jpg",
|
| 60 |
+
"https://i.imgur.com/289f9cebe37f31a943f98.jpg",
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
async def anime_quote():
|
| 64 |
+
url = "https://animechan.vercel.app/api/random"
|
| 65 |
+
async with aiohttp.ClientSession() as session:
|
| 66 |
+
async with session.get(url) as response:
|
| 67 |
+
if response.status == 200:
|
| 68 |
+
dic = await response.json()
|
| 69 |
+
return dic["quote"], dic["character"], dic["anime"]
|
| 70 |
+
return "No quote found.", "Unknown", "Unknown"
|
| 71 |
+
|
| 72 |
+
@pbot.on_message(filters.command("quote"))
|
| 73 |
+
@DisableAbleCommandHandler("quote")
|
| 74 |
+
async def quotes(client: Client, message: Message):
|
| 75 |
+
quote, character, anime = await anime_quote()
|
| 76 |
+
msg = f"<i>❝{quote}❞</i>\n\n<b>{character} from {anime}</b>"
|
| 77 |
+
keyboard = InlineKeyboardMarkup(
|
| 78 |
+
[[InlineKeyboardButton(text="Change🔁", callback_data="change_quote")]]
|
| 79 |
+
)
|
| 80 |
+
await message.reply_text(
|
| 81 |
+
msg,
|
| 82 |
+
reply_markup=keyboard,
|
| 83 |
+
parse_mode=enums.ParseMode.HTML,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
@pbot.on_callback_query(filters.regex(r"change_quote|quote_change"))
|
| 87 |
+
async def change_quote_btn(client: Client, query: CallbackQuery):
|
| 88 |
+
quote, character, anime = await anime_quote()
|
| 89 |
+
msg = f"<i>❝{quote}❞</i>\n\n<b>{character} from {anime}</b>"
|
| 90 |
+
keyboard = InlineKeyboardMarkup(
|
| 91 |
+
[[InlineKeyboardButton(text="ᴄʜᴀɴɢᴇ🔁", callback_data="quote_change")]]
|
| 92 |
+
)
|
| 93 |
+
try:
|
| 94 |
+
await query.message.edit_text(msg, reply_markup=keyboard, parse_mode=enums.ParseMode.HTML)
|
| 95 |
+
except:
|
| 96 |
+
await query.answer("Error or same content.")
|
| 97 |
+
|
| 98 |
+
@pbot.on_message(filters.command("animequotes"))
|
| 99 |
+
@DisableAbleCommandHandler("animequotes")
|
| 100 |
+
async def animequotes_cmd(client: Client, message: Message):
|
| 101 |
+
if message.reply_to_message:
|
| 102 |
+
await message.reply_to_message.reply_photo(random.choice(QUOTES_IMG))
|
| 103 |
+
else:
|
| 104 |
+
await message.reply_photo(random.choice(QUOTES_IMG))
|
| 105 |
+
|
| 106 |
+
__mod_name__ = "Quotes"
|
| 107 |
+
__help__ = """
|
| 108 |
+
• `/quote`: Get a random anime quote.
|
| 109 |
+
• `/animequotes`: Get a random anime quote image.
|
| 110 |
+
"""
|
QueenNoxi/modules/animez.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import html
|
| 3 |
+
import textwrap
|
| 4 |
+
import aiohttp
|
| 5 |
+
import bs4
|
| 6 |
+
from jikanpy import AioJikan
|
| 7 |
+
from pyrogram import filters, Client
|
| 8 |
+
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
|
| 9 |
+
|
| 10 |
+
from QueenNoxi import pbot
|
| 11 |
+
from QueenNoxi.modules.disable import DisableAbleCommandHandler
|
| 12 |
+
|
| 13 |
+
info_btn = "More Information"
|
| 14 |
+
close_btn = "Close ❌"
|
| 15 |
+
|
| 16 |
+
def shorten(description, info="anilist.co"):
|
| 17 |
+
msg = ""
|
| 18 |
+
if len(description) > 700:
|
| 19 |
+
description = description[0:500] + "...."
|
| 20 |
+
msg += f"\n**Description**: _{description}_[Read More]({info})"
|
| 21 |
+
else:
|
| 22 |
+
msg += f"\n**Description**:_{description}_"
|
| 23 |
+
return msg
|
| 24 |
+
|
| 25 |
+
def t(milliseconds: int) -> str:
|
| 26 |
+
seconds, milliseconds = divmod(int(milliseconds), 1000)
|
| 27 |
+
minutes, seconds = divmod(seconds, 60)
|
| 28 |
+
hours, minutes = divmod(minutes, 60)
|
| 29 |
+
days, hours = divmod(hours, 24)
|
| 30 |
+
tmp = (
|
| 31 |
+
((str(days) + " Days, ") if days else "")
|
| 32 |
+
+ ((str(hours) + " Hours, ") if hours else "")
|
| 33 |
+
+ ((str(minutes) + " Minutes, ") if minutes else "")
|
| 34 |
+
+ ((str(seconds) + " Seconds, ") if seconds else "")
|
| 35 |
+
+ ((str(milliseconds) + " ms, ") if milliseconds else "")
|
| 36 |
+
)
|
| 37 |
+
return tmp[:-2]
|
| 38 |
+
|
| 39 |
+
airing_query = """
|
| 40 |
+
query ($id: Int,$search: String) {
|
| 41 |
+
Media (id: $id, type: ANIME,search: $search) {
|
| 42 |
+
id
|
| 43 |
+
episodes
|
| 44 |
+
title {
|
| 45 |
+
romaji
|
| 46 |
+
english
|
| 47 |
+
native
|
| 48 |
+
}
|
| 49 |
+
nextAiringEpisode {
|
| 50 |
+
airingAt
|
| 51 |
+
timeUntilAiring
|
| 52 |
+
episode
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
anime_query = """
|
| 59 |
+
query ($id: Int,$search: String) {
|
| 60 |
+
Media (id: $id, type: ANIME,search: $search) {
|
| 61 |
+
id
|
| 62 |
+
title {
|
| 63 |
+
romaji
|
| 64 |
+
english
|
| 65 |
+
native
|
| 66 |
+
}
|
| 67 |
+
description (asHtml: false)
|
| 68 |
+
startDate{
|
| 69 |
+
year
|
| 70 |
+
}
|
| 71 |
+
episodes
|
| 72 |
+
season
|
| 73 |
+
type
|
| 74 |
+
format
|
| 75 |
+
status
|
| 76 |
+
duration
|
| 77 |
+
siteUrl
|
| 78 |
+
studios{
|
| 79 |
+
nodes{
|
| 80 |
+
name
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
trailer{
|
| 84 |
+
id
|
| 85 |
+
site
|
| 86 |
+
thumbnail
|
| 87 |
+
}
|
| 88 |
+
averageScore
|
| 89 |
+
genres
|
| 90 |
+
bannerImage
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
"""
|
| 94 |
+
|
| 95 |
+
character_query = """
|
| 96 |
+
query ($query: String) {
|
| 97 |
+
Character (search: $query) {
|
| 98 |
+
id
|
| 99 |
+
name {
|
| 100 |
+
first
|
| 101 |
+
last
|
| 102 |
+
full
|
| 103 |
+
}
|
| 104 |
+
siteUrl
|
| 105 |
+
image {
|
| 106 |
+
large
|
| 107 |
+
}
|
| 108 |
+
description
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
"""
|
| 112 |
+
|
| 113 |
+
manga_query = """
|
| 114 |
+
query ($id: Int,$search: String) {
|
| 115 |
+
Media (id: $id, type: MANGA,search: $search) {
|
| 116 |
+
id
|
| 117 |
+
title {
|
| 118 |
+
romaji
|
| 119 |
+
english
|
| 120 |
+
native
|
| 121 |
+
}
|
| 122 |
+
description (asHtml: false)
|
| 123 |
+
startDate{
|
| 124 |
+
year
|
| 125 |
+
}
|
| 126 |
+
type
|
| 127 |
+
format
|
| 128 |
+
status
|
| 129 |
+
siteUrl
|
| 130 |
+
averageScore
|
| 131 |
+
genres
|
| 132 |
+
bannerImage
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
url = "https://graphql.anilist.co"
|
| 138 |
+
|
| 139 |
+
async def get_response(query, variables):
|
| 140 |
+
async with aiohttp.ClientSession() as session:
|
| 141 |
+
async with session.post(url, json={"query": query, "variables": variables}) as resp:
|
| 142 |
+
return await resp.json()
|
| 143 |
+
|
| 144 |
+
@pbot.on_message(filters.command("airing"))
|
| 145 |
+
@DisableAbleCommandHandler("airing")
|
| 146 |
+
async def airing(client: Client, message: Message):
|
| 147 |
+
search_str = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 148 |
+
if not search_str and message.reply_to_message:
|
| 149 |
+
search_str = message.reply_to_message.text
|
| 150 |
+
|
| 151 |
+
if not search_str:
|
| 152 |
+
await message.reply_text("Tell Anime Name :) ( /airing <anime name>)")
|
| 153 |
+
return
|
| 154 |
+
|
| 155 |
+
data = await get_response(airing_query, {"search": search_str})
|
| 156 |
+
if not data or "data" not in data or not data["data"]["Media"]:
|
| 157 |
+
await message.reply_text("Anime not found")
|
| 158 |
+
return
|
| 159 |
+
|
| 160 |
+
response = data["data"]["Media"]
|
| 161 |
+
msg = f"**Name**: **{response['title']['romaji']}**(`{response['title']['native']}`)\n**ID**: `{response['id']}`"
|
| 162 |
+
if response["nextAiringEpisode"]:
|
| 163 |
+
time_val = response["nextAiringEpisode"]["timeUntilAiring"] * 1000
|
| 164 |
+
time_str = t(time_val)
|
| 165 |
+
msg += f"\n**Episode**: `{response['nextAiringEpisode']['episode']}`\n**Airing In**: `{time_str}`"
|
| 166 |
+
else:
|
| 167 |
+
msg += f"\n**Episode**:{response['episodes']}\n**Status**: `N/A`"
|
| 168 |
+
await message.reply_text(msg)
|
| 169 |
+
|
| 170 |
+
@pbot.on_message(filters.command("anime"))
|
| 171 |
+
@DisableAbleCommandHandler("anime")
|
| 172 |
+
async def anime(client: Client, message: Message):
|
| 173 |
+
search = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 174 |
+
if not search and message.reply_to_message:
|
| 175 |
+
search = message.reply_to_message.text
|
| 176 |
+
|
| 177 |
+
if not search:
|
| 178 |
+
await message.reply_text("Format : /anime < anime name >")
|
| 179 |
+
return
|
| 180 |
+
|
| 181 |
+
data = await get_response(anime_query, {"search": search})
|
| 182 |
+
if not data or "data" not in data or not data["data"]["Media"]:
|
| 183 |
+
await message.reply_text("Anime not found")
|
| 184 |
+
return
|
| 185 |
+
|
| 186 |
+
json_data = data["data"]["Media"]
|
| 187 |
+
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**: `"
|
| 188 |
+
msg += ", ".join(json_data["genres"]) + "`\n"
|
| 189 |
+
msg += "**Studios**: `"
|
| 190 |
+
msg += ", ".join([x['name'] for x in json_data["studios"]["nodes"]]) + "`\n"
|
| 191 |
+
|
| 192 |
+
info = json_data.get("siteUrl")
|
| 193 |
+
trailer = json_data.get("trailer", None)
|
| 194 |
+
if trailer:
|
| 195 |
+
trailer_id = trailer.get("id", None)
|
| 196 |
+
site = trailer.get("site", None)
|
| 197 |
+
if site == "youtube":
|
| 198 |
+
trailer = "https://youtu.be/" + trailer_id
|
| 199 |
+
|
| 200 |
+
description = json_data.get("description", "N/A").replace("<i>", "").replace("</i>", "").replace("<br>", "")
|
| 201 |
+
msg += train_shorten(description, info)
|
| 202 |
+
image = json_data.get("bannerImage", None)
|
| 203 |
+
|
| 204 |
+
buttons = [
|
| 205 |
+
[
|
| 206 |
+
InlineKeyboardButton("ᴍᴏʀᴇ ɪɴғᴏ", url=info),
|
| 207 |
+
]
|
| 208 |
+
]
|
| 209 |
+
if trailer:
|
| 210 |
+
buttons[0].append(InlineKeyboardButton("ᴛʀᴀɪʟᴇʀ", url=str(trailer)))
|
| 211 |
+
|
| 212 |
+
if image:
|
| 213 |
+
try:
|
| 214 |
+
await message.reply_photo(photo=image, caption=msg,保护_markup=InlineKeyboardMarkup(buttons))
|
| 215 |
+
except Exception:
|
| 216 |
+
msg += f" [〽️]({image})"
|
| 217 |
+
await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons))
|
| 218 |
+
else:
|
| 219 |
+
await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons))
|
| 220 |
+
|
| 221 |
+
def train_shorten(description, info):
|
| 222 |
+
if len(description) > 700:
|
| 223 |
+
return f"\n**Description**: _{description[:500]}...._[Read More]({info})"
|
| 224 |
+
return f"\n**Description**: _{description}_"
|
| 225 |
+
|
| 226 |
+
@pbot.on_message(filters.command("character"))
|
| 227 |
+
@DisableAbleCommandHandler("character")
|
| 228 |
+
async def character(client: Client, message: Message):
|
| 229 |
+
search = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 230 |
+
if not search and message.reply_to_message:
|
| 231 |
+
search = message.reply_to_message.text
|
| 232 |
+
|
| 233 |
+
if not search:
|
| 234 |
+
await message.reply_text("Format : /character < character name >")
|
| 235 |
+
return
|
| 236 |
+
|
| 237 |
+
data = await get_response(character_query, {"query": search})
|
| 238 |
+
if not data or "data" not in data or not data["data"]["Character"]:
|
| 239 |
+
await message.reply_text("Character not found")
|
| 240 |
+
return
|
| 241 |
+
|
| 242 |
+
json_data = data["data"]["Character"]
|
| 243 |
+
msg = f"**{json_data.get('name').get('full')}**(`{json_data.get('name').get('native')}`)\n"
|
| 244 |
+
description = f"{json_data['description']}"
|
| 245 |
+
site_url = json_data.get("siteUrl")
|
| 246 |
+
msg += train_shorten(description, site_url)
|
| 247 |
+
image = json_data.get("image", None)
|
| 248 |
+
|
| 249 |
+
if image:
|
| 250 |
+
await message.reply_photo(photo=image["large"], caption=msg.replace("<b>", "").replace("</b>", ""))
|
| 251 |
+
else:
|
| 252 |
+
await message.reply_text(msg.replace("<b>", "").replace("</b>", ""))
|
| 253 |
+
|
| 254 |
+
@pbot.on_message(filters.command("manga"))
|
| 255 |
+
@DisableAbleCommandHandler("manga")
|
| 256 |
+
async def manga(client: Client, message: Message):
|
| 257 |
+
search = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 258 |
+
if not search and message.reply_to_message:
|
| 259 |
+
search = message.reply_to_message.text
|
| 260 |
+
|
| 261 |
+
if not search:
|
| 262 |
+
await message.reply_text("Format : /manga < manga name >")
|
| 263 |
+
return
|
| 264 |
+
|
| 265 |
+
data = await get_response(manga_query, {"search": search})
|
| 266 |
+
if not data or "data" not in data or not data["data"]["Media"]:
|
| 267 |
+
await message.reply_text("Manga not found")
|
| 268 |
+
return
|
| 269 |
+
|
| 270 |
+
json_data = data["data"]["Media"]
|
| 271 |
+
msg = f"**{json_data['title']['romaji']}**(`{json_data['title']['native']}`)\n"
|
| 272 |
+
if json_data["startDate"].get("year"):
|
| 273 |
+
msg += f"**Start Date**: `{json_data['startDate']['year']}`\n"
|
| 274 |
+
if json_data.get("status"):
|
| 275 |
+
msg += f"**Status**: `{json_data['status']}`\n"
|
| 276 |
+
if json_data.get("averageScore"):
|
| 277 |
+
msg += f"**Score**: `{json_data['averageScore']}`\n"
|
| 278 |
+
|
| 279 |
+
msg += "**Genres**: " + ", ".join(json_data.get("genres", [])) + "\n"
|
| 280 |
+
|
| 281 |
+
info = json_data["siteUrl"]
|
| 282 |
+
description = json_data.get('description', 'N/A')
|
| 283 |
+
msg += train_shorten(description, info)
|
| 284 |
+
|
| 285 |
+
image = json_data.get("bannerImage")
|
| 286 |
+
buttons = [[InlineKeyboardButton("More Info", url=info)]]
|
| 287 |
+
|
| 288 |
+
if image:
|
| 289 |
+
try:
|
| 290 |
+
await message.reply_photo(photo=image, caption=msg, reply_markup=InlineKeyboardMarkup(buttons))
|
| 291 |
+
except Exception:
|
| 292 |
+
msg += f" [〽️]({image})"
|
| 293 |
+
await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons))
|
| 294 |
+
else:
|
| 295 |
+
await message.reply_text(msg, reply_markup=InlineKeyboardMarkup(buttons))
|
| 296 |
+
|
| 297 |
+
@pbot.on_message(filters.command("user"))
|
| 298 |
+
@DisableAbleCommandHandler("user")
|
| 299 |
+
async def user_info(client: Client, message: Message):
|
| 300 |
+
search_query = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 301 |
+
if not search_query:
|
| 302 |
+
await message.reply_text("Format : /user <username>")
|
| 303 |
+
return
|
| 304 |
+
|
| 305 |
+
async with AioJikan() as jikan:
|
| 306 |
+
try:
|
| 307 |
+
us = await jikan.user(search_query)
|
| 308 |
+
except Exception:
|
| 309 |
+
await message.reply_text("Username not found.")
|
| 310 |
+
return
|
| 311 |
+
|
| 312 |
+
img = us.get("images", {}).get("jpg", {}).get("image_url", "https://cdn.myanimelist.net/images/questionmark_50.gif")
|
| 313 |
+
|
| 314 |
+
birthday = us.get("birthday")
|
| 315 |
+
joined = us.get("joined")
|
| 316 |
+
|
| 317 |
+
caption = f"**ᴜsᴇʀɴᴀᴍᴇ**: [{us['username']}]({us['url']})\n\n"
|
| 318 |
+
caption += f"**ɢᴇɴᴅᴇʀ**: `{us.get('gender', 'Unknown')}`\n"
|
| 319 |
+
caption += f"**ʙɪʀᴛʜᴅᴀʏ**: `{birthday[:10] if birthday else 'Unknown'}`\n"
|
| 320 |
+
caption += f"**ᴊᴏɪɴᴇᴅ**: `{joined[:10] if joined else 'Unknown'}`\n"
|
| 321 |
+
|
| 322 |
+
anime_stats = us.get("statistics", {}).get("anime", {})
|
| 323 |
+
manga_stats = us.get("statistics", {}).get("manga", {})
|
| 324 |
+
|
| 325 |
+
caption += f"**ᴅᴀʏs ᴡᴀsᴛᴇᴅ ᴡᴀᴛᴄʜɪɴɢ ᴀɴɪᴍᴇ**: `{anime_stats.get('days_watched', 0)}`\n"
|
| 326 |
+
caption += f"**ᴅᴀʏs ᴡᴀsᴛᴇᴅ ʀᴇᴀᴅɪɴɢ ᴍᴀɴɢᴀ**: `{manga_stats.get('days_read', 0)}`\n\n"
|
| 327 |
+
|
| 328 |
+
about = us.get("about", "N/A")
|
| 329 |
+
if len(about) > 300:
|
| 330 |
+
about = about[:300] + "..."
|
| 331 |
+
caption += f"**ᴀʙᴏᴜᴛ**: {about}"
|
| 332 |
+
|
| 333 |
+
buttons = [
|
| 334 |
+
[InlineKeyboardButton(info_btn, url=us["url"])],
|
| 335 |
+
[InlineKeyboardButton(close_btn, callback_data=f"anime_close,{message.from_user.id}")]
|
| 336 |
+
]
|
| 337 |
+
|
| 338 |
+
await message.reply_photo(photo=img, caption=caption, reply_markup=InlineKeyboardMarkup(buttons))
|
| 339 |
+
|
| 340 |
+
@pbot.on_message(filters.command("upcoming"))
|
| 341 |
+
@DisableAbleCommandHandler("upcoming")
|
| 342 |
+
async def upcoming_anime(client: Client, message: Message):
|
| 343 |
+
async with AioJikan() as jikan:
|
| 344 |
+
upcomin = await jikan.top(type="anime", filter="upcoming")
|
| 345 |
+
|
| 346 |
+
upcoming_message = "Upcoming Anime:\n"
|
| 347 |
+
for i, entry in enumerate(upcomin["data"][:10]):
|
| 348 |
+
upcoming_message += f"{i + 1}. {entry['title']}\n"
|
| 349 |
+
|
| 350 |
+
await message.reply_text(upcoming_message)
|
| 351 |
+
|
| 352 |
+
async def search_site(message, query, site):
|
| 353 |
+
search_url = f"https://{site}.com/?s={query}"
|
| 354 |
+
async with aiohttp.ClientSession() as session:
|
| 355 |
+
async with session.get(search_url) as resp:
|
| 356 |
+
html_text = await resp.text()
|
| 357 |
+
|
| 358 |
+
soup = bs4.BeautifulSoup(html_text, "html.parser")
|
| 359 |
+
if site == "animekaizoku":
|
| 360 |
+
search_result = soup.find_all("h2", {"class": "post-title"})
|
| 361 |
+
else:
|
| 362 |
+
search_result = soup.find_all("h2", {"class": "title"})
|
| 363 |
+
|
| 364 |
+
if not search_result:
|
| 365 |
+
await message.reply_text(f"**No result found for** `{query}`")
|
| 366 |
+
return
|
| 367 |
+
|
| 368 |
+
result = f"**Search results for** `{query}`\n"
|
| 369 |
+
for entry in search_result:
|
| 370 |
+
if entry.text.strip() == "Nothing Found":
|
| 371 |
+
await message.reply_text(f"**No result found for** `{query}`")
|
| 372 |
+
return
|
| 373 |
+
|
| 374 |
+
link = entry.a["href"]
|
| 375 |
+
if site == "animekaizoku" and not link.startswith("http"):
|
| 376 |
+
link = "https://animekaizoku.com/" + link
|
| 377 |
+
|
| 378 |
+
name = html.escape(entry.text.strip())
|
| 379 |
+
result += f"• [{name}]({link})\n"
|
| 380 |
+
|
| 381 |
+
buttons = [[InlineKeyboardButton("See all results", url=search_url)]]
|
| 382 |
+
await message.reply_text(result, reply_markup=InlineKeyboardMarkup(buttons), disable_web_page_preview=True)
|
| 383 |
+
|
| 384 |
+
@pbot.on_message(filters.command("kaizoku"))
|
| 385 |
+
@DisableAbleCommandHandler("kaizoku")
|
| 386 |
+
async def kaizoku_cmd(client: Client, message: Message):
|
| 387 |
+
query = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 388 |
+
if not query:
|
| 389 |
+
await message.reply_text("Give something to search")
|
| 390 |
+
return
|
| 391 |
+
await search_site(message, query, "animekaizoku")
|
| 392 |
+
|
| 393 |
+
@pbot.on_message(filters.command("kayo"))
|
| 394 |
+
@DisableAbleCommandHandler("kayo")
|
| 395 |
+
async def kayo_cmd(client: Client, message: Message):
|
| 396 |
+
query = message.text.split(None, 1)[1] if len(message.command) > 1 else None
|
| 397 |
+
if not query:
|
| 398 |
+
await message.reply_text("Give something to search")
|
| 399 |
+
return
|
| 400 |
+
await search_site(message, query, "animekayo")
|
| 401 |
+
|
| 402 |
+
__mod_name__ = "Aɴɪᴍᴇ"
|
| 403 |
+
__help__ = """
|
| 404 |
+
ɢᴇᴛ ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀɴɪᴍᴇ, ᴍᴀɴɢᴀ ᴏʀ ᴄʜᴀʀᴀᴄᴛᴇʀs ғʀᴏᴍ [ᴀɴɪʟɪsᴛ](ᴀɴɪʟɪsᴛ.ᴄᴏ).
|
| 405 |
+
|
| 406 |
+
**ᴀᴠᴀɪʟᴀʙʟᴇ ᴄᴏᴍᴍᴀɴᴅs:**
|
| 407 |
+
• `/anime <anime>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴀɴɪᴍᴇ.
|
| 408 |
+
• `/character <ᴄʜᴀʀᴀᴄᴛᴇʀ>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴄʜᴀʀᴀᴄᴛᴇʀ.
|
| 409 |
+
• `/manga <ᴍᴀɴɢᴀ>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴛʜᴇ ᴍᴀɴɢᴀ.
|
| 410 |
+
• `/user <ᴜsᴇʀ>`: ʀᴇᴛᴜʀɴs ɪɴғᴏʀᴍᴀᴛɪᴏɴ ᴀʙᴏᴜᴛ ᴀ ᴍʏᴀɴɪᴍᴇʟɪsᴛ ᴜsᴇʀ.
|
| 411 |
+
• `/upcoming`: ʀᴇᴛᴜʀɴs ᴀ ʟɪsᴛ ᴏғ ɴᴇᴡ ᴀɴɪᴍᴇ ɪɴ ᴛʜᴇ ᴜᴘᴄᴏᴍɪɴɢ sᴇᴀsᴏɴs.
|
| 412 |
+
• `/kaizoku <ᴀɴɪᴍᴇ>`: sᴇᴀʀᴄʜ ᴀɴ ᴀɴɪᴍᴇ ᴏɴ ᴀɴɪᴍᴇᴋᴀɪᴢᴏᴋᴜ.ᴄᴏᴍ
|
| 413 |
+
• `/kayo <ᴀɴɪᴍᴇ>`: sᴇᴀʀ��ʜ ᴀɴ ᴀɴɪᴍᴇ ᴏɴ ᴀɴɪᴍᴇᴋᴀʏᴏ.ᴄᴏᴍ
|
| 414 |
+
• `/airing <ᴀɴɪᴍᴇ>`: ʀᴇᴛᴜʀɴs ᴀɴɪᴍᴇ ᴀɪɪɴɢ ɪɴғᴏ.
|
| 415 |
+
"""
|
QueenNoxi/modules/antiban.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram import Client
|
| 2 |
+
from pyrogram.raw import functions, types
|
| 3 |
+
from pyrogram.raw.base import Update
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@Client.on_raw_update()
|
| 7 |
+
async def channel_handler(client: Client, update: Update, _, chats: dict):
|
| 8 |
+
while True:
|
| 9 |
+
try:
|
| 10 |
+
# Check for message that are from channel
|
| 11 |
+
if not isinstance(update, types.UpdateNewChannelMessage) or not isinstance(
|
| 12 |
+
update.message.from_id, types.PeerChannel
|
| 13 |
+
):
|
| 14 |
+
return
|
| 15 |
+
# Basic data
|
| 16 |
+
message = update.message
|
| 17 |
+
chat_id = int(f"-100{message.peer_id.channel_id}")
|
| 18 |
+
channel_id = int(f"-100{message.from_id.channel_id}")
|
| 19 |
+
# Check enable or not
|
| 20 |
+
# Check for linked or free channel
|
| 21 |
+
if (
|
| 22 |
+
message.fwd_from
|
| 23 |
+
and message.fwd_from.saved_from_peer
|
| 24 |
+
== message.fwd_from.from_id
|
| 25 |
+
== message.from_id
|
| 26 |
+
) or channel_id == chat_id:
|
| 27 |
+
return
|
| 28 |
+
# Delete the message sent by channel and ban it
|
| 29 |
+
await client.send(
|
| 30 |
+
functions.channels.EditBanned(
|
| 31 |
+
channel=await client.resolve_peer(chat_id),
|
| 32 |
+
participant=await client.resolve_peer(channel_id),
|
| 33 |
+
banned_rights=types.ChatBannedRights(
|
| 34 |
+
until_date=0,
|
| 35 |
+
view_messages=True,
|
| 36 |
+
send_messages=True,
|
| 37 |
+
send_media=True,
|
| 38 |
+
send_stickers=True,
|
| 39 |
+
send_gifs=True,
|
| 40 |
+
send_games=True,
|
| 41 |
+
send_polls=True,
|
| 42 |
+
),
|
| 43 |
+
)
|
| 44 |
+
)
|
| 45 |
+
await client.delete_messages(chat_id, message.id)
|
| 46 |
+
await client.send_message(
|
| 47 |
+
int(chat_id),
|
| 48 |
+
f"#𝙰𝙽𝚃𝙸𝙲𝙷𝙰𝙽𝙽𝙴𝙻\n\n᛭ 𝚂𝙴𝙽𝙳𝙴𝚁 𝙸𝙳: `{channel_id}`\n᛭ 𝚃𝙰𝙺𝙴𝙽 𝙰𝙲𝚃𝙸𝙾𝙽: `DELETE BAN`",
|
| 49 |
+
disable_web_page_preview=True,
|
| 50 |
+
)
|
| 51 |
+
break
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(e)
|
| 54 |
+
break
|
QueenNoxi/modules/approve.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import html
|
| 2 |
+
from pyrogram import filters, Client, enums
|
| 3 |
+
from pyrogram.types import (
|
| 4 |
+
Message,
|
| 5 |
+
InlineKeyboardMarkup,
|
| 6 |
+
InlineKeyboardButton,
|
| 7 |
+
CallbackQuery
|
| 8 |
+
)
|
| 9 |
+
from pyrogram.errors import RPCError
|
| 10 |
+
|
| 11 |
+
import QueenNoxi.modules.sql.approve_sql as sql
|
| 12 |
+
from QueenNoxi import DRAGONS, pbot
|
| 13 |
+
from QueenNoxi.modules.disable import DisableAbleCommandHandler
|
| 14 |
+
from QueenNoxi.modules.helper_funcs.chat_status import user_admin, is_user_admin
|
| 15 |
+
from QueenNoxi.modules.helper_funcs.extraction import extract_user
|
| 16 |
+
from QueenNoxi.modules.log_channel import loggable
|
| 17 |
+
|
| 18 |
+
@pbot.on_message(filters.command("approve") & filters.group)
|
| 19 |
+
@DisableAbleCommandHandler("approve")
|
| 20 |
+
@user_admin
|
| 21 |
+
@loggable
|
| 22 |
+
async def approve(client: Client, message: Message) -> str:
|
| 23 |
+
chat = message.chat
|
| 24 |
+
user = message.from_user
|
| 25 |
+
user_id = await extract_user(message, message.command[1:])
|
| 26 |
+
if not user_id:
|
| 27 |
+
await message.reply_text("I don't know who you're talking about, you're going to need to specify a user!")
|
| 28 |
+
return ""
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
member = await chat.get_member(user_id)
|
| 32 |
+
except RPCError:
|
| 33 |
+
return ""
|
| 34 |
+
|
| 35 |
+
if member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER):
|
| 36 |
+
await message.reply_text("User is already admin - locks, blocklists, and antiflood already don't apply to them.")
|
| 37 |
+
return ""
|
| 38 |
+
|
| 39 |
+
if sql.is_approved(chat.id, user_id):
|
| 40 |
+
await message.reply_text(f"{member.user.mention} is already approved in {chat.title}")
|
| 41 |
+
return ""
|
| 42 |
+
|
| 43 |
+
sql.approve(chat.id, user_id)
|
| 44 |
+
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.")
|
| 45 |
+
|
| 46 |
+
return f"<b>{html.escape(chat.title)}:</b>\n#APPROVED\n<b>Admin:</b> {user.mention}\n<b>User:</b> {member.user.mention}"
|
| 47 |
+
|
| 48 |
+
@pbot.on_message(filters.command("unapprove") & filters.group)
|
| 49 |
+
@DisableAbleCommandHandler("unapprove")
|
| 50 |
+
@user_admin
|
| 51 |
+
@loggable
|
| 52 |
+
async def disapprove(client: Client, message: Message) -> str:
|
| 53 |
+
chat = message.chat
|
| 54 |
+
user = message.from_user
|
| 55 |
+
user_id = await extract_user(message, message.command[1:])
|
| 56 |
+
if not user_id:
|
| 57 |
+
await message.reply_text("I don't know who you're talking about, you're going to need to specify a user!")
|
| 58 |
+
return ""
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
member = await chat.get_member(user_id)
|
| 62 |
+
except RPCError:
|
| 63 |
+
return ""
|
| 64 |
+
|
| 65 |
+
if member.status in (enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER):
|
| 66 |
+
await message.reply_text("This user is an admin, they can't be unapproved.")
|
| 67 |
+
return ""
|
| 68 |
+
|
| 69 |
+
if not sql.is_approved(chat.id, user_id):
|
| 70 |
+
await message.reply_text(f"{member.user.first_name} isn't approved yet!")
|
| 71 |
+
return ""
|
| 72 |
+
|
| 73 |
+
sql.disapprove(chat.id, user_id)
|
| 74 |
+
await message.reply_text(f"{member.user.first_name} is no longer approved in {chat.title}.")
|
| 75 |
+
|
| 76 |
+
return f"<b>{html.escape(chat.title)}:</b>\n#UNAPPROVED\n<b>Admin:</b> {user.mention}\n<b>User:</b> {member.user.mention}"
|
| 77 |
+
|
| 78 |
+
@pbot.on_message(filters.command("approved") & filters.group)
|
| 79 |
+
@DisableAbleCommandHandler("approved")
|
| 80 |
+
@user_admin
|
| 81 |
+
async def approved(client: Client, message: Message):
|
| 82 |
+
chat = message.chat
|
| 83 |
+
msg = "The following users are approved.\n"
|
| 84 |
+
approved_users = sql.list_approved(chat.id)
|
| 85 |
+
for i in approved_users:
|
| 86 |
+
try:
|
| 87 |
+
member = await chat.get_member(int(i.user_id))
|
| 88 |
+
msg += f"- `{i.user_id}`: {member.user.first_name}\n"
|
| 89 |
+
except:
|
| 90 |
+
msg += f"- `{i.user_id}`\n"
|
| 91 |
+
|
| 92 |
+
if msg.endswith("approved.\n"):
|
| 93 |
+
await message.reply_text(f"No users are approved in {chat.title}.")
|
| 94 |
+
else:
|
| 95 |
+
await message.reply_text(msg)
|
| 96 |
+
|
| 97 |
+
@pbot.on_message(filters.command("approval") & filters.group)
|
| 98 |
+
@DisableAbleCommandHandler("approval")
|
| 99 |
+
@user_admin
|
| 100 |
+
async def approval(client: Client, message: Message):
|
| 101 |
+
chat = message.chat
|
| 102 |
+
user_id = await extract_user(message, message.command[1:])
|
| 103 |
+
if not user_id:
|
| 104 |
+
await message.reply_text("I don't know who you're talking about, you're going to need to specify a user!")
|
| 105 |
+
return ""
|
| 106 |
+
|
| 107 |
+
member = await chat.get_member(int(user_id))
|
| 108 |
+
if sql.is_approved(chat.id, user_id):
|
| 109 |
+
await message.reply_text(f"{member.user.first_name} is an approved user. Locks, antiflood, and blocklists won't apply to them.")
|
| 110 |
+
else:
|
| 111 |
+
await message.reply_text(f"{member.user.first_name} is not an approved user. They are affected by normal commands.")
|
| 112 |
+
|
| 113 |
+
@pbot.on_message(filters.command("unapproveall") & filters.group)
|
| 114 |
+
@DisableAbleCommandHandler("unapproveall")
|
| 115 |
+
async def unapproveall(client: Client, message: Message):
|
| 116 |
+
chat = message.chat
|
| 117 |
+
user = message.from_user
|
| 118 |
+
member = await chat.get_member(user.id)
|
| 119 |
+
if member.status != enums.ChatMemberStatus.OWNER and user.id not in DRAGONS:
|
| 120 |
+
await message.reply_text("Only the chat owner can unapprove all users at once.")
|
| 121 |
+
return
|
| 122 |
+
|
| 123 |
+
buttons = InlineKeyboardMarkup(
|
| 124 |
+
[
|
| 125 |
+
[InlineKeyboardButton(text="Unapprove all users", callback_data="unapproveall_user")],
|
| 126 |
+
[InlineKeyboardButton(text="Cancel", callback_data="unapproveall_cancel")],
|
| 127 |
+
]
|
| 128 |
+
)
|
| 129 |
+
await message.reply_text(
|
| 130 |
+
f"Are you sure you would like to unapprove ALL users in {chat.title}? This action cannot be undone.",
|
| 131 |
+
reply_markup=buttons,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
@pbot.on_callback_query(filters.regex(r"unapproveall_.*"))
|
| 135 |
+
async def unapproveall_btn(client: Client, query: CallbackQuery):
|
| 136 |
+
chat = query.message.chat
|
| 137 |
+
user_id = query.from_user.id
|
| 138 |
+
member = await chat.get_member(user_id)
|
| 139 |
+
|
| 140 |
+
if query.data == "unapproveall_user":
|
| 141 |
+
if member.status == enums.ChatMemberStatus.OWNER or user_id in DRAGONS:
|
| 142 |
+
approved_users = sql.list_approved(chat.id)
|
| 143 |
+
for i in approved_users:
|
| 144 |
+
sql.disapprove(chat.id, int(i.user_id))
|
| 145 |
+
await query.message.edit_text("Successfully unapproved all users.")
|
| 146 |
+
else:
|
| 147 |
+
await query.answer("Only the owner of the chat can do this.", show_alert=True)
|
| 148 |
+
|
| 149 |
+
elif query.data == "unapproveall_cancel":
|
| 150 |
+
if member.status == enums.ChatMemberStatus.OWNER or user_id in DRAGONS:
|
| 151 |
+
await query.message.edit_text("Removing of all approved users has been cancelled.")
|
| 152 |
+
else:
|
| 153 |
+
await query.answer("Only the owner of the chat can do this.", show_alert=True)
|
| 154 |
+
|
| 155 |
+
__mod_name__ = "Approve"
|
| 156 |
+
__help__ = """
|
| 157 |
+
Some users might be trustworthy enough to be ignored by automated admin actions.
|
| 158 |
+
Approval allows them to be bypassed by locks, blocklists, and antiflood.
|
| 159 |
+
|
| 160 |
+
**Admin Commands:**
|
| 161 |
+
• `/approval`: Check a user's approval status
|
| 162 |
+
• `/approve`: Approve a user
|
| 163 |
+
• `/unapprove`: Unapprove a user
|
| 164 |
+
• `/approved`: List all approved users
|
| 165 |
+
• `/unapproveall`: Unapprove ALL users (Owner only)
|
| 166 |
+
"""
|