Improved locales, added /language

This commit is contained in:
Profitroll 2023-08-11 15:04:21 +02:00
parent 07963d705c
commit 370bcd0653
Signed by: profitroll
GPG Key ID: FA35CAB49DACD3B2
17 changed files with 465 additions and 104 deletions

52
classes/callbacks.py Normal file
View File

@ -0,0 +1,52 @@
from dataclasses import dataclass
from pyrogram.types import CallbackQuery
@dataclass
class CallbackVerify:
user_id: int
@classmethod
def from_callback(cls, callback: CallbackQuery):
action, user_id = str(callback.data).split(";")
if action.lower() != "verify":
raise ValueError("Callback provided is not a verification callback")
return cls(int(user_id))
@dataclass
class CallbackEmoji:
user_id: int
emoji: str
@classmethod
def from_callback(cls, callback: CallbackQuery):
action, user_id, emoji = str(callback.data).split(";")
if action.lower() != "emoji":
raise ValueError("Callback provided is not an emoji button callback")
return cls(int(user_id), emoji)
@dataclass
class CallbackBan:
user_id: int
@classmethod
def from_callback(cls, callback: CallbackQuery):
action, user_id = str(callback.data).split(";")
if action.lower() != "ban":
raise ValueError("Callback provided is not a ban callback")
return cls(int(user_id))
@dataclass
class CallbackLanguage:
language: str
@classmethod
def from_callback(cls, callback: CallbackQuery):
action, language = str(callback.data).split(";")
if action.lower() != "language":
raise ValueError("Callback provided is not a language callback")
return cls(language)

View File

@ -18,7 +18,7 @@ class PyroClient(PyroClient):
### Returns: ### Returns:
* `PyroUser`: PyroUser object * `PyroUser`: PyroUser object
""" """
db_record = col_users.find_one( db_record = await col_users.find_one(
{"id": user.id if isinstance(user, User) else user, "group": group} {"id": user.id if isinstance(user, User) else user, "group": group}
) )

72
classes/pyrogroup.py Normal file
View File

@ -0,0 +1,72 @@
import logging
from dataclasses import dataclass
from typing import Union
from bson import ObjectId
from pyrogram.types import User
from classes.pyroclient import PyroClient
from modules.database import col_groups
logger = logging.getLogger(__name__)
@dataclass
class PyroGroup:
"""Dataclass of DB entry of a group"""
__slots__ = ("_id", "id", "locale", "locale_auto")
_id: ObjectId
id: int
locale: Union[str, None]
locale_auto: bool
@classmethod
async def create_if_not_exists(
cls,
id: int,
locale: Union[str, None] = None,
locale_auto: bool = True,
):
db_entry = await col_groups.find_one(
{
"id": id,
}
)
if db_entry is None:
inserted = await col_groups.insert_one(
{"id": id, "locale": locale, "locale_auto": locale_auto}
)
db_entry = {
"_id": inserted.inserted_id,
"id": id,
"locale": locale,
"locale_auto": locale_auto,
}
return cls(**db_entry)
async def set_locale(self, locale: Union[str, None]) -> None:
logger.debug("Locale of group %s has been set to %s", self.id, locale)
await col_groups.update_one({"_id": self._id}, {"$set": {"locale": locale}})
async def set_locale_auto(self, enabled: bool) -> None:
logger.debug(
"Automatic locale selection of group %s has been set to %s",
self.id,
enabled,
)
await col_groups.update_one(
{"_id": self._id}, {"$set": {"locale_auto": enabled}}
)
# Group settings
# User locale
def select_locale(
self, app: PyroClient, user: Union[User, None] = None, ignore_auto: bool = False
) -> str:
if not ignore_auto and self.locale_auto is True:
if user.language_code is not None:
return user.language_code
return self.locale if self.locale is not None else app.default_locale
return self.locale if self.locale is not None else app.default_locale

View File

