16 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
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
10 changed files with 337 additions and 58 deletions

View File

@@ -213,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

@@ -76,11 +76,16 @@
"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_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.",
"message_sent": "Повідомлення надіслано", "message_sent": "Повідомлення надіслано",
"message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.",
@@ -256,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": {
@@ -294,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

@@ -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

@@ -1,39 +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
@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")

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

@@ -1,11 +1,13 @@
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
@app.on_message( @app.on_message(
@@ -15,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),
@@ -23,7 +60,7 @@ async def cmd_warnings(app: Client, msg: Message):
return return
try: try:
user_db = col_users.find_one({"user": int(msg.command[1]), "active": True}) user_db = col_users.find_one({"user": int(msg.command[1])})
target_id = user_db["user"] target_id = user_db["user"]
target_name = user_db["tg_name"] target_name = user_db["tg_name"]
except: except:
@@ -47,6 +84,35 @@ async def cmd_warnings(app: Client, msg: Message):
) )
return return
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}) warns = col_warnings.count_documents({"user": target_id, "active": True})
if warns == 0: if warns == 0:
@@ -60,14 +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

@@ -1,13 +1,16 @@
aiocsv==1.2.3
aiofiles~=23.1.0
APScheduler~=3.10.1 APScheduler~=3.10.1
convopyro==0.5
fastapi~=0.95.0 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.102 Pyrogram~=2.0.102
requests==2.28.2
tgcrypto==1.2.5
python_dateutil==2.8.2 python_dateutil==2.8.2
pykeyboard==0.1.5
requests==2.28.2
starlette==0.26.1 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
convopyro==0.5

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"
}
}
}
}