27 Commits

Author SHA1 Message Date
b5c9a0783e Merge pull request 'Data export, warnings' improvements, bug fixes' (#35) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#35
2023-04-02 23:27:31 +03:00
aef4dd091d Warnings can now be revoked using /warnings 2023-04-02 22:17:54 +02:00
a3f75bec7c /warnings now also considers user membership 2023-04-02 21:38:23 +02:00
1f45398de5 /warnings can now show all warnings 2023-04-02 21:31:45 +02:00
Profitroll
cb2f3358b2 Typo fixed 2023-04-02 19:54:16 +02:00
74ae30d841 Sorted imports 2023-04-02 18:48:35 +02:00
e235fe0ed2 Improved bans 2023-04-02 18:42:11 +02:00
bf8ec39584 Changed how export works (in context of #34) 2023-04-02 18:42:03 +02:00
972827d6c2 Does some tasks from #34 2023-04-02 16:44:46 +02:00
43e71c95c4 Cancel should also cancel the message listener 2023-03-26 19:50:10 +02:00
2a2ad9f96e Added permissions for users to use /message 2023-03-26 19:39:33 +02:00
ddb59fdfd5 Added additional locale string for /message 2023-03-26 19:38:59 +02:00
5c55af9e65 This commit closes #28 2023-03-26 19:32:07 +02:00
fd992e89e7 Bump fastapi to 0.95.0 and starlette to 0.26.1 2023-03-26 19:09:45 +02:00
cea5338706 Bump Pyrogram and APScheduler versions 2023-03-18 19:59:44 +01:00
a4faae5b45 Fixed messages 2023-03-18 17:13:00 +01:00
2dc6a54299 Spoiler can't be empty now 2023-03-12 20:02:49 +01:00
cb3a975303 Removed default config string and added badges 2023-03-09 16:35:51 +01:00
de984c2b78 Removed useless shit 2023-03-09 16:31:39 +01:00
3f6fb51a4f Merge pull request 'User bans, emoji and other bug fixes, age limiter, etc' (#26) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#26
2023-01-31 15:26:55 +02:00
95be1e72d3 Merge pull request 'Bug fixes and improvements' (#8) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#8
2023-01-11 17:22:34 +02:00
234b73add0 Merge pull request 'Bug fixes and small structural changes' (#7) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#7
2023-01-06 17:01:20 +02:00
f4fb85f7a4 Merge pull request 'Removed legacy, fixed some bugs, improved spoilers' (#6) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#6
2023-01-05 16:49:35 +02:00
4fba305b05 Merge pull request 'Small fix for spoiler with an empty description' (#5) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#5
2023-01-05 13:54:19 +02:00
68c7cc0ada Merge pull request 'Spoilers, major command system improvements' (#4) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#4
2023-01-05 13:45:14 +02:00
79304816b0 Merge pull request '/cancel, /identify, sponsorships improvements and fixes' (#3) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#3
2023-01-03 16:45:20 +02:00
2cfa5a8f8d Merge pull request '/nearby, subscriptions check, geocoding' (#2) from dev into master
Reviewed-on: profitroll/HoloCheckerBot#2
2023-01-02 12:16:38 +02:00
42 changed files with 531 additions and 523 deletions

243
README.md
View File

@@ -1,6 +1,11 @@
# HoloCheckerBot <h1 align="center">HoloCheckerBot</h1>
Small Telegram bot made on Pyrogram <p align="center">Small Telegram bot made on Pyrogram</p>
<p align="center">
<a href="https://git.end-play.xyz/profitroll/HoloCheckerBot/src/branch/master/LICENSE"><img alt="License: GPL" src="https://img.shields.io/badge/License-GPL-blue"></a>
<a href="https://git.end-play.xyz/profitroll/HoloCheckerBot"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
</p>
## What can this bot do? ## What can this bot do?
@@ -25,240 +30,6 @@ Small Telegram bot made on Pyrogram
So bot has its "config_example.json" and it needs to be changed. So bot has its "config_example.json" and it needs to be changed.
Copy this file to "config.json" and open it with any text editor. Copy this file to "config.json" and open it with any text editor.
You can see config file with all the comments below:
```json
{
"locale": "uk",
"debug": false,
"owner": 0,
"age_allowed": 0,
"age_maximum": 70,
"api": "http://example.com",
"issues": "https://github.com/example/test/issues/new",
"inline_preview_count": 7,
"remove_application_time": -1,
"search_radius": 50,
"admins": [],
"groups": {
"admin": 0,
"users": 0
},
"bot": {
"api_id": 0,
"api_hash": "",
"bot_token": ""
},
"database": {
"user": null,
"password": null,
"host": "127.0.0.1",
"port": 27017,
"name": "holochecker"
},
"geocoding": {
"username": "demo"
},
"logging": {
"size": 512,
"location": "logs"
},
"features": {
"general": {
"enabled": true
},
"applications": {
"enabled": true
},
"sponsorships": {
"enabled": true
},
"warnings": {
"enabled": true
},
"invites_check": {
"enabled": true
},
"dinovoice": {
"enabled": false
},
"spoilers": {
"enabled": true,
"allow_external": true
}
},
"scheduler": {
"birthdays": {
"time": 9,
"enabled": true
},
"sponsorships": {
"time": 9,
"enabled": true
},
"cache_avatars": {
"interval": 6,
"enabled": true
},
"cache_members": {
"interval": 30,
"enabled": true
},
"cache_admins": {
"interval": 120,
"enabled": true
},
"channels_monitor": {
"interval": 5,
"enabled": true,
"channels": []
}
},
"locations": {
"cache": "cache",
"locale": "locale"
},
"commands": {
"rules": {
"permissions": [
"users",
"admins"
],
"modules": [
"general"
]
},
"spoiler": {
"permissions": [
"users",
"admins"
],
"modules": [
"spoilers"
]
},
"cancel": {
"permissions": [
"users",
"admins"
],
"modules": [
"spoilers",
"applications",
"sponsorships"
]
},
"nearby": {
"permissions": [
"users",
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"warn": {
"permissions": [
"group_users_admins"
],
"modules": [
"warnings"
]
},
"reapply": {
"permissions": [
"users",
"admins"
],
"modules": [
"applications"
]
},
"sponsorship": {
"permissions": [
"users",
"admins"
],
"modules": [
"sponsorships"
]
},
"reboot": {
"permissions": [
"owner"
],
"modules": [
"general"
]
},
"label": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"message": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"general"
]
},
"identify": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications",
"sponsorships"
]
},
"issue": {
"permissions": [
"users",
"admins"
],
"modules": [
"general"
]
},
"application": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"applications": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"applications"
]
},
"resetcommands": {
"permissions": [
"owner"
],
"modules": [
"general"
]
}
}
}
```
After all of that you're good to go! Happy using :) After all of that you're good to go! Happy using :)
## To-Do ## To-Do

3
app.py
View File

@@ -4,6 +4,7 @@ from modules.logging import logWrite
from modules.utils import configGet, jsonLoad from modules.utils import configGet, jsonLoad
from pyrogram.client import Client from pyrogram.client import Client
from pyrogram.errors import bad_request_400 from pyrogram.errors import bad_request_400
from convopyro import Conversation
app = Client( app = Client(
"holochecker", "holochecker",
@@ -12,6 +13,8 @@ app = Client(
api_hash=configGet("api_hash", "bot"), api_hash=configGet("api_hash", "bot"),
) )
Conversation(app)
async def isAnAdmin(admin_id): async def isAnAdmin(admin_id):
# Check if user is mentioned in config # Check if user is mentioned in config

View File

@@ -82,6 +82,10 @@
"interval": 5, "interval": 5,
"enabled": true, "enabled": true,
"channels": [] "channels": []
},
"warnings_revocation": {
"interval": 6,
"enabled": true
} }
}, },
"locations": { "locations": {
@@ -182,6 +186,7 @@
}, },
"message": { "message": {
"permissions": [ "permissions": [
"users",
"admins", "admins",
"group_admins" "group_admins"
], ],
@@ -208,16 +213,16 @@
"general" "general"
] ]
}, },
"application": { "export": {
"permissions": [ "permissions": [
"admins", "admins",
"group_admins" "group_admins"
], ],
"modules": [ "modules": [
"applications" "general"
] ]
}, },
"applications": { "application": {
"permissions": [ "permissions": [
"admins", "admins",
"group_admins" "group_admins"

View File

@@ -12,8 +12,8 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True)
# Importing # Importing
from modules.commands.application import * from modules.commands.application import *
from modules.commands.applications import *
from modules.commands.cancel import * from modules.commands.cancel import *
from modules.commands.export import *
from modules.commands.identify import * from modules.commands.identify import *
from modules.commands.issue import * from modules.commands.issue import *
from modules.commands.label import * from modules.commands.label import *
@@ -37,6 +37,7 @@ from modules.callbacks.spoiler import *
from modules.callbacks.sponsorship import * from modules.callbacks.sponsorship import *
from modules.callbacks.sub import * from modules.callbacks.sub import *
from modules.callbacks.sus import * from modules.callbacks.sus import *
from modules.callbacks.warnings import *
from modules.handlers.confirmation import * from modules.handlers.confirmation import *
from modules.handlers.contact import * from modules.handlers.contact import *

View File

@@ -34,8 +34,8 @@
"sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.",
"confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?",
"sponsor_confirm": "**Дані форми:**\nСтрімер: {0}\nПідписка до: {1}\nХочу роль: {2}\n\nПеревір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", "sponsor_confirm": "**Дані форми:**\nСтрімер: {0}\nПідписка до: {1}\nХочу роль: {2}\n\nПеревір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.",
"application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення. До тих пір від тебе більше нічого не потребується :)", "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення, як тільки її перевірять та приймуть рішення.. До тих пір від тебе більше нічого не потребується :)",
"sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення як тільки її перевірять та приймуть рішення :)", "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення, як тільки її перевірять та приймуть рішення. :)",
"application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}",
"reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", "reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}",
"sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}", "sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}",
@@ -76,11 +76,17 @@
"application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`", "application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`",
"warned": "Попереджено **{0}** (`{1}`) про порушення правил", "warned": "Попереджено **{0}** (`{1}`) про порушення правил",
"warned_reason": "Попереджено **{0}** (`{1}`)\n\n**Причина:**\n{2}", "warned_reason": "Попереджено **{0}** (`{1}`)\n\n**Причина:**\n{2}",
"warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження", "warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження\n\nОбрати та зняти попередження:\n`/warnings {3} revoke`",
"warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень", "warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень\n\nОбрати та зняти попередження:\n`/warnings {3} revoke`",
"warnings_all": "**Список попереджень**\n\n{0}\n\nДля перегляду попереджень окремо взятого користувача слід використовувати `/warnings ID/NAME/USERNAME`",
"warnings_entry": "• {0} (`{1}`)\n Попереджень: {2}",
"warnings_empty": "Щось тут порожньо...\nЗ іншого боку, це добре!",
"warnings_revoke": "**Попередження {0}:**\n\n{1}\n\nБудь ласка, користуйтесь клавіатурою щоб зняти попередження з відповідним номером.",
"no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень",
"no_user_warnings": "Не знайдено користувачів за запитом **{0}**", "no_user_warnings": "Не знайдено користувачів за запитом **{0}**",
"syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`", "syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`",
"syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans`",
"message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.",
"message_sent": "Повідомлення надіслано", "message_sent": "Повідомлення надіслано",
"message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.",
"message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`", "message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`",
@@ -109,14 +115,12 @@
"spoiler_started": "Розпочато створення спойлера. Будь ласка, оберіть категорію спойлера за допомогою клавіатури бота.", "spoiler_started": "Розпочато створення спойлера. Будь ласка, оберіть категорію спойлера за допомогою клавіатури бота.",
"spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення", "spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення",
"spoiler_cancel": "Створення спойлера було припинено", "spoiler_cancel": "Створення спойлера було припинено",
"spoiler_empty": "Спойлер категорії \"{0}\" без опису",
"spoiler_empty_named": "Спойлер категорії \"{0}\" без опису від **{1}**",
"spoiler_described": "Спойлер категорії \"{0}\": {1}", "spoiler_described": "Спойлер категорії \"{0}\": {1}",
"spoiler_described_named": "Спойлер категорії \"{0}\" від **{1}**: {2}", "spoiler_described_named": "Спойлер категорії \"{0}\" від **{1}**: {2}",
"spoiler_description_enter": "Добре, введіть бажаний опис спойлера", "spoiler_description_enter": "Добре, введіть бажаний опис спойлера",
"spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.", "spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.",
"spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)", "spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)",
"spoiler_send_description": "Тепер треба надіслати коротенький опис спойлера, щоб люди розуміли що під ним варто очкувати. Надішли мінус (-) щоб пропустити цей крок.", "spoiler_send_description": "Тепер треба надіслати коротенький опис спойлера, щоб люди розуміли що під ним варто очкувати.",
"spoiler_ready": "Успіх! Спойлер створено", "spoiler_ready": "Успіх! Спойлер створено",
"spoiler_send": "Користуйтесь кнопкою нижче щоб надіслати його.", "spoiler_send": "Користуйтесь кнопкою нижче щоб надіслати його.",
"spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).", "spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).",
@@ -257,7 +261,9 @@
"sponsor_accepted": "✅ Форму {0} схвалено", "sponsor_accepted": "✅ Форму {0} схвалено",
"sponsor_rejected": "❌ Форму {0} відхилено", "sponsor_rejected": "❌ Форму {0} відхилено",
"spoiler_sent": "✅ Повідомлення надіслано в холо-чат", "spoiler_sent": "✅ Повідомлення надіслано в холо-чат",
"spoiler_forbidden": "❌ Треба бути учасником чату" "spoiler_forbidden": "❌ Треба бути учасником чату",
"warning_revoked": "✅ Попередження скасовано",
"warning_not_found": "❌ Попередження вже скасовано або не існує"
}, },
"inline": { "inline": {
"forbidden": { "forbidden": {
@@ -295,13 +301,13 @@
"rules_additional": "Додаткові правила, які несуть рекомендаційний характер та не мають явних покарань за порушення:\n1⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2⃣) У чаті немає заборони на російський контент. Але майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4⃣) Поважайте авторські права контент-мейкерів. Якщо ви знаходите арт, анімацію, музику тощо на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом /search на повідомлення із артом.\n5⃣) В особливо критичних ситуаціях порушник може отримати бан або бути повністю видаленим із чату, без попереджень.\n6⃣) Якщо з кимось із учасників у вас трапиться якесь непорозуміння і вам неприємно буде перебувати в чаті один з одним (навіть, якщо конфлікт стався з кимось із адміністраторів) - напишіть мені в особисті повідомлення @Chirkopol. Я, як засновник чату та головний адміністратор, найбільше зацікавлений у збереженні цілісності чату та розвитку нашого ком'юніті, і я зроблю все, що в моїх силах, щоб допомогти вирішити вашу ситуацію.", "rules_additional": "Додаткові правила, які несуть рекомендаційний характер та не мають явних покарань за порушення:\n1⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2⃣) У чаті немає заборони на російський контент. Але майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4⃣) Поважайте авторські права контент-мейкерів. Якщо ви знаходите арт, анімацію, музику тощо на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом /search на повідомлення із артом.\n5⃣) В особливо критичних ситуаціях порушник може отримати бан або бути повністю видаленим із чату, без попереджень.\n6⃣) Якщо з кимось із учасників у вас трапиться якесь непорозуміння і вам неприємно буде перебувати в чаті один з одним (навіть, якщо конфлікт стався з кимось із адміністраторів) - напишіть мені в особисті повідомлення @Chirkopol. Я, як засновник чату та головний адміністратор, найбільше зацікавлений у збереженні цілісності чату та розвитку нашого ком'юніті, і я зроблю все, що в моїх силах, щоб допомогти вирішити вашу ситуацію.",
"commands": { "commands": {
"application": "Переглянути анкету користувача", "application": "Переглянути анкету користувача",
"applications": "Отримати всі анкети як JSON",
"cancel": "Відмінити актуальну дію", "cancel": "Відмінити актуальну дію",
"identify": "Дізнатись дані про користувача за айді", "identify": "Дізнатись дані про користувача за айді",
"issue": "Задачі для покращення бота", "issue": "Задачі для покращення бота",
"label": "Встановити нікнейм користувачу", "label": "Встановити нікнейм користувачу",
"message": "Надіслати користувачу повідомлення", "message": "Надіслати користувачу повідомлення",
"nearby": "Показати користувачів поблизу", "nearby": "Показати користувачів поблизу",
"export": "Експортувати дані як CSV та JSON",
"reapply": "Повторно заповнити анкету", "reapply": "Повторно заповнити анкету",
"reboot": "Перезапустити бота", "reboot": "Перезапустити бота",
"resetcommands": "Відреєструвати всі команди", "resetcommands": "Відреєструвати всі команди",

View File

@@ -1,3 +1,4 @@
from datetime import datetime
from app import app, isAnAdmin from app import app, isAnAdmin
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from pyrogram import filters from pyrogram import filters
@@ -12,7 +13,9 @@ async def callback_query_reject(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")
if not await isAnAdmin(int(fullclb[1])): if not await isAnAdmin(int(fullclb[1])):
col_bans.insert_one({"user": int(fullclb[1])}) col_bans.insert_one(
{"user": int(fullclb[1]), "admin": clb.from_user.id, "date": datetime.now()}
)
edited_markup = [ edited_markup = [
[ [

View File

@@ -5,10 +5,6 @@ from pyrogram.client import Client
from modules.utils import locale from modules.utils import locale
# Callback empty ===============================================================================================================
@app.on_callback_query(filters.regex("nothing")) @app.on_callback_query(filters.regex("nothing"))
async def callback_query_nothing(app: Client, clb: CallbackQuery): async def callback_query_nothing(app: Client, clb: CallbackQuery):
await clb.answer(text=locale("nothing", "callback", locale=clb.from_user)) await clb.answer(text=locale("nothing", "callback", locale=clb.from_user))
# ==============================================================================================================================

View File

@@ -15,7 +15,6 @@ from modules.handlers.welcome import welcome_pass
from modules.database import col_tmp, col_applications from modules.database import col_tmp, col_applications
# Callbacks reapply ============================================================================================================
@app.on_callback_query(filters.regex("reapply_yes_[\s\S]*")) @app.on_callback_query(filters.regex("reapply_yes_[\s\S]*"))
async def callback_reapply_query_accept(app: Client, clb: CallbackQuery): async def callback_reapply_query_accept(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")
@@ -285,6 +284,3 @@ async def callback_query_reapply_stop(app: Client, clb: CallbackQuery):
locale("reapply_restarted", "message", locale=holo_user), locale("reapply_restarted", "message", locale=holo_user),
reply_markup=ReplyKeyboardRemove(), reply_markup=ReplyKeyboardRemove(),
) )
# ==============================================================================================================================

View File

@@ -7,7 +7,6 @@ from modules.utils import locale, logWrite
from modules.commands.rules import DefaultRulesMarkup from modules.commands.rules import DefaultRulesMarkup
# Callback rule ================================================================================================================
@app.on_callback_query(filters.regex("rule_[\s\S]*")) @app.on_callback_query(filters.regex("rule_[\s\S]*"))
async def callback_query_rule(app: Client, clb: CallbackQuery): async def callback_query_rule(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")
@@ -100,6 +99,3 @@ async def callback_query_rules_additional(app: Client, clb: CallbackQuery):
pass pass
await clb.answer(text=locale("rules_additional", "callback", locale=clb.from_user)) await clb.answer(text=locale("rules_additional", "callback", locale=clb.from_user))
# ==============================================================================================================================

View File

@@ -9,7 +9,6 @@ from bson.objectid import ObjectId
from modules.utils import configGet, jsonLoad, locale from modules.utils import configGet, jsonLoad, locale
# Callback sid =================================================================================================================
@app.on_callback_query(filters.regex("sid_[\s\S]*")) @app.on_callback_query(filters.regex("sid_[\s\S]*"))
async def callback_query_sid(app: Client, clb: CallbackQuery): async def callback_query_sid(app: Client, clb: CallbackQuery):
await clb.answer( await clb.answer(
@@ -17,10 +16,6 @@ async def callback_query_sid(app: Client, clb: CallbackQuery):
) )
# ==============================================================================================================================
# Callback shc =================================================================================================================
@app.on_callback_query(filters.regex("shc_[\s\S]*")) @app.on_callback_query(filters.regex("shc_[\s\S]*"))
async def callback_query_shc(app: Client, clb: CallbackQuery): async def callback_query_shc(app: Client, clb: CallbackQuery):
if clb.from_user.id not in jsonLoad( if clb.from_user.id not in jsonLoad(
@@ -34,19 +29,13 @@ async def callback_query_shc(app: Client, clb: CallbackQuery):
spoil = col_spoilers.find_one({"_id": ObjectId(clb.data.split("_")[1])}) spoil = col_spoilers.find_one({"_id": ObjectId(clb.data.split("_")[1])})
if spoil["description"] == "": desc = locale(
desc = locale("spoiler_empty_named", "message", locale=clb.from_user).format( "spoiler_described_named", "message", locale=clb.from_user
locale(spoil["category"], "message", "spoiler_categories"), ).format(
clb.from_user.first_name, locale(spoil["category"], "message", "spoiler_categories"),
) clb.from_user.first_name,
else: spoil["description"],
desc = locale( )
"spoiler_described_named", "message", locale=clb.from_user
).format(
locale(spoil["category"], "message", "spoiler_categories"),
clb.from_user.first_name,
spoil["description"],
)
await app.send_message( await app.send_message(
configGet("users", "groups"), configGet("users", "groups"),
@@ -80,6 +69,3 @@ async def callback_query_shc(app: Client, clb: CallbackQuery):
await clb.answer( await clb.answer(
locale("spoiler_sent", "callback", locale=clb.from_user), show_alert=True locale("spoiler_sent", "callback", locale=clb.from_user), show_alert=True
) )
# ==============================================================================================================================

View File

@@ -14,7 +14,6 @@ from modules.utils import configGet, locale, logWrite, should_quote
from modules.database import col_tmp, col_sponsorships from modules.database import col_tmp, col_sponsorships
# Callbacks sponsorship ========================================================================================================
@app.on_callback_query(filters.regex("sponsor_apply_[\s\S]*")) @app.on_callback_query(filters.regex("sponsor_apply_[\s\S]*"))
async def callback_query_sponsor_apply(app: Client, clb: CallbackQuery): async def callback_query_sponsor_apply(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")

View File

@@ -9,7 +9,6 @@ from modules.database import col_tmp, col_applications
from modules.commands.rules import DefaultRulesMarkup from modules.commands.rules import DefaultRulesMarkup
# Callbacks application ========================================================================================================
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*")) @app.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
async def callback_query_accept(app: Client, clb: CallbackQuery): async def callback_query_accept(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")
@@ -188,6 +187,3 @@ async def callback_query_reject_russian(app: Client, clb: CallbackQuery):
), ),
show_alert=True, show_alert=True,
) )
# ==============================================================================================================================

View File

@@ -12,7 +12,6 @@ from modules.utils import configGet, locale, logWrite
from modules.database import col_tmp from modules.database import col_tmp
# Callbacks sus users ==========================================================================================================
@app.on_callback_query(filters.regex("sus_allow_[\s\S]*")) @app.on_callback_query(filters.regex("sus_allow_[\s\S]*"))
async def callback_query_sus_allow(app: Client, clb: CallbackQuery): async def callback_query_sus_allow(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")
@@ -99,6 +98,3 @@ async def callback_query_sus_reject(app: Client, clb: CallbackQuery):
{"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}},
{"$set": {"state": "rejected", "sent": False}}, {"$set": {"state": "rejected", "sent": False}},
) )
# ==============================================================================================================================

View File

@@ -0,0 +1,27 @@
from datetime import datetime
from app import app
from pyrogram import filters
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from modules.utils import locale
from modules.database import col_warnings
from bson import ObjectId
@app.on_callback_query(filters.regex("w_rev_[\s\S]*"))
async def callback_query_warning_revoke(app: Client, clb: CallbackQuery):
warning = col_warnings.find_one({"_id": ObjectId(str(clb.data).split("_")[2])})
if warning is None:
await clb.answer(
text=locale("warning_not_found", "callback", locale=clb.from_user),
show_alert=True,
)
return
col_warnings.update_one(
{"_id": warning["_id"]},
{"$set": {"active": False, "revoke_date": datetime.now()}},
)
await clb.answer(
text=locale("warning_revoked", "callback", locale=clb.from_user).format(),
show_alert=True,
)

View File

@@ -13,7 +13,6 @@ from modules.database import col_applications
from modules import custom_filters from modules import custom_filters
# Application command ==========================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_applications custom_filters.enabled_applications
& ~filters.scheduled & ~filters.scheduled
@@ -98,6 +97,3 @@ async def cmd_application(app: Client, msg: Message):
locale("application_invalid_syntax", "message", locale=msg.from_user), locale("application_invalid_syntax", "message", locale=msg.from_user),
quote=should_quote(msg), quote=should_quote(msg),
) )
# ==============================================================================================================================

View File

@@ -1,43 +0,0 @@
from os import sep, makedirs, remove
from uuid import uuid1
from app import app
from pyrogram import filters
from pyrogram.types import Message
from pyrogram.client import Client
from pyrogram.enums.chat_action import ChatAction
from modules.logging import logWrite
from modules.utils import should_quote, jsonSave
from modules.database import col_applications
from modules import custom_filters
# Applications command =========================================================================================================
@app.on_message(
custom_filters.enabled_applications
& ~filters.scheduled
& filters.command(["applications"], prefixes=["/"])
& custom_filters.admin
)
async def cmd_applications(app: Client, msg: Message):
logWrite(f"Admin {msg.from_user.id} requested export of a database")
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
filename = uuid1()
output = []
for entry in col_applications.find():
del entry["_id"]
entry["date"] = entry["date"].strftime("%d.%m.%Y, %H:%M")
entry["application"]["2"] = entry["application"]["2"].strftime(
"%d.%m.%Y, %H:%M"
)
output.append(entry)
makedirs("tmp", exist_ok=True)
jsonSave(output, f"tmp{sep}{filename}.json")
await msg.reply_document(
document=f"tmp{sep}{filename}.json",
file_name="applications",
quote=should_quote(msg),
)
remove(f"tmp{sep}{filename}.json")
# ==============================================================================================================================

View File

@@ -7,7 +7,6 @@ from modules.database import col_tmp, col_spoilers, col_applications
from modules import custom_filters from modules import custom_filters
# Cancel command ===============================================================================================================
@app.on_message( @app.on_message(
(custom_filters.enabled_applications | custom_filters.enabled_sponsorships) (custom_filters.enabled_applications | custom_filters.enabled_sponsorships)
& ~filters.scheduled & ~filters.scheduled
@@ -17,6 +16,10 @@ from modules import custom_filters
async def command_cancel(app: Client, msg: Message): async def command_cancel(app: Client, msg: Message):
col_tmp.delete_many({"user": msg.from_user.id, "sent": False}) col_tmp.delete_many({"user": msg.from_user.id, "sent": False})
col_spoilers.delete_many({"user": msg.from_user.id, "completed": False}) col_spoilers.delete_many({"user": msg.from_user.id, "completed": False})
try:
await app.listen.Cancel(filters.user(msg.from_user.id))
except:
pass
if col_applications.find_one({"user": msg.from_user.id}) is None: if col_applications.find_one({"user": msg.from_user.id}) is None:
await msg.reply_text( await msg.reply_text(
locale("cancel_reapply", "message", locale=msg.from_user), locale("cancel_reapply", "message", locale=msg.from_user),
@@ -30,6 +33,3 @@ async def command_cancel(app: Client, msg: Message):
reply_markup=ReplyKeyboardRemove(), reply_markup=ReplyKeyboardRemove(),
) )
logWrite(f"Cancelling all ongoing tmp operations for {msg.from_user.id}") logWrite(f"Cancelling all ongoing tmp operations for {msg.from_user.id}")
# ==============================================================================================================================

182
modules/commands/export.py Normal file
View File

@@ -0,0 +1,182 @@
from csv import QUOTE_ALL
from os import makedirs, path, remove
from uuid import uuid1
import aiofiles
from aiocsv.writers import AsyncDictWriter
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.enums.chat_action import ChatAction
from pyrogram.types import Message
from ujson import dumps
from app import app
from modules import custom_filters
from modules.database import col_applications, col_sponsorships, col_warnings
from modules.logging import logWrite
from modules.utils import locale, should_quote
@app.on_message(
custom_filters.enabled_general
& ~filters.scheduled
& filters.command(["export"], prefixes=["/"])
& custom_filters.admin
)
async def cmd_export(app: Client, msg: Message):
if len(msg.command) <= 1:
await msg.reply_text(
locale("syntax_export", "message", locale=msg.from_user),
quote=should_quote(msg),
)
return
selection = msg.command[1].lower()
if selection not in ["applications", "warnings", "sponsorships", "bans"]:
await msg.reply_text(
locale("syntax_export", "message", locale=msg.from_user),
quote=should_quote(msg),
)
return
logWrite(f"Admin {msg.from_user.id} requested export of {selection}")
makedirs("tmp", exist_ok=True)
temp_file = path.join("tmp", str(uuid1()))
await app.send_chat_action(msg.chat.id, ChatAction.TYPING)
output_csv = []
output_json = []
if selection == "applications":
header_csv = [
"user",
"date",
"admin",
"question_1",
"question_2",
"question_3",
"question_4",
"question_5",
"question_6",
"question_7",
"question_8",
"question_9",
"question_10",
]
for entry in list(col_applications.find()):
del entry["_id"]
entry["date"] = entry["date"].isoformat()
entry["application"]["2"] = entry["application"]["2"].isoformat()
output_json.append(entry)
for entry in list(col_applications.find()):
del entry["_id"]
entry["date"] = entry["date"].isoformat()
entry["application"]["2"] = entry["application"]["2"].isoformat()
for index, value in enumerate(entry["application"]):
entry[f"question_{index+1}"] = entry["application"][value]
entry[
"question_3"
] = f"{entry['application']['3']['name']} ({entry['application']['3']['adminName1']}, {entry['application']['3']['countryName']})"
del entry["application"]
output_csv.append(entry)
elif selection == "warnings":
header_csv = [
"id",
"user",
"admin",
"date",
"reason",
"active",
"revoke_date",
]
for entry in list(col_warnings.find()):
for k, v in list(entry.items()):
entry[{"_id": "id"}.get(k, k)] = entry.pop(k)
entry["id"] = str(entry["id"])
entry["date"] = entry["date"].isoformat()
if entry["revoke_date"] is not None:
entry["revoke_date"] = entry["revoke_date"].isoformat()
output_json.append(entry)
output_csv.append(entry)
elif selection == "sponsorships":
header_csv = [
"user",
"date",
"admin",
"streamer",
"expires",
"proof",
"label",
]
for entry in list(col_sponsorships.find()):
del entry["_id"]
entry["date"] = entry["date"].isoformat()
entry["sponsorship"]["expires"] = entry["sponsorship"][
"expires"
].isoformat()
output_json.append(entry)
for entry in list(col_sponsorships.find()):
del entry["_id"]
entry["date"] = entry["date"].isoformat()
entry["sponsorship"]["expires"] = entry["sponsorship"][
"expires"
].isoformat()
for index, value in enumerate(entry["sponsorship"]):
entry[value] = entry["sponsorship"][value]
del entry["sponsorship"]
output_csv.append(entry)
elif selection == "bans":
header_csv = ["user", "admin", "date"]
for entry in list(col_warnings.find()):
del entry["id"]
entry["date"] = entry["date"].isoformat()
output_json.append(entry)
output_csv.append(entry)
# Saving CSV
async with aiofiles.open(temp_file + ".csv", mode="w", encoding="utf-8") as file:
writer = AsyncDictWriter(file, header_csv, restval="NULL", quoting=QUOTE_ALL)
await writer.writeheader()
await writer.writerows(output_csv)
# Saving JSON
async with aiofiles.open(temp_file + ".json", mode="w", encoding="utf-8") as file:
await file.write(
dumps(
output_json, ensure_ascii=False, escape_forward_slashes=False, indent=4
)
)
# Sending CSV
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
await msg.reply_document(
document=temp_file + ".csv",
file_name=f"{selection}.csv",
quote=should_quote(msg),
)
# Sending JSON
await app.send_chat_action(msg.chat.id, ChatAction.UPLOAD_DOCUMENT)
await msg.reply_document(
document=temp_file + ".json",
file_name=f"{selection}.json",
quote=should_quote(msg),
)
del output_csv, output_json
# Removing temp files
remove(temp_file + ".csv")
remove(temp_file + ".json")

View File

@@ -19,7 +19,6 @@ from modules.utils import (
from modules import custom_filters from modules import custom_filters
# Identify command =============================================================================================================
@app.on_message( @app.on_message(
(custom_filters.enabled_applications | custom_filters.enabled_sponsorships) (custom_filters.enabled_applications | custom_filters.enabled_sponsorships)
& ~filters.scheduled & ~filters.scheduled
@@ -104,6 +103,3 @@ async def cmd_identify(app: Client, msg: Message):
await msg.reply_text(output, quote=should_quote(msg)) await msg.reply_text(output, quote=should_quote(msg))
logWrite(f"User {msg.from_user.id} identified user {holo_user.id}") logWrite(f"User {msg.from_user.id} identified user {holo_user.id}")
# ==============================================================================================================================

View File

@@ -1,14 +1,11 @@
from typing import Union
from app import app from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User, Message from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message
from pyrogram.client import Client from pyrogram.client import Client
from modules.utils import configGet, locale from modules.utils import configGet, locale
from modules import custom_filters from modules import custom_filters
from classes.holo_user import HoloUser
# Issue command ================================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_general custom_filters.enabled_general
& ~filters.scheduled & ~filters.scheduled
@@ -31,6 +28,3 @@ async def cmd_issue(app: Client, msg: Message):
] ]
), ),
) )
# ==============================================================================================================================

View File

@@ -8,7 +8,6 @@ from classes.holo_user import HoloUser
from modules import custom_filters from modules import custom_filters
# Label command ================================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_applications custom_filters.enabled_applications
& ~filters.scheduled & ~filters.scheduled
@@ -53,6 +52,3 @@ async def cmd_label(app: Client, msg: Message):
else: else:
await msg.reply_text(f"User not found") await msg.reply_text(f"User not found")
# ==============================================================================================================================

View File

@@ -1,62 +1,82 @@
from app import app from app import app, isAnAdmin
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message from pyrogram.types import Message
from pyrogram.client import Client from pyrogram.client import Client
from convopyro import listen_message
from classes.errors.holo_user import UserInvalidError from classes.errors.holo_user import UserInvalidError
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import logWrite, locale, should_quote, find_user from modules.utils import configGet, logWrite, locale, should_quote, find_user
from modules import custom_filters from modules import custom_filters
from modules.database import col_messages
# Message command ==============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_general custom_filters.enabled_general
& ~filters.scheduled & ~filters.scheduled
& filters.command(["message"], prefixes=["/"]) & filters.command(["message"], prefixes=["/"])
& custom_filters.admin # & custom_filters.admin
) )
async def cmd_message(app: Client, msg: Message): async def cmd_message(app: Client, msg: Message):
try: try:
try: if await isAnAdmin(msg.from_user.id):
destination = HoloUser(int(msg.command[1])) try:
except (ValueError, UserInvalidError): destination = HoloUser(int(msg.command[1]))
destination = HoloUser(await find_user(app, query=msg.command[1])) except (ValueError, UserInvalidError):
destination = HoloUser(await find_user(app, query=msg.command[1]))
if (msg.text is not None) and (len(str(msg.text).split()) > 2): if (msg.text is not None) and (len(str(msg.text).split()) > 2):
await destination.message( await destination.message(
context=msg, context=msg,
text=" ".join(str(msg.text).split()[2:]), text=" ".join(str(msg.text).split()[2:]),
caption=msg.caption, caption=msg.caption,
photo=msg.photo, photo=msg.photo,
video=msg.video, video=msg.video,
file=msg.document, file=msg.document,
voice=msg.voice, voice=msg.voice,
animation=msg.animation, animation=msg.animation,
adm_context=True, adm_context=True,
) )
elif (msg.caption is not None) and (len(msg.caption.split()) > 2): elif (msg.caption is not None) and (len(msg.caption.split()) > 2):
await destination.message( await destination.message(
context=msg, context=msg,
text=str(msg.text), text=str(msg.text),
caption=" ".join(msg.caption.split()[2:]), caption=" ".join(msg.caption.split()[2:]),
photo=msg.photo, photo=msg.photo,
video=msg.video, video=msg.video,
file=msg.document, file=msg.document,
voice=msg.voice, voice=msg.voice,
animation=msg.animation, animation=msg.animation,
adm_context=True, adm_context=True,
) )
else:
await destination.message(
context=msg,
text=None,
caption=None,
photo=msg.photo,
video=msg.video,
file=msg.document,
voice=msg.voice,
animation=msg.animation,
adm_context=True,
)
else: else:
await destination.message( await msg.reply_text(
context=msg, locale("message_enter", "message", locale=msg.from_user)
text=None, )
caption=None, message = await listen_message(app, msg.chat.id, timeout=None)
photo=msg.photo, if message.text is not None and message.text == "/cancel":
video=msg.video, return
file=msg.document, sent = await app.forward_messages(
voice=msg.voice, configGet("admin", "groups"), msg.chat.id, message.id
animation=msg.animation, )
adm_context=True, col_messages.insert_one(
{
"origin": {"chat": message.chat.id, "id": message.id},
"destination": {"chat": sent.chat.id, "id": sent.id},
}
)
await message.reply_text(
locale("message_sent", "message", locale=message.from_user), quote=True
) )
except IndexError: except IndexError:
@@ -71,6 +91,3 @@ async def cmd_message(app: Client, msg: Message):
quote=should_quote(msg), quote=should_quote(msg),
) )
logWrite(f"Admin {msg.from_user.id} tried to send message but 'ValueError'") logWrite(f"Admin {msg.from_user.id} tried to send message but 'ValueError'")
# ==============================================================================================================================

View File

@@ -12,7 +12,6 @@ from modules.database import col_applications, col_users
from classes.errors.geo import PlaceNotFoundError from classes.errors.geo import PlaceNotFoundError
# Nearby command ===============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_applications custom_filters.enabled_applications
& ~filters.scheduled & ~filters.scheduled
@@ -116,6 +115,3 @@ async def cmd_nearby(app: Client, msg: Message):
await msg.reply_text( await msg.reply_text(
locale("nearby_empty", "message", locale=holo_user), quote=should_quote(msg) locale("nearby_empty", "message", locale=holo_user), quote=should_quote(msg)
) )
# ==============================================================================================================================

View File

@@ -10,7 +10,6 @@ from modules.database import col_tmp, col_applications
from modules import custom_filters from modules import custom_filters
# Reapply command ==============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_applications custom_filters.enabled_applications
& ~filters.scheduled & ~filters.scheduled
@@ -168,6 +167,3 @@ async def cmd_reapply(app: Client, msg: Message):
] ]
), ),
) )
# ==============================================================================================================================

View File

@@ -12,7 +12,6 @@ from modules import custom_filters
pid = getpid() pid = getpid()
# Reboot command ===============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_general custom_filters.enabled_general
& ~filters.scheduled & ~filters.scheduled
@@ -33,6 +32,3 @@ async def cmd_kill(app: Client, msg: Message):
path.join(configGet("cache", "locations"), "shutdown_time"), path.join(configGet("cache", "locations"), "shutdown_time"),
) )
exit() exit()
# ==============================================================================================================================

View File

@@ -10,7 +10,6 @@ from modules import custom_filters
pid = getpid() pid = getpid()
# Reset commands command =======================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_general custom_filters.enabled_general
& ~filters.scheduled & ~filters.scheduled
@@ -110,6 +109,3 @@ async def cmd_resetcommands(app: Client, msg: Message):
), ),
debug=True, debug=True,
) )
# ==============================================================================================================================