@ -34,7 +34,7 @@ class PyroUser:
mistakes: int mistakes: int
@classmethod @classmethod
def create_if_not_exists( async def create_if_not_exists(
cls, cls,
id: int, id: int,
group: int, group: int,
@ -44,9 +44,9 @@ class PyroUser:
score: int = 0, score: int = 0,
mistakes: int = 0, mistakes: int = 0,
): ):
db_entry = col_users.find_one({"id": id, "group": group}) db_entry = await col_users.find_one({"id": id, "group": group})
if db_entry is None: if db_entry is None:
inserted = col_users.insert_one( inserted = await col_users.insert_one(
{ {
"id": id, "id": id,
"group": group, "group": group,
@ -69,36 +69,36 @@ class PyroUser:
} }
return cls(**db_entry) return cls(**db_entry)
def set_failed(self, failed: bool = True) -> None: async def set_failed(self, failed: bool = True) -> None:
logger.debug("%s's failure state has been set to %s", self.id, failed) logger.debug("%s's failure state has been set to %s", self.id, failed)
col_users.update_one({"_id": self._id}, {"$set": {"failed": failed}}) await col_users.update_one({"_id": self._id}, {"$set": {"failed": failed}})
def set_emojis(self, emojis: List[str]) -> None: async def set_emojis(self, emojis: List[str]) -> None:
logger.debug("%s's emojis have been set to %s", self.id, emojis) logger.debug("%s's emojis have been set to %s", self.id, emojis)
col_users.update_one({"_id": self._id}, {"$set": {"emojis": emojis}}) await col_users.update_one({"_id": self._id}, {"$set": {"emojis": emojis}})
def set_score(self, score: int = 0) -> None: async def set_score(self, score: int = 0) -> None:
logger.debug("%s's score has been set to %s", self.id, score) logger.debug("%s's score has been set to %s", self.id, score)
col_users.update_one({"_id": self._id}, {"$set": {"score": score}}) await col_users.update_one({"_id": self._id}, {"$set": {"score": score}})
def set_mistakes(self, mistakes: int = 0) -> None: async def set_mistakes(self, mistakes: int = 0) -> None:
logger.debug("%s's mistakes count has been set to %s", self.id, mistakes) logger.debug("%s's mistakes count has been set to %s", self.id, mistakes)
col_users.update_one({"_id": self._id}, {"$set": {"mistakes": mistakes}}) await col_users.update_one({"_id": self._id}, {"$set": {"mistakes": mistakes}})
def update_score(self, points: int = 1) -> None: async def update_score(self, points: int = 1) -> None:
logger.debug("%s point(s) have been added to %s score", points, self.id) logger.debug("%s point(s) have been added to %s score", points, self.id)
col_users.update_one( await col_users.update_one(
{"_id": self._id}, {"$set": {"score": self.score + points}} {"_id": self._id}, {"$set": {"score": self.score + points}}
) )
def update_mistakes(self, points: int = 1) -> None: async def update_mistakes(self, points: int = 1) -> None:
logger.debug("%s point(s) have been added to %s mistakes", points, self.id) logger.debug("%s point(s) have been added to %s mistakes", points, self.id)
col_users.update_one( await col_users.update_one(
{"_id": self._id}, {"$set": {"mistakes": self.mistakes + points}} {"_id": self._id}, {"$set": {"mistakes": self.mistakes + points}}
) )
def update_selected(self, entry: str) -> None: async def update_selected(self, entry: str) -> None:
logger.debug("Emoji %s has been added to %s's selection list", entry, self.id) logger.debug("Emoji %s has been added to %s's selection list", entry, self.id)
col_users.update_one( await col_users.update_one(
{"_id": self._id}, {"$set": {"selected": self.selected + [entry]}} {"_id": self._id}, {"$set": {"selected": self.selected + [entry]}}
) )

View File

@ -105,5 +105,20 @@
"😀", "😀",
"🙊" "🙊"
], ],
"commands": {} "commands": {
"language": {
"scopes": [
{
"name": "BotCommandScopeAllChatAdministrators"
}
]
},
"language_auto": {
"scopes": [
{
"name": "BotCommandScopeAllChatAdministrators"
}
]
}
}
} }

View File

@ -1,19 +1,38 @@
{ {
"metadata": {
"flag": "🇬🇧",
"name": "English",
"codes": [
"en",
"en-US",
"en-GB"
]
},
"commands": {
"language": "Set bot's language in this group",
"language_auto": "Toggle automatic language selection"
},
"messages": { "messages": {
"locale_auto_disabled": "Automatic language selection on per-user basis has been **disabled**. Please note that group's language will now be used. You can change it using /language. If \"Default\" language is set bot's default language ({default_locale}) will be used.",
"locale_auto_enabled": "Automatic language selection on per-user basis has been **enabled**. Please note that if bot does not have a language pack for user group's or bot's default language will be used. You can change group's language using /language. If \"Default\" language is set bot's default language ({default_locale}) will be used.",
"locale_choice": "Alright. Please choose the language using keyboard below.\n\nNote, that you can also enable automatic language selection on per-user basis using command /language_auto",
"permission_denied": "You are not allowed to do so.",
"verify": "Please, use the buttons below to complete the captcha. Tap on the buttons with emojis you see on this image.", "verify": "Please, use the buttons below to complete the captcha. Tap on the buttons with emojis you see on this image.",
"welcome_verify": "Welcome, {mention}! In order to chat here, we need to verify you're a human. Please, press the button below to start the verification.", "welcome_verify": "Welcome, {mention}! In order to chat here, we need to verify you're a human. Please, press the button below to start the verification.",
"welcome": "Welcome to the chat, {mention}!" "welcome": "Welcome to the chat, {mention}!"
}, },
"callbacks": { "callbacks": {
"captcha_failed": "You have failed the captcha.",
"captcha_failed_force": "You have forced {user_id} to fail the captcha.", "captcha_failed_force": "You have forced {user_id} to fail the captcha.",
"captcha_failed": "You have failed the captcha.",
"captcha_mistake": "Invalid answer. Remaining attempts: {remaining}.", "captcha_mistake": "Invalid answer. Remaining attempts: {remaining}.",
"captcha_passed": "You have passed the captcha. Welcome!", "captcha_passed": "You have passed the captcha. Welcome!",
"locale_set": "Group's language is now: {locale}",
"nothing": "This actions has already been finished.", "nothing": "This actions has already been finished.",
"wrong_user": "This message is not for you." "wrong_user": "This message is not for you."
}, },
"buttons": { "buttons": {
"ban": "Ban (for admins)", "ban": "Ban (for admins)",
"locale_default": "Default",
"verify": "Verify" "verify": "Verify"
} }
} }

View File

@ -1,5 +1,21 @@
{ {
"metadata": {
"flag": "🇺🇦",
"name": "Українська",
"codes": [
"uk",
"uk-UA"
]
},
"commands": {
"language": "Встановити мову бота в цій групі",
"language_auto": "Переключити автоматичний вибір мови"
},
"messages": { "messages": {
"locale_auto_disabled": "Автоматичний вибір мови для кожного користувача було **вимкнено**. Зверніть увагу, що тепер буде використовуватися мова групи. Ви можете змінити її за допомогою /language. Якщо встановлено мову \"За замовчуванням\" буде використовуватися мова бота за замовчуванням ({default_locale}).",
"locale_auto_enabled": "Автоматичний вибір мови для кожного користувача був **увімкнений**. Зверніть увагу, що якщо бот не має мовного пакета для користувача буде використовуватися мова групи або бота за замовчуванням. Ви можете змінити мову групи за допомогою /language. Якщо встановлено мову \"За замовчуванням\" буде використовуватися мова бота за замовчуванням ({default_locale}).",
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.\n\nЗауважте, що ви також можете увімкнути автоматичний вибір мови для кожного користувача за допомогою команди /language_auto",
"permission_denied": "Вам не дозволено робити це.",
"verify": "Будь ласка, використовуйте кнопки нижче, щоб заповнити капчу. Натисніть на кнопки зі смайликами, які ви бачите на цьому зображенні.", "verify": "Будь ласка, використовуйте кнопки нижче, щоб заповнити капчу. Натисніть на кнопки зі смайликами, які ви бачите на цьому зображенні.",
"welcome_verify": "Ласкаво просимо, {mention}! Для того, щоб спілкуватися в чаті, ми повинні переконатися, що ви людина. Будь ласка, натисніть кнопку нижче, щоб почати перевірку.", "welcome_verify": "Ласкаво просимо, {mention}! Для того, щоб спілкуватися в чаті, ми повинні переконатися, що ви людина. Будь ласка, натисніть кнопку нижче, щоб почати перевірку.",
"welcome": "Ласкаво просимо до чату, {mention}!" "welcome": "Ласкаво просимо до чату, {mention}!"
@ -9,11 +25,13 @@
"captcha_failed_force": "Ви змусили {user_id} не пройти капчу.", "captcha_failed_force": "Ви змусили {user_id} не пройти капчу.",
"captcha_mistake": "Неправильна відповідь. Спроби, що залишилися: {remaining}.", "captcha_mistake": "Неправильна відповідь. Спроби, що залишилися: {remaining}.",
"captcha_passed": "Ви пройшли капчу. Ласкаво просимо!", "captcha_passed": "Ви пройшли капчу. Ласкаво просимо!",
"locale_set": "Встановлено мову групи: {locale}",
"nothing": "Цю дію вже завершено.", "nothing": "Цю дію вже завершено.",
"wrong_user": "Це повідомлення не для вас." "wrong_user": "Це повідомлення не для вас."
}, },
"buttons": { "buttons": {
"ban": "Заблокувати (для адмінів)", "ban": "Заблокувати (для адмінів)",
"locale_default": "За замовчуванням",
"verify": "Верифікуватись" "verify": "Верифікуватись"
} }
} }