View File

@@ -42,7 +42,6 @@ class DefaultRulesMarkup(list):
) )
# Rules command =============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_general custom_filters.enabled_general
& ~filters.scheduled & ~filters.scheduled
@@ -56,6 +55,3 @@ async def cmd_rules(app: Client, msg: Message):
disable_web_page_preview=True, disable_web_page_preview=True,
reply_markup=DefaultRulesMarkup(msg.from_user).keyboard, reply_markup=DefaultRulesMarkup(msg.from_user).keyboard,
) )
# ==============================================================================================================================

View File

@@ -10,7 +10,6 @@ from modules.database import col_spoilers, col_applications
from modules import custom_filters from modules import custom_filters
# Spoiler command ==============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_spoilers custom_filters.enabled_spoilers
& ~filters.scheduled & ~filters.scheduled
@@ -63,6 +62,3 @@ async def cmd_spoiler(app: Client, msg: Message):
await msg.reply_text( await msg.reply_text(
locale("spoiler_unfinished", "message", locale=msg.from_user) locale("spoiler_unfinished", "message", locale=msg.from_user)
) )
# ==============================================================================================================================

View File

@@ -8,7 +8,6 @@ from modules.utils import locale, should_quote
from modules.database import col_applications from modules.database import col_applications
# Sponsorship command ==========================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_sponsorships custom_filters.enabled_sponsorships
& ~filters.scheduled & ~filters.scheduled
@@ -45,6 +44,3 @@ async def cmd_sponsorship(app: Client, msg: Message):
) )
# else: # else:
# await msg.reply_text(locale("sponsorship_application_empty", "message")) # await msg.reply_text(locale("sponsorship_application_empty", "message"))
# ==============================================================================================================================

View File

@@ -9,7 +9,6 @@ from bson.objectid import ObjectId
from bson.errors import InvalidId from bson.errors import InvalidId
# Start command ================================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_applications custom_filters.enabled_applications
& ~filters.scheduled & ~filters.scheduled
@@ -69,6 +68,3 @@ async def cmd_start(app: Client, msg: Message):
await msg.reply_text(spoiler["text"]) await msg.reply_text(spoiler["text"])
except InvalidId: except InvalidId:
await msg.reply_text(f"Got an invalid ID {msg.command[1]}") await msg.reply_text(f"Got an invalid ID {msg.command[1]}")
# ==============================================================================================================================

View File

@@ -8,7 +8,6 @@ from modules.database import col_warnings
from modules import custom_filters from modules import custom_filters
# Warn command =================================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_warnings custom_filters.enabled_warnings
& ~filters.scheduled & ~filters.scheduled
@@ -25,6 +24,8 @@ async def cmd_warn(app: Client, msg: Message):
"admin": msg.from_user.id, "admin": msg.from_user.id,
"date": datetime.now(), "date": datetime.now(),
"reason": message, "reason": message,
"active": True,
"revoke_date": None,
} }
) )
if message == "": if message == "":
@@ -42,6 +43,3 @@ async def cmd_warn(app: Client, msg: Message):
message, message,
) )
) )
# ==============================================================================================================================