View File

@ -1,6 +1,6 @@
"""Module that provides all database columns""" """Module that provides all database columns"""
from pymongo import MongoClient from async_pymongo import AsyncClient
from ujson import loads from ujson import loads
with open("config.json", "r", encoding="utf-8") as f: with open("config.json", "r", encoding="utf-8") as f:
@ -20,14 +20,15 @@ else:
db_config["host"], db_config["port"], db_config["name"] db_config["host"], db_config["port"], db_config["name"]
) )
db_client = MongoClient(con_string) db_client = AsyncClient(con_string)
db = db_client.get_database(name=db_config["name"]) db = db_client.get_database(name=db_config["name"])
collections = db.list_collection_names() # collections = db.list_collection_names()
for collection in ["users", "schedule"]: # for collection in ["users", "groups", "schedule"]:
if collection not in collections: # if collection not in collections:
db.create_collection(collection) # db.create_collection(collection)
col_users = db.get_collection("users") col_users = db.get_collection("users")
col_groups = db.get_collection("groups")
col_schedule = db.get_collection("schedule") col_schedule = db.get_collection("schedule")

View File

@ -4,22 +4,30 @@ from pyrogram import filters
from pyrogram.enums.chat_member_status import ChatMemberStatus from pyrogram.enums.chat_member_status import ChatMemberStatus
from pyrogram.types import CallbackQuery, Message from pyrogram.types import CallbackQuery, Message
from classes.callbacks import CallbackBan
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@PyroClient.on_callback_query(filters.regex(r"ban;[\s\S]*")) @PyroClient.on_callback_query(filters.regex(r"ban;[\s\S]*"))
async def callback_ban(app: PyroClient, callback: CallbackQuery): async def callback_ban(app: PyroClient, callback: CallbackQuery):
if ( group = await PyroGroup.create_if_not_exists(callback.message.chat.id, None, True)
await app.get_chat_member(callback.message.chat.id, callback.from_user.id) locale = group.select_locale(app, callback.message.from_user)
).status not in [ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER]:
await callback.answer(app._("wrong_user", "callbacks"), show_alert=True) if (await app.get_chat_member(group.id, callback.from_user.id)).status not in [
ChatMemberStatus.ADMINISTRATOR,
ChatMemberStatus.OWNER,
]:
await callback.answer(
app._("wrong_user", "callbacks", locale=locale), show_alert=True
)
return return
user = await app.find_user( parsed = CallbackBan.from_callback(callback)
int(str(callback.data).split(";")[1]), callback.message.chat.id
) user = await app.find_user(parsed.user_id, group.id)
logger.info( logger.info(
"User %s has been marked as failed the captcha by %s", "User %s has been marked as failed the captcha by %s",
@ -27,14 +35,16 @@ async def callback_ban(app: PyroClient, callback: CallbackQuery):
callback.from_user.id, callback.from_user.id,
) )
user.set_mistakes(3) await user.set_mistakes(3)
user.set_failed(True) await user.set_failed(True)
await callback.answer( await callback.answer(
app._("captcha_failed_force", "callbacks").format(user_id=user.id), app._("captcha_failed_force", "callbacks", locale=locale).format(
user_id=user.id
),
show_alert=True, show_alert=True,
) )
banned = await app.ban_chat_member(callback.message.chat.id, user.id) banned = await app.ban_chat_member(group.id, user.id)
if isinstance(banned, Message): if isinstance(banned, Message):
await banned.delete() await banned.delete()

View File