View File

@@ -1,14 +1,15 @@
from os import path
from app import app from app import app
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message from pyrogram.types import Message
from pyrogram.client import Client from pyrogram.client import Client
from pyrogram.enums.chat_members_filter import ChatMembersFilter from pyrogram.enums.chat_members_filter import ChatMembersFilter
from modules.utils import configGet, locale, should_quote from modules.utils import configGet, jsonLoad, locale, should_quote
from modules.database import col_users, col_warnings from modules.database import col_users, col_warnings
from modules import custom_filters from modules import custom_filters
from pykeyboard import InlineKeyboard, InlineButton
# Warnings command =============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_warnings custom_filters.enabled_warnings
& ~filters.scheduled & ~filters.scheduled
@@ -16,7 +17,42 @@ from modules import custom_filters
& custom_filters.admin & custom_filters.admin
) )
async def cmd_warnings(app: Client, msg: Message): async def cmd_warnings(app: Client, msg: Message):
if len(msg.command) <= 1: if len(msg.command) == 1:
warnings = {}
warnings_output = []
group_members = jsonLoad(
path.join(configGet("cache", "locations"), "group_members")
)
for warning in col_warnings.find({"active": True}):
if warning["user"] not in group_members:
continue
if str(warning["user"]) not in warnings:
warnings[str(warning["user"])] = {
"name": (col_users.find_one({"user": warning["user"]}))["tg_name"],
"warns": 1,
}
else:
warnings[str(warning["user"])]["warns"] += 1
for warning in warnings:
warnings_output.append(
locale("warnings_entry", "message", locale=msg.from_user).format(
warnings[warning]["name"], warning, warnings[warning]["warns"]
),
)
warnings_output = (
locale("warnings_empty", "message", locale=msg.from_user)
if len(warnings_output) == 0
else "\n".join(warnings_output)
)
await msg.reply_text(
locale("warnings_all", "message", locale=msg.from_user).format(
warnings_output
),
quote=should_quote(msg),
)
return
if len(msg.command) > 3:
await msg.reply_text( await msg.reply_text(
locale("syntax_warnings", "message", locale=msg.from_user), locale("syntax_warnings", "message", locale=msg.from_user),
quote=should_quote(msg), quote=should_quote(msg),
@@ -48,7 +84,36 @@ async def cmd_warnings(app: Client, msg: Message):
) )
return return
warns = col_warnings.count_documents({"user": target_id}) if len(msg.command) == 3 and msg.command[2].lower() == "revoke":
if col_warnings.count_documents({"user": target_id, "active": True}) == 0:
await msg.reply_text(
locale("no_warnings", "message", locale=msg.from_user).format(
target_name, target_id
),
quote=should_quote(msg),
)
return
keyboard = InlineKeyboard()
buttons = []
warnings = []
for index, warning in enumerate(
list(col_warnings.find({"user": target_id, "active": True}))
):
warnings.append(
f'{index+1}. {warning["date"].strftime("%d.%m.%Y, %H:%M")}\n Адмін: {warning["admin"]}\n Причина: {warning["reason"]}'
)
buttons.append(InlineButton(str(index + 1), f'w_rev_{str(warning["_id"])}'))
keyboard.add(*buttons)
await msg.reply_text(
locale("warnings_revoke", "message", locale=msg.from_user).format(
target_name, "\n".join(warnings)
),
reply_markup=keyboard,
quote=should_quote(msg),
)
return
warns = col_warnings.count_documents({"user": target_id, "active": True})
if warns == 0: if warns == 0:
await msg.reply_text( await msg.reply_text(
@@ -61,17 +126,14 @@ async def cmd_warnings(app: Client, msg: Message):
if warns <= 5: if warns <= 5:
await msg.reply_text( await msg.reply_text(
locale("warnings_1", "message", locale=msg.from_user).format( locale("warnings_1", "message", locale=msg.from_user).format(
target_name, target_id, warns target_name, target_id, warns, target_id
), ),
quote=should_quote(msg), quote=should_quote(msg),
) )
else: else:
await msg.reply_text( await msg.reply_text(
locale("warnings_2", "message", locale=msg.from_user).format( locale("warnings_2", "message", locale=msg.from_user).format(
target_name, target_id, warns target_name, target_id, warns, target_id
), ),
quote=should_quote(msg), quote=should_quote(msg),
) )
# ==============================================================================================================================

View File

@@ -18,7 +18,7 @@ from modules.handlers.welcome import welcome_pass
from modules.database import col_tmp, col_applications from modules.database import col_tmp, col_applications
from modules import custom_filters from modules import custom_filters
# Confirmation =================================================================================================================
confirmation_1 = [] confirmation_1 = []
for pattern in all_locales("confirm", "keyboard"): for pattern in all_locales("confirm", "keyboard"):
confirmation_1.append(pattern[0][0]) confirmation_1.append(pattern[0][0])
@@ -286,6 +286,3 @@ async def confirm_no(
f"User {msg.from_user.id} restarted the sponsorship application due to typo in it" f"User {msg.from_user.id} restarted the sponsorship application due to typo in it"
) )
return return
# ==============================================================================================================================

View File

@@ -10,7 +10,6 @@ from classes.holo_user import HoloUser
from modules import custom_filters from modules import custom_filters
# Contact getting ==============================================================================================================
@app.on_message( @app.on_message(
custom_filters.enabled_applications custom_filters.enabled_applications
& ~filters.scheduled & ~filters.scheduled
@@ -82,6 +81,3 @@ async def get_contact(app: Client, msg: Message):
await msg.reply_text( await msg.reply_text(
locale("contact_not_member", "message", locale=holo_user.locale) locale("contact_not_member", "message", locale=holo_user.locale)
) )
# ==============================================================================================================================

View File

@@ -41,7 +41,6 @@ async def message_context(msg: Message) -> tuple:
return 0, 0 return 0, 0
# Any other input ==============================================================================================================
@app.on_message( @app.on_message(
~filters.scheduled ~filters.scheduled
& (filters.private | filters.chat(configGet("admin", "groups"))) & (filters.private | filters.chat(configGet("admin", "groups")))
@@ -148,33 +147,27 @@ async def any_stage(app: Client, msg: Message):
# await msg.reply_text(locale("spoiler_description_enter", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("spoiler_description", "force_reply", locale=msg.from_user))) # await msg.reply_text(locale("spoiler_description_enter", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("spoiler_description", "force_reply", locale=msg.from_user)))
# return # return
if str(msg.text) != "-": msg.text = fix_text(str(msg.text))
msg.text = fix_text(str(msg.text)) if len(str(msg.text)) > 1024:
if len(str(msg.text)) > 1024: await msg.reply_text(
await msg.reply_text( locale(
locale( "spoiler_description_too_long",
"spoiler_description_too_long", "message",
"message", locale=msg.from_user,
),
reply_markup=ForceReply(
placeholder=locale(
"spoiler_description",
"force_reply",
locale=msg.from_user, locale=msg.from_user,
), )
reply_markup=ForceReply( ),
placeholder=locale(
"spoiler_description",
"force_reply",
locale=msg.from_user,
)
),
)
return
col_spoilers.find_one_and_update(
{"user": msg.from_user.id, "completed": False},
{"$set": {"description": msg.text}},
)
else:
col_spoilers.find_one_and_update(
{"user": msg.from_user.id, "completed": False},
{"$set": {"description": ""}},
) )
return
col_spoilers.find_one_and_update(
{"user": msg.from_user.id, "completed": False},
{"$set": {"description": msg.text}},
)
logWrite( logWrite(
f"Adding description '{str(msg.text)}' to {msg.from_user.id}'s spoiler" f"Adding description '{str(msg.text)}' to {msg.from_user.id}'s spoiler"
@@ -377,8 +370,6 @@ async def message_in_group(app: Client, msg: Message):
): ):
if str(msg.text).startswith( if str(msg.text).startswith(
locale("spoiler_described", "message").split()[0] locale("spoiler_described", "message").split()[0]
) or str(msg.text).startswith(
locale("spoiler_empty", "message").split()[0]
): ):
logWrite(f"User {msg.from_user.id} sent spoiler to user's group") logWrite(f"User {msg.from_user.id} sent spoiler to user's group")
try: try:
@@ -400,6 +391,3 @@ async def message_in_group(app: Client, msg: Message):
logWrite( logWrite(
f"Removed application requested by {msg.from_user.id} in destination group" f"Removed application requested by {msg.from_user.id} in destination group"
) )
# ==============================================================================================================================

View File

@@ -15,7 +15,6 @@ from classes.holo_user import HoloUser
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
# Filter users on join =========================================================================================================
@app.on_chat_member_updated( @app.on_chat_member_updated(
custom_filters.enabled_invites_check, group=configGet("users", "groups") custom_filters.enabled_invites_check, group=configGet("users", "groups")
) )
@@ -150,6 +149,3 @@ async def filter_join(app: Client, member: ChatMemberUpdated):
can_send_polls=False, can_send_polls=False,
), ),
) )
# ==============================================================================================================================

View File

@@ -6,7 +6,6 @@ from classes.holo_user import HoloUser
from modules.utils import all_locales, locale, logWrite from modules.utils import all_locales, locale, logWrite
from modules import custom_filters from modules import custom_filters
# Welcome check ================================================================================================================
welcome_1 = [] welcome_1 = []
for pattern in all_locales("welcome", "keyboard"): for pattern in all_locales("welcome", "keyboard"):
welcome_1.append(pattern[0][0]) welcome_1.append(pattern[0][0])
@@ -72,6 +71,3 @@ async def welcome_reject(app: Client, msg: Message):
locale("return", "keyboard", locale=msg.from_user), resize_keyboard=True locale("return", "keyboard", locale=msg.from_user), resize_keyboard=True
), ),
) )
# ==============================================================================================================================

View File

@@ -36,21 +36,13 @@ async def inline_answer(client: Client, inline_query: InlineQuery):
) )
if spoil is not None: if spoil is not None:
desc = ( desc = locale(
locale( "spoiler_described",
"spoiler_empty", "message", locale=inline_query.from_user "message",
).format( locale=inline_query.from_user,
locale(spoil["category"], "message", "spoiler_categories") ).format(
) locale(spoil["category"], "message", "spoiler_categories"),
if spoil["description"] == "" spoil["description"],
else locale(
"spoiler_described",
"message",
locale=inline_query.from_user,
).format(
locale(spoil["category"], "message", "spoiler_categories"),
spoil["description"],
)
) )
results = [ results = [

View File

@@ -18,7 +18,12 @@ from pyrogram.enums.chat_members_filter import ChatMembersFilter
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import configGet, jsonLoad, jsonSave, locale, logWrite from modules.utils import configGet, jsonLoad, jsonSave, locale, logWrite
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from modules.database import col_applications, col_sponsorships, col_youtube from modules.database import (
col_applications,
col_sponsorships,
col_youtube,
col_warnings,
)
from xmltodict import parse from xmltodict import parse
from requests import get from requests import get
@@ -182,6 +187,45 @@ if configGet("enabled", "features", "sponsorships") is True:
logWrite("Sponsorships check performed") logWrite("Sponsorships check performed")
# Revoke old warnings
if configGet("enabled", "features", "warnings") is True:
if configGet("enabled", "scheduler", "warnings_revocation") is True:
@scheduler.scheduled_job(
trigger="date", run_date=datetime.now() + timedelta(seconds=10)
)
@scheduler.scheduled_job(
trigger="interval",
hours=configGet("interval", "scheduler", "warnings_revocation"),
)
async def revoke_warnings():
for warning in list(
col_warnings.find(
{
"active": True,
"date": {"$lt": datetime.now() - timedelta(days=90)},
}
)
):
if (
col_warnings.count_documents(
{
"user": warning["user"],
"active": True,
"date": {"$gt": datetime.now() - timedelta(days=90)},
}
)
== 0
):
col_warnings.update_one(
{"_id": warning["_id"]},
{"$set": {"active": False, "revoke_date": datetime.now()}},
)
logWrite(
f'Revoked warning {str(warning["_id"])} of user {warning["user"]} because no active warnings for the last 90 days found.'
)
# Register all bot commands # Register all bot commands
@scheduler.scheduled_job( @scheduler.scheduled_job(
trigger="date", run_date=datetime.now() + timedelta(seconds=10) trigger="date", run_date=datetime.now() + timedelta(seconds=10)

View File

@@ -1,12 +1,16 @@
APScheduler==3.10.0 aiocsv==1.2.3
fastapi~=0.88.0 aiofiles~=23.1.0
APScheduler~=3.10.1
convopyro==0.5
fastapi~=0.95.0
ftfy~=6.1.1
psutil==5.9.4 psutil==5.9.4
pymongo==4.3.3 pymongo==4.3.3
Pyrogram~=2.0.100 Pyrogram~=2.0.102
requests==2.28.2
tgcrypto==1.2.5
python_dateutil==2.8.2 python_dateutil==2.8.2
starlette~=0.22.0 pykeyboard==0.1.5
requests==2.28.2
starlette==0.26.1
tgcrypto==1.2.5
ujson~=5.7.0 ujson~=5.7.0
ftfy~=6.1.1 xmltodict==0.13.0
xmltodict~=0.13.0

29
validation/bans.json Normal file
View File

@@ -0,0 +1,29 @@
{
"$jsonSchema": {
"required": [
"user",
"admin",
"date"
],
"properties": {
"user": {
"bsonType": [
"int",
"long"
],
"description": "Telegram ID of user"
},
"admin": {
"bsonType": [
"int",
"long"
],
"description": "Telegram ID of admin"
},
"date": {
"bsonType": "date",
"description": "Date and time of getting"
}
}
}
}

View File

@@ -4,15 +4,23 @@
"user", "user",
"admin", "admin",
"date", "date",
"reason" "reason",
"active",
"revoke_date"
], ],
"properties": { "properties": {
"user": { "user": {
"bsonType": ["int", "long"], "bsonType": [
"int",
"long"
],
"description": "Telegram ID of user" "description": "Telegram ID of user"
}, },
"admin": { "admin": {
"bsonType": ["int", "long"], "bsonType": [
"int",
"long"
],
"description": "Telegram ID of admin" "description": "Telegram ID of admin"
}, },
"date": { "date": {
@@ -22,6 +30,17 @@
"reason": { "reason": {
"bsonType": "string", "bsonType": "string",
"description": "Broken rule or admin's comment" "description": "Broken rule or admin's comment"
},
"active": {
"bsonType": "bool",
"description": "Whether warning is still present"
},
"revoke_date": {
"bsonType": [
"date",
"null"
],
"description": "Date when warning got inactive"
} }
} }
} }