@ -9,52 +9,58 @@ from pyrogram.types import (
Message, Message,
) )
from classes.callbacks import CallbackEmoji
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@PyroClient.on_callback_query(filters.regex(r"emoji;[\s\S]*")) @PyroClient.on_callback_query(filters.regex(r"emoji;[\s\S]*"))
async def callback_emoji_button(app: PyroClient, callback: CallbackQuery): async def callback_emoji_button(app: PyroClient, callback: CallbackQuery):
user_id = int(str(callback.data).split(";")[1]) parsed = CallbackEmoji.from_callback(callback)
emoji = str(callback.data).split(";")[2] group = await PyroGroup.create_if_not_exists(callback.message.chat.id, None, True)
locale = group.select_locale(app, callback.message.from_user)
if callback.from_user.id != user_id: if callback.from_user.id != parsed.user_id:
await callback.answer(app._("wrong_user", "callbacks"), show_alert=True) await callback.answer(
app._("wrong_user", "callbacks", locale=locale), show_alert=True
)
return return
user = await app.find_user(callback.from_user, callback.message.chat.id) user = await app.find_user(callback.from_user, group.id)
logger.debug( logger.debug(
"User %s has pressed the %s emoji '%s'", "User %s has pressed the %s emoji '%s'",
user.id, user.id,
"correct" if emoji in user.emojis else "wrong", "correct" if parsed.emoji in user.emojis else "wrong",
emoji, parsed.emoji,
) )
if emoji in user.selected: if parsed.emoji in user.selected:
await callback.answer() await callback.answer()
return return
user.update_selected(emoji) await user.update_selected(parsed.emoji)
if emoji in user.emojis: if parsed.emoji in user.emojis:
user.update_score(1) await user.update_score(1)
if user.score >= 5: if user.score >= 5:
logger.info("User %s has passed the captcha", user.id) logger.info("User %s has passed the captcha", user.id)
await callback.message.delete() await callback.message.delete()
await callback.answer(app._("captcha_passed", "callbacks"), show_alert=True) await callback.answer(
app._("captcha_passed", "callbacks", locale=locale), show_alert=True
)
await app.send_message( await app.send_message(
callback.message.chat.id, group.id,
app._( app._("welcome", "messages", locale=locale).format(
"welcome", mention=callback.from_user.mention
"messages", ),
).format(mention=callback.from_user.mention),
) )
await app.restrict_chat_member( await app.restrict_chat_member(
chat_id=callback.message.chat.id, chat_id=group.id,
user_id=callback.from_user.id, user_id=callback.from_user.id,
permissions=ChatPermissions(can_send_messages=True), permissions=ChatPermissions(can_send_messages=True),
) )
@ -71,20 +77,20 @@ async def callback_emoji_button(app: PyroClient, callback: CallbackQuery):
if user.mistakes >= 2: if user.mistakes >= 2:
logger.info("User %s has failed the captcha", user.id) logger.info("User %s has failed the captcha", user.id)
user.set_failed(True) await user.set_failed(True)
await callback.answer(app._("captcha_failed", "callbacks"), show_alert=True) await callback.answer(
app._("captcha_failed", "callbacks", locale=locale), show_alert=True
banned = await app.ban_chat_member(
callback.message.chat.id, callback.from_user.id
) )
banned = await app.ban_chat_member(group.id, callback.from_user.id)
if isinstance(banned, Message): if isinstance(banned, Message):
await banned.delete() await banned.delete()
await callback.message.delete() await callback.message.delete()
return return
user.update_mistakes(1) await user.update_mistakes(1)
logger.info( logger.info(
"User %s has made a mistake and has %s attempt(s) left", "User %s has made a mistake and has %s attempt(s) left",
user.id, user.id,
@ -92,7 +98,9 @@ async def callback_emoji_button(app: PyroClient, callback: CallbackQuery):
) )
await callback.answer( await callback.answer(
app._("captcha_mistake", "callbacks").format(remaining=2 - user.mistakes), app._("captcha_mistake", "callbacks", locale=locale).format(
remaining=2 - user.mistakes
),
show_alert=True, show_alert=True,
) )
@ -102,7 +110,7 @@ async def callback_emoji_button(app: PyroClient, callback: CallbackQuery):
for row_index, row in enumerate(callback.message.reply_markup.inline_keyboard): for row_index, row in enumerate(callback.message.reply_markup.inline_keyboard):
for button_index, button in enumerate(row): for button_index, button in enumerate(row):
if button.text == emoji: if button.text == parsed.emoji:
button_replace = (row_index, button_index) button_replace = (row_index, button_index)
new_keyboard = callback.message.reply_markup.inline_keyboard new_keyboard = callback.message.reply_markup.inline_keyboard

View File

@ -2,8 +2,12 @@ from pyrogram import filters
from pyrogram.types import CallbackQuery from pyrogram.types import CallbackQuery
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
@PyroClient.on_callback_query(filters.regex(r"nothing")) @PyroClient.on_callback_query(filters.regex(r"nothing"))
async def callback_nothing(app: PyroClient, callback: CallbackQuery): async def callback_nothing(app: PyroClient, callback: CallbackQuery):
await callback.answer(app._("nothing", "callbacks")) group = await PyroGroup.create_if_not_exists(callback.message.chat.id, None, True)
locale = group.select_locale(app, callback.message.from_user)
await callback.answer(app._("nothing", "callbacks", locale=locale))

View File

@ -7,7 +7,9 @@ from pykeyboard import InlineButton, InlineKeyboard
from pyrogram import filters from pyrogram import filters
from pyrogram.types import CallbackQuery from pyrogram.types import CallbackQuery
from classes.callbacks import CallbackVerify
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
from modules.database import col_schedule from modules.database import col_schedule
from modules.kicker import kick_unverified from modules.kicker import kick_unverified
from modules.utils import get_captcha_image from modules.utils import get_captcha_image
@ -17,13 +19,17 @@ logger = logging.getLogger(__name__)
@PyroClient.on_callback_query(filters.regex(r"verify;[\s\S]*")) @PyroClient.on_callback_query(filters.regex(r"verify;[\s\S]*"))
async def callback_verify(app: PyroClient, callback: CallbackQuery): async def callback_verify(app: PyroClient, callback: CallbackQuery):
user_id = int(str(callback.data).split(";")[1]) parsed = CallbackVerify.from_callback(callback)
group = await PyroGroup.create_if_not_exists(callback.message.chat.id, None, True)
locale = group.select_locale(app, callback.message.from_user)
if callback.from_user.id != user_id: if callback.from_user.id != parsed.user_id:
await callback.answer(app._("wrong_user", "callbacks"), show_alert=True) await callback.answer(
app._("wrong_user", "callbacks", locale=locale), show_alert=True
)
return return
user = await app.find_user(callback.from_user, callback.message.chat.id) user = await app.find_user(callback.from_user, group.id)
captcha = get_captcha_image(app.config["emojis"]) captcha = get_captcha_image(app.config["emojis"])
logger.info( logger.info(
@ -33,15 +39,15 @@ async def callback_verify(app: PyroClient, callback: CallbackQuery):
captcha.emojis_correct, captcha.emojis_correct,
) )
scheduled_job = col_schedule.find_one_and_delete( scheduled_job = await col_schedule.find_one_and_delete(
{"user": user_id, "group": callback.message.chat.id} {"user": parsed.user_id, "group": group.id}
) )
if scheduled_job is not None and app.scheduler is not None: if scheduled_job is not None and app.scheduler is not None:
with contextlib.suppress(JobLookupError): with contextlib.suppress(JobLookupError):
app.scheduler.remove_job(scheduled_job["job_id"]) app.scheduler.remove_job(scheduled_job["job_id"])
user.set_emojis(captcha.emojis_correct) await user.set_emojis(captcha.emojis_correct)
buttons = [ buttons = [
InlineButton(emoji, f"emoji;{user.id};{emoji}") for emoji in captcha.emojis_all InlineButton(emoji, f"emoji;{user.id};{emoji}") for emoji in captcha.emojis_all
@ -53,9 +59,9 @@ async def callback_verify(app: PyroClient, callback: CallbackQuery):
await callback.message.delete() await callback.message.delete()
captcha_message = await app.send_photo( captcha_message = await app.send_photo(
callback.message.chat.id, group.id,
captcha.image, captcha.image,
caption=app._("verify", "messages"), caption=app._("verify", "messages", locale=locale),
reply_markup=keyboard, reply_markup=keyboard,
) )
@ -65,7 +71,7 @@ async def callback_verify(app: PyroClient, callback: CallbackQuery):
app.scheduler.add_job( app.scheduler.add_job(
kick_unverified, kick_unverified,
"date", "date",
[app, user.id, callback.message.chat.id, captcha_message.id], [app, user.id, group.id, captcha_message.id],
run_date=datetime.now() run_date=datetime.now()
+ timedelta(seconds=app.config["timeouts"]["verify"]), + timedelta(seconds=app.config["timeouts"]["verify"]),
) )

View File

@ -0,0 +1,51 @@
import logging
from pyrogram import filters
from pyrogram.enums.chat_member_status import ChatMemberStatus
from pyrogram.types import Message
from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
logger = logging.getLogger(__name__)
@PyroClient.on_message(
~filters.scheduled
& filters.group
& filters.command(["language_auto"], prefixes=["/"]) # type: ignore
)
async def command_language(app: PyroClient, message: Message):
group = await PyroGroup.create_if_not_exists(message.chat.id, None, True)
locale = group.select_locale(app, message.from_user)
if (await app.get_chat_member(group.id, message.from_user.id)).status not in [
ChatMemberStatus.ADMINISTRATOR,
ChatMemberStatus.OWNER,
]:
await message.reply_text(
app._("permission_denied", "messages", locale=locale), quote=True
)
return
if group.locale_auto:
await message.reply_text(
app._("locale_auto_disabled", "messages", locale=locale).format(
default_locale=f"{app._('flag', 'metadata', locale=app.default_locale)} {app._('name', 'metadata', locale=app.default_locale)}"
)
)
else:
await message.reply_text(
app._("locale_auto_enabled", "messages", locale=locale).format(
default_locale=f"{app._('flag', 'metadata', locale=app.default_locale)} {app._('name', 'metadata', locale=app.default_locale)}"
)
)
logger.info(
"Automatic locale selection of group %s has been set to %s (group: %s)",
group.id,
not group.locale_auto,
group.locale,
)
await group.set_locale_auto(not group.locale_auto)

View File

@ -0,0 +1,15 @@
import logging
from pyrogram import filters
from pyrogram.types import Message
from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
logger = logging.getLogger(__name__)
@PyroClient.on_message(filters.new_chat_members & filters.group & filters.me)
async def handler_bot_join(app: PyroClient, message: Message):
logger.info("Bot has joined the group %s")
await PyroGroup.create_if_not_exists(message.chat.id, None, True)

View File

@ -12,6 +12,7 @@ from pyrogram.types import (
) )
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
from classes.pyrouser import PyroUser from classes.pyrouser import PyroUser
from modules.database import col_schedule from modules.database import col_schedule
from modules.kicker import kick_unstarted from modules.kicker import kick_unstarted
@ -19,34 +20,37 @@ from modules.kicker import kick_unstarted
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@PyroClient.on_message(filters.new_chat_members & ~filters.me) @PyroClient.on_message(
filters.new_chat_members & filters.group & ~filters.me & ~filters.bot
)
async def handler_user_join(app: PyroClient, message: Message): async def handler_user_join(app: PyroClient, message: Message):
group = await PyroGroup.create_if_not_exists(message.chat.id, None, True)
locale = group.select_locale(app, message.from_user)
if ( if (
app.config["whitelist"]["enabled"] app.config["whitelist"]["enabled"]
and message.chat.id not in app.config["whitelist"]["groups"] and group.id not in app.config["whitelist"]["groups"]
): ):
logger.info( logger.info(
"User %s has joined the group %s, but it's not whitelisted, ignoring.", "User %s has joined the group %s, but it's not whitelisted, ignoring.",
message.from_user.id, message.from_user.id,
message.chat.id, group.id,
) )
return return
logger.info( logger.info("User %s has joined the group %s", message.from_user.id, group.id)
"User %s has joined the group %s", message.from_user.id, message.chat.id
)
await message.delete() await message.delete()
# If user has already failed the test and joined once more # If user has already failed the test and joined once more
with contextlib.suppress(KeyError): with contextlib.suppress(KeyError):
user = await app.find_user(message.from_user, message.chat.id) user = await app.find_user(message.from_user, group.id)
if user.failed is True: if user.failed is True:
logger.info( logger.info(
"User %s has previously failed the captcha, kicking and banning him", "User %s has previously failed the captcha, kicking and banning him",
user.id, user.id,
) )
banned = await app.ban_chat_member(message.chat.id, user.id) banned = await app.ban_chat_member(group.id, user.id)
if isinstance(banned, Message): if isinstance(banned, Message):
await banned.delete() await banned.delete()
@ -54,42 +58,35 @@ async def handler_user_join(app: PyroClient, message: Message):
return return
await app.restrict_chat_member( await app.restrict_chat_member(
chat_id=message.chat.id, chat_id=group.id,
user_id=message.from_user.id, user_id=message.from_user.id,
permissions=ChatPermissions(can_send_messages=False), permissions=ChatPermissions(can_send_messages=False),
) )
user = PyroUser.create_if_not_exists(message.from_user.id, message.chat.id) user = await PyroUser.create_if_not_exists(message.from_user.id, group.id)
if user.mistakes > 0 or user.score > 0: if user.mistakes > 0 or user.score > 0:
user.set_score(0) await user.set_score(0)
user.set_mistakes(0) await user.set_mistakes(0)
await asyncio.sleep(2) await asyncio.sleep(2)
verification_request = await app.send_message( verification_request = await app.send_message(
chat_id=message.chat.id, chat_id=group.id,
text=app._( text=app._("welcome", "messages", locale=locale).format(
"welcome", mention=message.from_user.mention
"messages", ),
).format(mention=message.from_user.mention),
reply_markup=InlineKeyboardMarkup( reply_markup=InlineKeyboardMarkup(
[ [
[ [
InlineKeyboardButton( InlineKeyboardButton(
app._( app._("verify", "buttons", locale=locale),
"verify", callback_data=f"verify;{user.id}",
"buttons",
),
callback_data=f"verify;{message.from_user.id}",
) )
], ],
[ [
InlineKeyboardButton( InlineKeyboardButton(
app._( app._("ban", "buttons", locale=locale),
"ban", callback_data=f"ban;{user.id}",
"buttons",
),
callback_data=f"ban;{message.from_user.id}",
) )
], ],
], ],
@ -103,6 +100,6 @@ async def handler_user_join(app: PyroClient, message: Message):
[app, user.id, verification_request.chat.id, verification_request.id], [app, user.id, verification_request.chat.id, verification_request.id],
run_date=datetime.now() + timedelta(seconds=app.config["timeouts"]["join"]), run_date=datetime.now() + timedelta(seconds=app.config["timeouts"]["join"]),
) )
col_schedule.insert_one( await col_schedule.insert_one(
{"user": message.from_user.id, "group": message.chat.id, "job_id": job.id} {"user": user.id, "group": group.id, "job_id": job.id}
) )

93
plugins/language.py Normal file
View File

@ -0,0 +1,93 @@
import logging
from pykeyboard import InlineButton, InlineKeyboard
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.enums.chat_member_status import ChatMemberStatus
from pyrogram.types import CallbackQuery, Message
from classes.callbacks import CallbackLanguage
from classes.pyroclient import PyroClient
from classes.pyrogroup import PyroGroup
logger = logging.getLogger(__name__)
@PyroClient.on_message(
~filters.scheduled & filters.group & filters.command(["language"], prefixes=["/"]) # type: ignore
)
async def command_language(app: PyroClient, message: Message):
group = await PyroGroup.create_if_not_exists(message.chat.id, None, True)
locale = group.select_locale(app, message.from_user)
if (await app.get_chat_member(group.id, message.from_user.id)).status not in [
ChatMemberStatus.ADMINISTRATOR,
ChatMemberStatus.OWNER,
]:
await message.reply_text(
app._("permission_denied", "messages", locale=locale), quote=True
)
return
keyboard = InlineKeyboard(row_width=2)
buttons = []
for language, data in app.in_every_locale("metadata").items():
buttons.append(
InlineButton(f"{data['flag']} {data['name']}", f"language;{language}")
)
buttons.append(
InlineButton(
f"🤖 {app._('locale_default', 'buttons', locale=locale)}", "language;default"
)
)
keyboard.add(*buttons)
await message.reply_text(
app._("locale_choice", "messages", locale=locale),
reply_markup=keyboard,
)
@Client.on_callback_query(filters.regex(r"language;[\s\S]*")) # type: ignore
async def callback_language(app: PyroClient, callback: CallbackQuery):
group = await PyroGroup.create_if_not_exists(callback.message.chat.id, None, True)
locale = group.select_locale(app, callback.message.from_user)
if (await app.get_chat_member(group.id, callback.from_user.id)).status not in [
ChatMemberStatus.ADMINISTRATOR,
ChatMemberStatus.OWNER,
]:
await callback.answer(
app._("wrong_user", "callbacks", locale=locale), show_alert=True
)
return
parsed = CallbackLanguage.from_callback(callback)
logger.info(
"Locale of group %s has been set to %s (auto: %s)",
group.id,
parsed.language,
group.locale_auto,
)
if parsed.language == "default":
await group.set_locale(None)
await callback.answer(
app._("locale_set", "callbacks", locale=app.default_locale).format(
locale=app._("locale_default", "buttons", locale=locale)
),
show_alert=True,
)
return
await group.set_locale(parsed.language)
await callback.answer(
app._("locale_set", "callbacks", locale=parsed.language).format(
locale=app._("name", "metadata", locale=parsed.language)
),
show_alert=True,
)

View File

@ -1,9 +1,9 @@
aiofiles~=23.2.1 aiofiles~=23.2.1
apscheduler~=3.10.1 apscheduler~=3.10.1
async_pymongo==0.1.3
black~=23.7.0 black~=23.7.0
Pillow~=10.0.0 Pillow~=10.0.0
pykeyboard==0.1.5 pykeyboard==0.1.5
pymongo==4.4.1
pyrogram==2.0.106 pyrogram==2.0.106
tgcrypto==1.2.5 tgcrypto==1.2.5
ujson==5.8.0 ujson==5.8.0