41 Commits

Author SHA1 Message Date
393a7584d5 uk locale update 2023-03-06 19:28:31 +01:00
e50cdd53b3 Updated dependencies 2023-03-01 19:16:41 +01:00
3012c3d5ae Fixed object type and db lookup 2023-03-01 19:16:34 +01:00
8c21ae1844 Fixed absence of /warnings in config 2023-03-01 19:16:13 +01:00
3df57f1c42 Added one more venv to ignore 2023-03-01 19:15:53 +01:00
5b8951fe07 This commit closes #30 2023-02-20 15:04:24 +01:00
64b7a6a4ba Made message about JAPANESE girls even bolder 2023-02-20 15:04:09 +01:00
8d18ed2126 This commit closes #29 2023-02-15 11:13:07 +01:00
ed2361638a This commit closes #27 2023-02-07 11:35:47 +01:00
0c78f21c96 This commit closes #23 2023-02-01 14:21:48 +01:00
23b2925ffa This commit closes #17 2023-01-30 11:28:23 +01:00
82a878aa6d WIP: #17 2023-01-30 11:01:52 +01:00
360bdf2c72 This commit closes #24 2023-01-30 10:58:47 +01:00
f98fb0b6dc Probably fix for static emojis from #25 2023-01-30 10:54:43 +01:00
eaec6f2fe8 Fixed join messages not being sent 2023-01-30 10:39:14 +01:00
ee738d00b5 Merge branch 'dev' of https://git.profitroll.eu/profitroll/HoloCheckerBot into dev 2023-01-25 14:05:02 +01:00
fd19b4cb0b Seems like added a grayout for sponsors 2023-01-25 14:05:00 +01:00
Profitroll
c56385a181 Fixed spoiler flow 2023-01-23 18:25:03 +01:00
66a1ea17ab This commit is closing #20 2023-01-23 15:12:29 +01:00
b10a62c63e Updated requirements 2023-01-23 14:50:39 +01:00
a3c27ab4bd Updated default config 2023-01-23 14:48:29 +01:00
eeff6d40ce /issue command 2023-01-23 14:39:38 +01:00
7be7d5ac7b One hecking line added 2023-01-23 12:01:58 +01:00
1ce47b0aaf Polished solution for #18 2023-01-23 12:01:35 +01:00
f14e80856c This commit closes 2023-01-23 11:57:32 +01:00
431b2d048f This commit closes #11 2023-01-23 11:29:58 +01:00
7feaa7af56 This commit closes #14 2023-01-23 11:25:01 +01:00
834157030c This commit closes #19 2023-01-23 11:20:56 +01:00
a59a42ffd9 Is also connected #15 2023-01-23 11:17:39 +01:00
f4c1bf9587 This commit closes #15 2023-01-23 11:17:00 +01:00
adc8c83102 Closes #13 2023-01-23 11:15:26 +01:00
e3fd4b3576 This commit closes #21 2023-01-23 11:10:06 +01:00
4019f2f376 This fix resolves #10 2023-01-23 11:03:04 +01:00
51b943b576 Added format notice in 2nd question of application 2023-01-16 12:10:40 +01:00
de552db4c8 Reapply improved 2023-01-16 12:10:07 +01:00
8beb33b7c3 Now ignoring bdays of users that left 2023-01-16 12:09:53 +01:00
05e3916478 Fixed reply messages 2023-01-16 12:09:28 +01:00
414bfefb21 Fixed tmp download function 2023-01-13 14:45:23 +01:00
fd5e0b5b22 Spoilers on custom filters 2023-01-13 10:43:27 +01:00
bd925418fd YouTube new videos on channels monitoring 2023-01-12 13:57:41 +01:00
42a4a2e58e Internal and external spoilers are now separated 2023-01-12 11:04:52 +01:00
35 changed files with 480 additions and 163 deletions

1
.gitignore vendored
View File

@@ -162,5 +162,6 @@ TASK.md
inline_bot.py inline_bot.py
.vscode .vscode
migrate.py migrate.py
venv_linux
validation/* validation/*
!validation/*.json !validation/*.json

View File

@@ -33,7 +33,9 @@ You can see config file with all the comments below:
"debug": false, "debug": false,
"owner": 0, "owner": 0,
"age_allowed": 0, "age_allowed": 0,
"age_maximum": 70,
"api": "http://example.com", "api": "http://example.com",
"issues": "https://github.com/example/test/issues/new",
"inline_preview_count": 7, "inline_preview_count": 7,
"remove_application_time": -1, "remove_application_time": -1,
"search_radius": 50, "search_radius": 50,
@@ -81,7 +83,8 @@ You can see config file with all the comments below:
"enabled": false "enabled": false
}, },
"spoilers": { "spoilers": {
"enabled": true "enabled": true,
"allow_external": true
} }
}, },
"scheduler": { "scheduler": {
@@ -104,6 +107,11 @@ You can see config file with all the comments below:
"cache_admins": { "cache_admins": {
"interval": 120, "interval": 120,
"enabled": true "enabled": true
},
"channels_monitor": {
"interval": 5,
"enabled": true,
"channels": []
} }
}, },
"locations": { "locations": {
@@ -144,7 +152,6 @@ You can see config file with all the comments below:
"permissions": [ "permissions": [
"users", "users",
"admins", "admins",
"group_users",
"group_admins" "group_admins"
], ],
"modules": [ "modules": [
@@ -153,7 +160,7 @@ You can see config file with all the comments below:
}, },
"warn": { "warn": {
"permissions": [ "permissions": [
"group_users" "group_users_admins"
], ],
"modules": [ "modules": [
"warnings" "warnings"
@@ -213,6 +220,15 @@ You can see config file with all the comments below:
"sponsorships" "sponsorships"
] ]
}, },
"issue": {
"permissions": [
"users",
"admins"
],
"modules": [
"general"
]
},
"application": { "application": {
"permissions": [ "permissions": [
"admins", "admins",

View File

@@ -319,7 +319,7 @@ class HoloUser():
if progress["state"] == "fill" and progress["sent"] is False: if progress["state"] == "fill" and progress["sent"] is False:
if msg.text is not None: if msg.text is not None:
msg.text = fix_text(msg.text) msg.text = fix_text(str(msg.text))
if stage == 2: if stage == 2:
@@ -330,7 +330,7 @@ class HoloUser():
await msg.reply_text(locale(f"question2_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale)))) await msg.reply_text(locale(f"question2_invalid", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale(f"question{stage}", "force_reply", locale=self.locale))))
return return
if datetime.now() <= input_dt: if (datetime.now() <= input_dt) or ((datetime.now() - input_dt).days) > ((datetime.now() - datetime.now().replace(year=datetime.now().year - configGet("age_maximum"))).days):
logWrite(f"User {msg.from_user.id} failed stage {stage} due to joking") logWrite(f"User {msg.from_user.id} failed stage {stage} due to joking")
await msg.reply_text(locale("question2_joke", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply", locale=self.locale)))) await msg.reply_text(locale("question2_joke", "message", locale=self.locale), reply_markup=ForceReply(placeholder=str(locale("question2", "force_reply", locale=self.locale))))
return return
@@ -445,7 +445,7 @@ class HoloUser():
stage = progress["stage"] stage = progress["stage"]
if msg.text is not None: if msg.text is not None:
msg.text = fix_text(msg.text) msg.text = fix_text(str(msg.text))
elif msg.caption is not None: elif msg.caption is not None:
msg.caption = fix_text(msg.caption) msg.caption = fix_text(msg.caption)

View File

@@ -3,7 +3,9 @@
"debug": false, "debug": false,
"owner": 0, "owner": 0,
"age_allowed": 0, "age_allowed": 0,
"age_maximum": 70,
"api": "http://example.com", "api": "http://example.com",
"issues": "https://github.com/example/test/issues/new",
"inline_preview_count": 7, "inline_preview_count": 7,
"remove_application_time": -1, "remove_application_time": -1,
"search_radius": 50, "search_radius": 50,
@@ -51,7 +53,8 @@
"enabled": false "enabled": false
}, },
"spoilers": { "spoilers": {
"enabled": true "enabled": true,
"allow_external": true
} }
}, },
"scheduler": { "scheduler": {
@@ -74,6 +77,11 @@
"cache_admins": { "cache_admins": {
"interval": 120, "interval": 120,
"enabled": true "enabled": true
},
"channels_monitor": {
"interval": 5,
"enabled": true,
"channels": []
} }
}, },
"locations": { "locations": {
@@ -128,6 +136,15 @@
"warnings" "warnings"
] ]
}, },
"warnings": {
"permissions": [
"admins",
"group_admins"
],
"modules": [
"warnings"
]
},
"reapply": { "reapply": {
"permissions": [ "permissions": [
"users", "users",
@@ -182,6 +199,15 @@
"sponsorships" "sponsorships"
] ]
}, },
"issue": {
"permissions": [
"users",
"admins"
],
"modules": [
"general"
]
},
"application": { "application": {
"permissions": [ "permissions": [
"admins", "admins",

View File

@@ -15,6 +15,7 @@ from modules.commands.application import *
from modules.commands.applications import * from modules.commands.applications import *
from modules.commands.cancel import * from modules.commands.cancel import *
from modules.commands.identify import * from modules.commands.identify import *
from modules.commands.issue import *
from modules.commands.label import * from modules.commands.label import *
from modules.commands.message import * from modules.commands.message import *
from modules.commands.nearby import * from modules.commands.nearby import *
@@ -28,10 +29,11 @@ from modules.commands.start import *
from modules.commands.warn import * from modules.commands.warn import *
from modules.commands.warnings import * from modules.commands.warnings import *
from modules.callbacks.ban import *
from modules.callbacks.nothing import * from modules.callbacks.nothing import *
from modules.callbacks.reapply import * from modules.callbacks.reapply import *
from modules.callbacks.rules import * from modules.callbacks.rules import *
from modules.callbacks.sid import * 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 *

View File

@@ -9,7 +9,7 @@
"question4": "When did you first learn about Hololive?", "question4": "When did you first learn about Hololive?",
"question5": "What made you interested in Hololive?", "question5": "What made you interested in Hololive?",
"question6": "Which girl's content do you like the most?", "question6": "Which girl's content do you like the most?",
"question7": "Name the content of at least five Japanese girls you like the most.", "question7": "Name the content of at least five **JAPANESE** girls you like the most.",
"question8": "Do you watch streams of Hololive girls?", "question8": "Do you watch streams of Hololive girls?",
"question9": "Whose songs from Hololive do you like the most?", "question9": "Whose songs from Hololive do you like the most?",
"question10": "And finally, tell us a little about yourself. About hobbies, what you like to do. In one message, please.", "question10": "And finally, tell us a little about yourself. About hobbies, what you like to do. In one message, please.",
@@ -31,7 +31,6 @@
"approved_joined": "Congratulations! Your application has been reviewed and confirmed as correct. Thank you for your time and have a nice day!", "approved_joined": "Congratulations! Your application has been reviewed and confirmed as correct. Thank you for your time and have a nice day!",
"read_rules": "Please read these rules before clicking the button and joining the chat.", "read_rules": "Please read these rules before clicking the button and joining the chat.",
"rejected.": "Oh dear! Your application has been reviewed but not confirmed as eligible to join the community. Better luck next time!\n\nYou can try to reapply with the /reapply command.", "rejected.": "Oh dear! Your application has been reviewed but not confirmed as eligible to join the community. Better luck next time!\n\nYou can try to reapply with the /reapply command.",
"rejected_aggressive": "Oh dear! Your application has been reviewed, but not confirmed as eligible to join the community.",
"rejected_russian": "Russian warship, go fuck yourself!", "rejected_russian": "Russian warship, go fuck yourself!",
"approved_by": "✅ **Application approved**\nAdmin **{0}** has reviewed and approved application `{1}`.", "approved_by": "✅ **Application approved**\nAdmin **{0}** has reviewed and approved application `{1}`.",
"rejected_by": "❌ **Form rejected**\nAdmin **{0}** has reviewed and rejected form `{1}`.", "rejected_by": "❌ **Form rejected**\nAdmin **{0}** has reviewed and rejected form `{1}`.",
@@ -122,7 +121,6 @@
"button": { "button": {
"sub_yes": "✅ Accept", "sub_yes": "✅ Accept",
"sub_no": "❌ Reject", "sub_no": "❌ Reject",
"sub_aggressive": "🤡 Reject (Toxic)",
"sub_russian": "🇷🇺 Reject (Russian)", "sub_russian": "🇷🇺 Reject (Russian)",
"accepted": "✅ Accepted", "accepted": "✅ Accepted",
"declined": "❌ Rejected", "declined": "❌ Rejected",
@@ -145,7 +143,6 @@
"callback": { "callback": {
"sub_accepted": "✅ Application {0} has been approved", "sub_accepted": "✅ Application {0} has been approved",
"sub_rejected": "❌ Application {0} rejected", "sub_rejected": "❌ Application {0} rejected",
"sub_aggressive": "🤡 Application {0} rejected",
"sub_russian": "🇷🇺 Application {0} rejected", "sub_russian": "🇷🇺 Application {0} rejected",
"sus_allowed": "✅ Access {0} allowed", "sus_allowed": "✅ Access {0} allowed",
"sus_rejected": "❌ Access {0} denied", "sus_rejected": "❌ Access {0} denied",

View File

@@ -1,15 +1,15 @@
{ {
"message": { "message": {
"start": "Привіт і ласкаво просимо!\n\nЦей бот створено для прийому заявок на вступ до нашої спільноти. Для продовження нас цікавить відповідь на одне питання:\n\nЧи хочеш ти доєднатися до українського ком'юніті фанатів Хололайв?", "start": "Привіт і ласкаво просимо!\n\nМи будуємо українське ком'юніті фанатів Гололайву і раді кожному, хто поділяє наші інтереси або тільки зацікавився цією тематикою та хоче дізнатись більше. Чим ми відрізняеємось від звичайного тематичного чату? Ми намагаємось створювати усі можливі умови, щоб люди знаходили однодумців у своїх містах та збирались разом. Інколи проводимо великі зустрічі на честь Голо-івентів у Києві. Збираємось у Дискорді для сумісних переглядів концертів, музичних топів, ігор тощо. Проводимо різні івенти у чаті. Об'єднуємось, щоб замовляти офіційний мерч із Японії. Підтримуємо україномовних кліперів та будуємо плани по поширенню нашого ком'юніті на майбутнє.\n\nЦей бот створений для прийому заявок на вступ до нашої чат-спільноти. Усі анкети, після підтвердження адміністрацією, можуть дивитися й інші учасники чату у будь-який момент. Тому, будь ласка, віднесіться до її заповнення відповідально.\n\nЯкщо вашу анкету відхилили, то, скоріше за все:\n1) Вона порушує перше правило чату (/rules - ознайомитись перед вступом до чату).\n2) Ви намагаєтесь додати до чату \"додатковий/запасний\" акаунт.\n3) Ви не дуже відповідально віднеслись до її заповнення.\n\nЯкщо із вашою анкетою щось не так, то вам через бота прийде повідомлення від адміністраторів для вирішення питання. Якщо ви зіштовхнулись із якоюсь проблемою або бот не надсилає вам необхідні повідомлення - напишіть мені у приватні повідомлення @Chirkopol.\n\nПісля прийому вашої анкети бот згенерує вам одноразове посилання. Не забудьте перейти по ньому, щоб потрапити до чату.\n\nДля продовження, нас цікавить відповідь на питання:\nЧи хочеш ти доєднатись до українського ком'юніті фанатів Гололайв та чи зобов'язуєшся ти дотримуватися усіх правил?",
"goodbye": "Добре, дякуємо за чесність! Вибачте, але за таких умов ми не будемо тебе додавати до спільноти. Якщо передумаєш та захочеш приєднатись - просто натисни на кнопку.", "goodbye": "Добре, дякуємо за чесність! Вибачте, але за таких умов ми не будемо тебе додавати до спільноти. Якщо передумаєш та захочеш приєднатись - просто натисни на кнопку.",
"privacy_notice": "Раді це чути!\n\nДля продовження треба буде заповнити невеличку анкетку. Будь ласка, віднесись до цього серйозно. Ми відповідально ставимось до персональних даних, тому ця анкета не буде передана третім особам, а буде використана лише для проходження до спільноти.", "privacy_notice": "Раді це чути!\n\nДля продовження треба буде заповнити невеличку анкетку. Будь ласка, віднесись до цього серйозно. Ми відповідально ставимось до персональних даних, тому ця анкета не буде передана третім особам, а буде використана лише для проходження до спільноти та подальшої взаємодії в ній.",
"question1": "Як до тебе можна звертатись?", "question1": "Як до тебе можна звертатись?",
"question2": "Коли в тебе день народження?", "question2": "Коли в тебе день народження?\n\nБудь ласка, у форматі ДД.ММ.РРРР",
"question3": "З якого ти міста або де проживаєш зараз?\n\n⚠ Будь ласка, не вказуйте точних адрес! \"Київ\" або \"Київська Область\" є достатньою конкретизацією.\n\nПриклади:\n• Київ\n• Одеська область\n• Макіївка (Луганська область)", "question3": "З якого ти міста або де проживаєш зараз?\n\n⚠ Будь ласка, не вказуйте точних адрес! \"Київ\" або \"Київська Область\" є достатньою конкретизацією.\n\nПриклади:\n• Київ\n• Одеська область\n• Макіївка (Луганська область)",
"question4": "Коли вперше довелось дізнатись про Хололайв?", "question4": "Коли вперше довелось дізнатись про Хололайв?",
"question5": "Чим тебе зацікавив Хололайв?", "question5": "Чим тебе зацікавив Хололайв?",
"question6": "Контент якої дівчини тобі подобається найбільше?", "question6": "Контент якої дівчини тобі подобається найбільше?",
"question7": "Назви контент хоча б п'яти японських холодівчат, які тобі подобаються найбільше.", "question7": "Назви контент хоча б п'яти **__--ЯПОНСЬКИХ--__** холодівчат, які тобі подобаються найбільше.",
"question8": "Чи дивишся ти стріми дівчат Хололайву?", "question8": "Чи дивишся ти стріми дівчат Хололайву?",
"question9": "Чиї пісні з Хололайву тобі подобаються найбільше?", "question9": "Чиї пісні з Хололайву тобі подобаються найбільше?",
"question10": "Ну і нарешті, розкажи трохи про себе. Про хобі, чим тобі подобається займатись. Одним повідомленням, будь ласка.", "question10": "Ну і нарешті, розкажи трохи про себе. Про хобі, чим тобі подобається займатись. Одним повідомленням, будь ласка.",
@@ -44,11 +44,10 @@
"startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)", "startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)",
"startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)", "startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)",
"startup_downtime_days": "Запуск бота з підом `{0}` (лежав {1} дн.)", "startup_downtime_days": "Запуск бота з підом `{0}` (лежав {1} дн.)",
"approved": "Вітаємо! Твою анкету переглянули та підтвердили твоє право на вступ. Скористайся кнопкою під повідомленням щоб вступити до нашої лампової спільноти!", "approved": "Вітаємо! Твою анкету переглянули та підтвердили твоє право на вступ.\n\nПеред тим, як ти натиснеш на кнопочку під повідомленням, щоб вступити до нашої лампової спільноти, дамо тобі трішечки додаткової інформації.\nПам'ятай, що натискаючи її, ти підтверджуєш, що ознайомився із нашими правилами (/rules) та зобов'язуєшся їх дотримуватись.\n\nПісля того, як потрапиш до чату, не закидуй цього бота далеко.\nПрописавши @holoua_bot у боті, ти відкриєш список із усіх наших анкет. Ти можеш натискати на будь-яку із них та дізнаватись про кожного із нас більше інформації.\nЗавдяки команді /nearby, ти зможеш дізнатись, чи є серед нас однодумці із твого міста.\nЯкщо у тебе є спонсорська підписка на будь-кого із учасниць Гололайву, то ти маєш право отримати унікальну роль у нашому чаті (/sponsorship) та виділятись серед інших учасників. (А ще зможеш отримати декілька додаткових функцій. Але нікому про це не кажи!)",
"approved_joined": "Вітаємо! Твою анкету переглянули та підтвердили її правильність. Дякуємо за витрачений на заповнення час та гарного дня!", "approved_joined": "Вітаємо! Твою анкету переглянули та підтвердили її правильність. Дякуємо за витрачений на заповнення час та гарного дня!",
"read_rules": "Будь ласка, прочитай ці правила перш ніж натискати на кнопку та приєднуватись до чату.", "read_rules": "Будь ласка, прочитай ці правила перш ніж натискати на кнопку та приєднуватись до чату.",
"rejected": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти. Better luck next time!\n\nТи можеш спробувати повторно заповнити анкету командою /reapply", "rejected": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти. Better luck next time!\n\nТи можеш спробувати повторно заповнити анкету командою /reapply",
"rejected_aggressive": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти.",
"rejected_russian": "русский военньій корабль, иди нахуй!", "rejected_russian": "русский военньій корабль, иди нахуй!",
"approved_by": "✅ **Анкету схвалено**\nАдмін **{0}** переглянув та схвалив анкету `{1}`.", "approved_by": "✅ **Анкету схвалено**\nАдмін **{0}** переглянув та схвалив анкету `{1}`.",
"rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.",
@@ -86,12 +85,13 @@
"message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.",
"message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`", "message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`",
"message_from": "Повідомлення від **{0}** (`{1}`):\n\n", "message_from": "Повідомлення від **{0}** (`{1}`):\n\n",
"message_reply_notice": "\n\n**Щоб надіслати відповідь на це повідомлення, тегніть його.**", "message_reply_notice": "\n\n__Для того, щоб адміністрація побачила вашу відповідь, відправте її **реплаєм на це повідомлення**__",
"message_error": "⚠️ **Сталась помилка**\nНе вдалось надіслати ваше повідомлення. Розробника повідомлено про цю помилку.", "message_error": "⚠️ **Сталась помилка**\nНе вдалось надіслати ваше повідомлення. Розробника повідомлено про цю помилку.",
"message_traceback": "⚠️ **Сталась помилка**\nПомилка повідомлень: `{0}` -> `{1}`\nПомилка: `{2}`\n\nTraceback:\n```\n{3}\n```", "message_traceback": "⚠️ **Сталась помилка**\nПомилка повідомлень: `{0}` -> `{1}`\nПомилка: `{2}`\n\nTraceback:\n```\n{3}\n```",
"no_user_application": "Не знайдено користувачів за запитом **{0}**", "no_user_application": "Не знайдено користувачів за запитом **{0}**",
"user_invalid": "Надісланий користувач не має завершеної анкети.", "user_invalid": "Надісланий користувач не має завершеної анкети.",
"joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", "joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням",
"joined_application": "{0} (@{1})\n\n**Дані анкети:**\n{2}",
"sponsorships_expires": "⚠️ **Нагадування**\nНадана платна підписка припинить діяти **за {0} д**. Будь ласка, оновіть дані про неї командою /sponsorship інакше роль буде втрачено!", "sponsorships_expires": "⚠️ **Нагадування**\nНадана платна підписка припинить діяти **за {0} д**. Будь ласка, оновіть дані про неї командою /sponsorship інакше роль буде втрачено!",
"sponsorships_expired": "⚠️ **Нагадування**\nТермін дії вказаної підписки сплив. Для повторного отримання ролі користуйся командою /sponsorship.", "sponsorships_expired": "⚠️ **Нагадування**\nТермін дії вказаної підписки сплив. Для повторного отримання ролі користуйся командою /sponsorship.",
"label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів", "label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів",
@@ -102,6 +102,7 @@
"nearby_result": "Результати пошуку:\n\n{0}", "nearby_result": "Результати пошуку:\n\n{0}",
"nearby_empty": "Здається, нікого поблизу немає.", "nearby_empty": "Здається, нікого поблизу немає.",
"cancel": "Всі поточні операції скасовано.", "cancel": "Всі поточні операції скасовано.",
"cancel_reapply": "Всі поточні операції скасовано.\nЩоб знову заповнити анкету користуйся /reapply",
"identify_invalid_syntax": "Неправильний синтаксис!\nТреба: `/identify ID/NAME/USERNAME`", "identify_invalid_syntax": "Неправильний синтаксис!\nТреба: `/identify ID/NAME/USERNAME`",
"identify_not_found": "Не знайдено користувачів за запитом **{0}**", "identify_not_found": "Не знайдено користувачів за запитом **{0}**",
"identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}", "identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}",
@@ -109,15 +110,22 @@
"spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення", "spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення",
"spoiler_cancel": "Створення спойлера було припинено", "spoiler_cancel": "Створення спойлера було припинено",
"spoiler_empty": "Спойлер категорії \"{0}\" без опису", "spoiler_empty": "Спойлер категорії \"{0}\" без опису",
"spoiler_empty_named": "Спойлер категорії \"{0}\" без опису від **{1}**",
"spoiler_described": "Спойлер категорії \"{0}\": {1}", "spoiler_described": "Спойлер категорії \"{0}\": {1}",
"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_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).", "spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).",
"spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.", "spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.",
"spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.", "spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.",
"youtube_video": "На каналі [{0}]({1}) нове відео!\n\n**[{2}]({3})**",
"not_member": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.",
"issue": "**Допоможіть боту**\nЗнайшли баг або помилку? Маєте файну ідею для нової функції? Повідомте нас, створивши нову задачу на гіті.\n\nЗа можливості, опишіть свій запит максимально детально. Якщо є змога, також додайте скріншоти або додаткову відому інформацію.",
"you_are_banned": "⚠️ **Вас було заблоковано**\nТепер не можна відправити анкету або користуватись командами бота.",
"yes": "Так", "yes": "Так",
"no": "Ні", "no": "Ні",
"voice_message": [ "voice_message": [
@@ -203,7 +211,6 @@
"button": { "button": {
"sub_yes": "✅ Прийняти", "sub_yes": "✅ Прийняти",
"sub_no": "❌ Відхилити", "sub_no": "❌ Відхилити",
"sub_aggressive": "🤡 Відхилити (Токс)",
"sub_russian": "🇷🇺 Відхилити (Русак)", "sub_russian": "🇷🇺 Відхилити (Русак)",
"sponsor_yes": "✅ Прийняти", "sponsor_yes": "✅ Прийняти",
"sponsor_no": "❌ Відхилити", "sponsor_no": "❌ Відхилити",
@@ -226,16 +233,21 @@
"done": "✅ Готово", "done": "✅ Готово",
"sponsor_apply": "Заповнити форму", "sponsor_apply": "Заповнити форму",
"sponsor_started": "Форму розпочато", "sponsor_started": "Форму розпочато",
"spoiler_send": "Надіслати", "spoiler_view": "Переглянути",
"spoiler_view": ереглянути" "spoiler_preview": опередній перегляд",
"spoiler_send_chat": "Надіслати в холо-чат",
"spoiler_send_other": "Надіслати в інший чат",
"issue": "🪄 Створити задачу",
"ban": "💀 Заблокувати",
"banned": "☠️ Заблоковано"
}, },
"callback": { "callback": {
"sub_accepted": "✅ Анкету {0} схвалено", "sub_accepted": "✅ Анкету {0} схвалено",
"sub_rejected": "❌ Анкету {0} відхилено", "sub_rejected": "❌ Анкету {0} відхилено",
"sub_aggressive": "🤡 Анкету {0} відхилено",
"sub_russian": "🇷🇺 Анкету {0} відхилено", "sub_russian": "🇷🇺 Анкету {0} відхилено",
"sus_allowed": "✅ Доступ {0} дозволено", "sus_allowed": "✅ Доступ {0} дозволено",
"sus_rejected": "❌ Доступ {0} заборонено", "sus_rejected": "❌ Доступ {0} заборонено",
"sub_banned": "☠️ Користувача заблоковано",
"nothing": "🔔 Дія вже виконана", "nothing": "🔔 Дія вже виконана",
"rules_page": " Показано правило {0}", "rules_page": " Показано правило {0}",
"rules_home": " Показано головну правил", "rules_home": " Показано головну правил",
@@ -243,7 +255,9 @@
"reapply_stopped": " Перервано заповнення анкети", "reapply_stopped": " Перервано заповнення анкети",
"sponsor_started": " Заповнення форми розпочато", "sponsor_started": " Заповнення форми розпочато",
"sponsor_accepted": "✅ Форму {0} схвалено", "sponsor_accepted": "✅ Форму {0} схвалено",
"sponsor_rejected": "❌ Форму {0} відхилено" "sponsor_rejected": "❌ Форму {0} відхилено",
"spoiler_sent": "✅ Повідомлення надіслано в холо-чат",
"spoiler_forbidden": "❌ Треба бути учасником чату"
}, },
"inline": { "inline": {
"forbidden": { "forbidden": {
@@ -266,24 +280,25 @@
"description": "Надіслати цей спойлер до чату" "description": "Надіслати цей спойлер до чату"
} }
}, },
"rules_msg": "📢Правила можуть доповнюватись та змінюватись, залежно від потреби. У такому разі, порушення, які були вчинені до введення (змінення) правила, порушеннями вважатися не будуть. Про всі зміни в правилах, ви будете проінформовані за допомогою закріплених повідомлень. Але вони не будуть закріплені на постійній основі, тому, час від часу, перевіряйте актуальність правил у боті.\n\n🔔Якщо ви бачите, як хтось із учасників порушив правила, тегніть одного із адмінів, у відповідь на повідомлення, яке, на вашу думку, є порушенням. У дописі до тегу, вкажіть, по якому пункту ви побачили порушення. Або перешліть повідомлення до будь кого із адміністраторів у особисті повідомлення, та коротко опишіть ситуацію.\nСписок адміністраторів: @Chirkopol @Za_NerZula @Denialvapr\nЗ питань функціонування бота звертайтесь до @Profitroll2281337\n\n❗Будь-який заборонений контент, може бути відправлений допомогою команди /spoiler у бота - з повним описом контенту, що міститься під спойлером. За неправильний або некоректний опис, може бути видане попередження.\n\n‼Видалені або змінені повідомлення, все ще є повідомленнями від вашого імені, які могли побачити учасники чату, і які можуть бути відстежені через адмінську панель.\n\n🔨 За порушення - ви отримаєте попередження. За наявності 3-х попереджень - мут на добу. За повторні порушення, ви одразу отримаєте покарання, без додаткових попереджень.", "rules_msg": "📢Правила можуть доповнюватись та змінюватись, залежно від потреби. У такому разі, порушення, які були вчинені до введення (змінення) правила, порушеннями вважатися не будуть. Про всі зміни в правилах, ви будете проінформовані за допомогою закріплених повідомлень. Але вони не будуть закріплені на постійній основі, тому, час від часу, перевіряйте актуальність правил у боті.\n\n🔔Якщо ви бачите, як хтось із учасників порушив правила, тегніть одного із адмінів, у відповідь на повідомлення, яке, на вашу думку, є порушенням. У дописі до тегу, вкажіть, по якому пункту ви побачили порушення. Або перешліть повідомлення до будь кого із адміністраторів у особисті повідомлення, та коротко опишіть ситуацію.\nСписок адміністраторів: @Chirkopol @Za_NerZula @Toxinushka\nЗ питань функціонування бота звертайтесь до @Profitroll2281337\n\n❗Будь-який заборонений контент, може бути відправлений за допомогою команди /spoiler у бота - з повним описом контенту, що міститься під спойлером. За неправильний або некоректний опис, може бути видане попередження.\n\n‼Видалені або змінені повідомлення, все ще є повідомленнями від вашого імені, які могли побачити учасники чату, і які можуть бути відстежені через адмінську панель.\n\n🔨 За порушення - ви отримаєте попередження. За наявності 3-х попереджень - мут на добу. За повторні порушення, ви одразу отримаєте покарання, без додаткових попереджень.",
"rules": [ "rules": [
"1⃣) \"HoloKyiv Chat\" та \"HoloUA (Hololive Ukraine) Chat\" створені виключно для українців (13+). В них можуть знаходитись тільки люди які: \n- Народились в Україні, та проживають, на данний момент, у ній.\n- Народились за межами України, але проживають у ній.\n- Народились в Україні але, на даний момент, не проживають у ній.\n\"HoloUA (Hololive Ukraine) Chat\" відкритий для усіх українців. Щоб потрапити до нього, заповніть, будь ласка, анкету, та дочекайтесь, поки її схвалять адміни.\nУ \"HoloKyiv Chat\" можна потрапити тільки особисто, якщо ви проживаєте у Київі, або є близьким другом одного із учасників чату. Із приводу додавання до чату пишіть @Chirkopol у приватні повідомлення.\n🔨 Якщо у процесі спілкування виявиться, що ви не українець, вас буде видалено із чату, до моменту, поки ви їм не станете. Без образ. Ми створюємо виключно українське ком'юніті.", "1⃣) \"HoloKyiv Chat\" та \"HoloUA (Hololive Ukraine) Chat\" створені виключно для українців (13+). В них можуть знаходитись тільки люди які: \n- Народились в Україні, та проживають, на данний момент, у ній.\n- Народились за межами України, але проживають у ній.\n- Народились в Україні але, на даний момент, не проживають у ній.\n\"HoloUA (Hololive Ukraine) Chat\" відкритий для усіх українців. Щоб потрапити до нього, заповніть, будь ласка, анкету, та дочекайтесь, поки її схвалять адміни.\nУ \"HoloKyiv Chat\" можна потрапити тільки особисто, якщо ви проживаєте у Київі, або є близьким другом одного із учасників чату. Із приводу додавання до чату пишіть @Chirkopol у приватні повідомлення.\n🔨 Якщо у процесі спілкування виявиться, що ви не українець, вас буде видалено із чату, до моменту, поки ви їм не станете. Без образ. Ми створюємо виключно українське ком'юніті.",
"2⃣) Заборонено поширення NSFW-контенту з прямим або частково прихованим порнографічним змістом. На контенті \"еротичного характеру\" повинні бути закриті \"сумнівні\" ділянки тіл. \nЗаборонено поширення шок-контенту з великою наявністю крові та/або фізичних пошкоджень.", "2⃣) Заборонено поширення артів, на яких зображено:\n2.1 - NSFW-контент з прямим або частково прихованим порнографічним змістом.\n2.2 - Контент \"еротичного характеру\" із неприкритими \"сумнівними\" ділянками тіл (так звані \"private parts\") або їх помітними силуетами крізь одяг.\n2.3 - Фетиш-контент, який спрямований на дуже вузьке коло шанувальників та може порушувати закон. Наприклад: копрофілія, педофілія, зоофілія.\n2.4 - Шок-контент із великою кількістю крові та/або фізичних пошкоджень.\n2.5 - Контент, який порушує будь-яке інше правило.\n❗Якщо якійсь арт викликає у вас сумніви і ви не впевнені, що він не порушує правила, скористайтесь командою /spoiler у боті. Це точно збереже вас від зайвих проблем. Але не забувайте робити опис спойлеру!",
"3⃣) Анонімність учасників Хололайв та Холостарз.\nЗаборонено: \n- Фотографії\n- Імена\n- Місце проживання\n- Точний вік (слово \"холохеґз\" не підпадає)\n- Подробиці особистого життя\n- Пости з руммейт акаунтів чи згадка про них з конкретними даними (тобто фраза \"там на ірл каналі Каллі щось вийшло\" - можна, а \"там на *назва ірл каналу* щось вийшло\" - ні)\n- Подробиці з минулого дівчат - лише поверхнево і без конкретики (тобто \"була офісним працівником\" - ок, \"була в *компанія_нейм*\" - ні).\nВиключення - якщо дівчата самі згадували про це на архівних(!) стрімах.\n❗Це правило не стосується тих, хто вже не знаходиться у Хололайві, або ніколи і не був його частиною. Але, прохання, відноситись до особистого життя інших вітуберів із повагою, та не перебільшувати із деанонами їх особистостей.", "3⃣) Анонімність учасників Гололайв та Голостарз.\nЗаборонено: \n- Фотографії.\n- Імена.\n- Місце проживання.\n- Точний вік (слово \"холохеґз\" не підпадає).\n- Подробиці особистого життя, які не було розкриті на Гололайв-акаунтах.\n- Пости з руммейт-акаунтів чи згадка про них з конкретними даними. Пояснення: фраза \"там на ірл каналі Каллі щось вийшло\" - можна, а \"там на *назва ірл каналу* щось вийшло\" - ні.\n- Подробиці з минулого дівчат - лише поверхнево і без конкретики (тобто, \"була офісним працівником\" - ок, \"була в *компанія_нейм*\" - ні).\nВиключення - якщо дівчата самі згадували про це на архівних(!) стрімах.\n❗Це правило не стосується тих, хто вже не знаходиться у Гололайві або ніколи і не був його частиною. Але, прохання, відноситись до особистого життя інших вітуберів із повагою та не перебільшувати із деанонами їх особистостей.",
"4⃣) Заборонено флуд однотипними повідомленнями, емодзі, смайликами, стікерами, гіфками тощо. Орієнтовна кількість повідомлень, які можуть отримати попередження за це правило - 5. Кожна ситуація може розглядатись окремо, але вкладайте усі свої думки в одне повідомлення.", "4⃣) Заборонено флуд однотипними текстовими повідомленнями, які не несуть у собі сенсу, емоджі, смайликами, стікерами, ґіфками, великою кількістю артів тощо. Орієнтовна кількість повідомлень, за які можна отримати попередження за це правило - 5. \nЯкщо учасник буде цілеспрямовано відправляти по менше ніж 5 повідомлень, але робити це регулярно, то попередження також може бути видане.\nПояснення: \"Відправлю зараз 4 гіфки, почекаю, поки хтось напише декілька повідомлень, і знову відправлю 4 гіфки\".\n- Попередження про флуд одночасно можуть отримати декілька учасників.\nПояснення: декілька учасників чату, по черзі, відправляють по декілька (менше п'яти) стікерів, але спільними зусиллями, і їх стає дуже багато.\n❗Кожна ситуація може і буде розглядатись окремо, але намагайтеся вкладати всі свої думки в одне повідомлення.\n❗Якщо ви масово відправляєте контент зі сторонніх ресурсів, будь то якісь новини у великій кількості (анонси мерчу, концертів тощо) або арти, кліпи та подібне, і ви ніяк не зможете посприяти тому, щоб уся інформація була подана більш компактно, то це правило на вас не розповсюджується, але не зловживайте цим.\n‼Це правило не стосується організованих івентів та флешмобів, затверджених адміністраторами.",
"5⃣) Заборонені відео та аудіо-повідомлення, які не несуть цілі передати почуте, або побачене. Якщо ви бажаєте розповісти про те, як пройшов ваш день, але не маєте можливості друкувати повідомлення, використовуйте магічний перетворювач войсів у текст - https://t.me/voicybot", "5⃣) Заборонені відео- та аудіо-повідомлення, які не несуть цілі передати почуте або побачене. \n❗Якщо ви бажаєте розповісти про те, як пройшов ваш день, але не маєте можливості друкувати повідомлення, використовуйте магічний перетворювач войсів у текст - https://t.me/voicybot",
"6⃣) Заборонені образи, погрози, булінг, приниження, тролінг учасників, членів їхніх сімей, друзів та іншого кола, що є наближеними до учасника чату. Повідомлення на кшталт: \"йди на ... \" - також є образами. Ви можете отримати попередження, навіть, якщо це був ваш приятель. Воно буде зняте, якщо приятель підтвердить, що не ображений на вас.\n🔨 Якщо на прохання учасника або адміністратора, ви не зміните темп спілкування і не вибачитесь, то отримаєте попередження. \n⚒ Якщо ваша поведінка спричинить те, що учасник залишив чат, покарання може бути жорсткішим.", "6⃣) Заборонені образи, погрози, булінг, приниження, тролінг учасників, членів їхніх сімей, друзів та іншого кола, що є наближеними до учасника чату. Повідомлення на кшталт: \"йди на ... \" - також є образами. Ви можете отримати попередження, навіть, якщо це був ваш приятель. Воно буде зняте, якщо приятель підтвердить, що не ображений на вас.\n🔨 Якщо на прохання учасника або адміністратора, ви не зміните темп спілкування і не вибачитесь, то отримаєте попередження. \n⚒ Якщо ваша поведінка спричинить те, що учасник залишив чат, покарання може бути жорсткішим.",
"7⃣) Заборонено провокування конфліктів та розпалювання ненависті між учасниками чату.", "7⃣) Правила щодо провокаційних тем у чаті:\n7.1 - У чаті немає заборони на обговорення будь-яких тем, однак, заборонені радикальні висловлювання у бік будь-якої із позицій. Якщо ви на 100% впевнені у своїй правоті і розумієте, що будуть люди, які не поділяють вашу точку зору і ви абсолютно точно не збираєтесь прислухатися до їхньої позиції, а налаштовуєтеся на заперечення будь-якого аргументу - то закрийте чат і займіться своїми справами.\n7.2 - Відносьтесь до позиції співрозмовника із повагою. Пам'ятайте, що всі ми різні і погляди у нас, аналогічно, різні.\n7.3 - Якщо ви розумієте, що обстановка в обговоренні загострюється, то запропонуйте співрозмовнику зупинитися і розійдіться, залишившись кожен при своїй думці та намагайтеся більше на проблемну тему не спілкуватися.\n7.4 - Вкидання спірної інформації, яка може призвести до конфлікту, навіть без прямої участі в ньому, може розцінюватися як провокація.\n7.5 - Навмисні образи когось чи чогось, кому чи чому може симпатизувати хтось із учасників чату, після прохання так більше не робити, розцінюватиметься як провокація.\nПриклад: образи кого-небудь із Голо-дівчат, знаючи про те, що у чаті є ті, кому вона може бути цікава і що людині не сподобаються подібні повідомлення.\n7.6 Адміністрація, на свій розсуд, може попросити згорнути тему, якщо розуміє, що вона може призвести до конфлікту.\n❗Кожна ситуація може розглядатися адміністрацією окремо. Ми не хочемо, щоб учасники пересварилися один з одним і вирішили залишити чат. Дані правила прописані насамперед для збереження чистоти та дружньої атмосфери. Якщо ви здатні спілкуватися на заборонені теми шляхом адекватного обміну думками та інформацією, ми можемо заплющити очі на порушення. Але це залежить лише від вашого вміння стримувати свій запал і здібностей викладати думки.",
"8⃣) Заборонені прояви расизму, сексизму, гомофобії та засудження за політичні та (або) релігійні упередження. Дані теми все ще можуть бути частиною діалогу, якщо не несуть у собі прямих засуджень, образ тощо.", "8⃣) Заборонені прояви расизму, сексизму, гомофобії та засудження за політичні та (або) релігійні упередження. \n❗Дані теми все ще можуть бути обговорювані в чаті \"з нейтральної точки зору\", якщо ви при цьому не порушуєте розділ правил 7.",
"9⃣) Заборонені аватарки, нікнейми, ролі, які порушують інші правила." "9⃣) Заборонені аватарки, нікнейми, ролі, які порушують інші правила."
], ],
"rules_additional": "Додаткові правила, які несуть рекомендаційний характер, та не мають явних покарань за порушення:\n1⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2⃣) У чаті немає заборони на російський контент. Але, майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4⃣) Поважайте авторські права контентмейкерів. Якщо ви знаходите арт, анімацію, музику тощо, на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом `/search` на повідомлення із артом.", "rules_additional": "Додаткові правила, які несуть рекомендаційний характер та не мають явних покарань за порушення:\n1⃣) У чаті немає заборони на російську мову. Ми поважаємо кожного українця і не бажаємо розпалювати мовні конфлікти.\n2⃣) У чаті немає заборони на російський контент. Але майте на увазі, що учасники, здебільшого, не будуть зацікавлені у тому, щоб обговорювати його і він може бути проігнорованим.\n3⃣) Не зловживайте матами. Намагайтесь спілкуватись чистою мовою.\n4⃣) Поважайте авторські права контент-мейкерів. Якщо ви знаходите арт, анімацію, музику тощо на офіційних ресурсах (pixiv, twitter, deviantart тощо), відправляйте на нього посилання.\nЯкщо хтось із учасників відправив арт із не офіційного ресурсу і ви бажаєте дізнатись його автора, відправте у відповідь повідомлення із текстом /search на повідомлення із артом.\n5⃣) В особливо критичних ситуаціях порушник може отримати бан або бути повністю видаленим із чату, без попереджень.\n6⃣) Якщо з кимось із учасників у вас трапиться якесь непорозуміння і вам неприємно буде перебувати в чаті один з одним (навіть, якщо конфлікт стався з кимось із адміністраторів) - напишіть мені в особисті повідомлення @Chirkopol. Я, як засновник чату та головний адміністратор, найбільше зацікавлений у збереженні цілісності чату та розвитку нашого ком'юніті, і я зроблю все, що в моїх силах, щоб допомогти вирішити вашу ситуацію.",
"commands": { "commands": {
"application": "Переглянути анкету користувача", "application": "Переглянути анкету користувача",
"applications": "Отримати всі анкети як JSON", "applications": "Отримати всі анкети як JSON",
"cancel": "Відмінити актуальну дію", "cancel": "Відмінити актуальну дію",
"identify": "Дізнатись дані про користувача за айді", "identify": "Дізнатись дані про користувача за айді",
"issue": "Задачі для покращення бота",
"label": "Встановити нікнейм користувачу", "label": "Встановити нікнейм користувачу",
"message": "Надіслати користувачу повідомлення", "message": "Надіслати користувачу повідомлення",
"nearby": "Показати користувачів поблизу", "nearby": "Показати користувачів поблизу",

27
modules/callbacks/ban.py Normal file
View File

@@ -0,0 +1,27 @@
from app import app, isAnAdmin
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from pyrogram import filters
from pyrogram.client import Client
from modules.utils import locale
from modules.database import col_bans
from modules.logging import logWrite
@app.on_callback_query(filters.regex("ban_[\s\S]*"))
async def callback_query_reject(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
if not await isAnAdmin(int(fullclb[1])):
col_bans.insert_one( {"user": int(fullclb[1])} )
edited_markup = [[InlineKeyboardButton(text=str(locale("banned", "button")), callback_data="nothing")]]
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
await clb.answer(text=locale("sub_banned", "callback", locale=clb.from_user))
logWrite(f"User {fullclb[1]} has been banned by {clb.from_user.id}")
try:
await app.send_message(int(fullclb[1]), locale("you_are_banned", "message"))
except Exception as exp:
logWrite(f"Could send ban message to {fullclb[1]} due to {exp}")

View File

@@ -75,7 +75,7 @@ async def callback_query_reapply_reject(app: Client, clb: CallbackQuery):
col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}})
edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")], [InlineKeyboardButton(text=str(locale("ban", "button")), callback_data=f"ban_{fullclb[2]}")]]
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
await clb.answer(text=locale("sub_rejected", "callback", locale=clb.from_user).format(fullclb[2]), show_alert=True) await clb.answer(text=locale("sub_rejected", "callback", locale=clb.from_user).format(fullclb[2]), show_alert=True)
@@ -83,13 +83,30 @@ async def callback_query_reapply_reject(app: Client, clb: CallbackQuery):
# Use old application when user reapplies after leaving the chat # Use old application when user reapplies after leaving the chat
@app.on_callback_query(filters.regex("reapply_old_[\s\S]*")) @app.on_callback_query(filters.regex("reapply_old_[\s\S]*"))
async def callback_query_reapply_old(app: Client, clb: CallbackQuery): async def callback_query_reapply_old(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
if HoloUser(clb.from_user).sponsorship_state()[0] == "fill": fullclb = clb.data.split("_")
holo_user = HoloUser(clb.from_user)
if holo_user.sponsorship_state()[0] == "fill":
await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False) await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False)
return return
message = await app.get_messages(clb.from_user.id, int(fullclb[2])) message = await app.get_messages(clb.from_user.id, int(fullclb[2]))
if col_tmp.find_one({"user": holo_user.id, "type": "application"}) is None and col_applications.find_one({"user": holo_user.id}) is not None:
col_tmp.insert_one(
{
"user": holo_user.id,
"type": "application",
"complete": True,
"sent": False,
"state": "fill",
"reapply": True,
"stage": 10,
"application": col_applications.find_one({"user": holo_user.id})["application"]
}
)
await confirm_yes(app, message, kind="application") await confirm_yes(app, message, kind="application")
await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button", locale=clb.from_user), "nothing")]])) await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button", locale=clb.from_user), "nothing")]]))
@@ -99,13 +116,16 @@ async def callback_query_reapply_new(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_") fullclb = clb.data.split("_")
if HoloUser(clb.from_user).sponsorship_state()[0] == "fill": holo_user = HoloUser(clb.from_user)
if holo_user.sponsorship_state()[0] == "fill":
await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False) await clb.message.reply_text(locale("finish_sponsorship", "message"), quote=False)
return return
await clb.answer(locale("reapply_stopped", "callback", locale=clb.from_user)) await clb.answer(locale("reapply_stopped", "callback", locale=clb.from_user))
message = await app.get_messages(clb.from_user.id, int(fullclb[2])) message = await app.get_messages(clb.from_user.id, int(fullclb[2]))
col_tmp.update_one({"user": clb.from_user.id}, {"$set": {"state": "fill", "completed": False, "stage": 1}}) col_tmp.update_one({"user": clb.from_user.id, "type": "application"}, {"$set": {"state": "fill", "completed": False, "stage": 1}})
holo_user.application_restart(reapply=True)
await welcome_pass(app, message, once_again=True) await welcome_pass(app, message, once_again=True)
logWrite(f"User {clb.from_user.id} restarted the application after leaving the chat earlier") logWrite(f"User {clb.from_user.id} restarted the application after leaving the chat earlier")
await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button", locale=clb.from_user), "nothing")]])) await clb.message.edit(clb.message.text, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("done", "button", locale=clb.from_user), "nothing")]]))

View File

@@ -1,10 +0,0 @@
from app import app
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from pyrogram import filters
# Callback rule ================================================================================================================
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
async def callback_query_rule(app: Client, clb: CallbackQuery):
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
# ==============================================================================================================================

View File

@@ -0,0 +1,36 @@
from os import path
from app import app
from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.client import Client
from pyrogram import filters
from modules.database import col_spoilers
from bson.objectid import ObjectId
from modules.utils import configGet, jsonLoad, locale
# Callback sid =================================================================================================================
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
async def callback_query_sid(app: Client, clb: CallbackQuery):
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
# ==============================================================================================================================
# Callback shc =================================================================================================================
@app.on_callback_query(filters.regex("shc_[\s\S]*"))
async def callback_query_shc(app: Client, clb: CallbackQuery):
if (clb.from_user.id not in jsonLoad(path.join(configGet("cache", "locations"), "group_members"))):
await clb.answer(locale("spoiler_forbidden", "callback", locale=clb.from_user), show_alert=True)
return
spoil = col_spoilers.find_one( {"_id": ObjectId(clb.data.split("_")[1])} )
if spoil["description"] == "":
desc = locale("spoiler_empty_named", "message", locale=clb.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), clb.from_user.first_name)
else:
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(configGet("users", "groups"), desc, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=clb.from_user), callback_data=f'sid_{clb.data.split("_")[1]}')]]))
await app.send_message(configGet("admin", "groups"), desc, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=clb.from_user), callback_data=f'sid_{clb.data.split("_")[1]}')]]))
await clb.answer(locale("spoiler_sent", "callback", locale=clb.from_user), show_alert=True)
# ==============================================================================================================================

View File

@@ -63,28 +63,11 @@ async def callback_query_reject(app: Client, clb: CallbackQuery):
col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}}) col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}})
edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]] edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")], [InlineKeyboardButton(text=str(locale("ban", "button")), callback_data=f"ban_{fullclb[2]}")]]
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
await clb.answer(text=locale("sub_rejected", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True) await clb.answer(text=locale("sub_rejected", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
@app.on_callback_query(filters.regex("sub_aggressive_[\s\S]*"))
async def callback_query_reject_aggressive(app: Client, clb: CallbackQuery):
fullclb = clb.data.split("_")
holo_user = HoloUser(int(fullclb[2]))
await app.send_message(configGet("admin", "groups"), locale("rejected_by_agr", "message").format(clb.from_user.first_name, holo_user.id), disable_notification=True)
await app.send_message(holo_user.id, locale("rejected_aggressive", "message", locale=holo_user))
logWrite(f"User {holo_user.id} got rejected by {clb.from_user.id} due to being aggressive")
col_tmp.update_one({"user": {"$eq": holo_user.id}, "type": {"$eq": "application"}}, {"$set": {"state": "rejected", "sent": False}})
edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button")), callback_data="nothing")]]
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
await clb.answer(text=locale("sub_aggressive", "callback", locale=clb.from_user).format(holo_user.id), show_alert=True)
@app.on_callback_query(filters.regex("sub_russian_[\s\S]*")) @app.on_callback_query(filters.regex("sub_russian_[\s\S]*"))
async def callback_query_reject_russian(app: Client, clb: CallbackQuery): async def callback_query_reject_russian(app: Client, clb: CallbackQuery):

View File

@@ -3,14 +3,17 @@ from pyrogram import filters
from pyrogram.types import Message, ReplyKeyboardRemove from pyrogram.types import Message, ReplyKeyboardRemove
from pyrogram.client import Client from pyrogram.client import Client
from modules.utils import should_quote, logWrite, locale from modules.utils import should_quote, logWrite, locale
from modules.database import col_tmp, col_spoilers from modules.database import col_tmp, col_spoilers, col_applications
from modules import custom_filters from modules import custom_filters
# Cancel command =============================================================================================================== # Cancel command ===============================================================================================================
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.command("cancel", prefixes=["/"])) @app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.command("cancel", prefixes=["/"]) & ~custom_filters.banned)
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} )
await msg.reply_text(locale("cancel", "message", locale=msg.from_user), quote=should_quote(msg), reply_markup=ReplyKeyboardRemove()) if col_applications.find_one( {"user": msg.from_user.id} ) is None:
await msg.reply_text(locale("cancel_reapply", "message", locale=msg.from_user), quote=should_quote(msg), reply_markup=ReplyKeyboardRemove())
else:
await msg.reply_text(locale("cancel", "message", locale=msg.from_user), quote=should_quote(msg), 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}")
# ============================================================================================================================== # ==============================================================================================================================

View File

@@ -51,7 +51,7 @@ async def cmd_identify(app: Client, msg: Message):
if user.photo is not None: if user.photo is not None:
await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO) await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO)
await msg.reply_photo( await msg.reply_photo(
create_tmp(await download_tmp(app, user.photo.big_file_id), kind="image"), create_tmp((await download_tmp(app, user.photo.big_file_id))[1], kind="image"),
quote=should_quote(msg), quote=should_quote(msg),
caption=output caption=output
) )

21
modules/commands/issue.py Normal file
View File

@@ -0,0 +1,21 @@
from typing import Union
from app import app
from pyrogram import filters
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User, Message
from pyrogram.client import Client
from modules.utils import configGet, locale
from modules import custom_filters
from classes.holo_user import HoloUser
# Issue command ================================================================================================================
@app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.private & filters.command(["issue"], prefixes=["/"]) & ~custom_filters.banned)
async def cmd_issue(app: Client, msg: Message):
await msg.reply_text(locale("issue", "message", locale=msg.from_user), disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(locale("issue", "button", locale=msg.from_user), url=configGet("issues"))
]
]
))
# ==============================================================================================================================

View File

@@ -18,10 +18,10 @@ async def cmd_message(app: Client, msg: Message):
except (ValueError, UserInvalidError): except (ValueError, UserInvalidError):
destination = HoloUser(await find_user(app, query=msg.command[1])) destination = HoloUser(await find_user(app, query=msg.command[1]))
if ((msg.text is not None) and (len(msg.text.split()) > 2)): if ((msg.text is not None) and (len(str(msg.text).split()) > 2)):
await destination.message(context=msg, text=" ".join(msg.text.split()[2:]), caption=msg.caption, photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, adm_context=True) await destination.message(context=msg, text=" ".join(str(msg.text).split()[2:]), caption=msg.caption, photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, 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(context=msg, text=msg.text, caption=" ".join(msg.caption.split()[2:]), photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, adm_context=True) await destination.message(context=msg, text=str(msg.text), caption=" ".join(msg.caption.split()[2:]), photo=msg.photo, video=msg.video, file=msg.document, voice=msg.voice, animation=msg.animation, adm_context=True)
else: 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) 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)

View File

@@ -1,3 +1,4 @@
from os import path
from traceback import print_exc from traceback import print_exc
from app import app from app import app
from pyrogram import filters from pyrogram import filters
@@ -6,12 +7,12 @@ from pyrogram.client import Client
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules import custom_filters from modules import custom_filters
from modules.logging import logWrite from modules.logging import logWrite
from modules.utils import configGet, locale, should_quote, find_location from modules.utils import configGet, jsonLoad, locale, should_quote, find_location
from modules.database import col_applications, col_users from modules.database import col_applications, col_users
from classes.errors.geo import PlaceNotFoundError from classes.errors.geo import PlaceNotFoundError
# Nearby command =============================================================================================================== # Nearby command ===============================================================================================================
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & (filters.private | (filters.chat(configGet("admin", "groups")) | filters.chat(configGet("users", "groups")))) & filters.command(["nearby"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin)) @app.on_message(custom_filters.enabled_applications & ~filters.scheduled & (filters.private | (filters.chat(configGet("admin", "groups")) | filters.chat(configGet("users", "groups")))) & filters.command(["nearby"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin) & ~custom_filters.banned)
async def cmd_nearby(app: Client, msg: Message): async def cmd_nearby(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user) holo_user = HoloUser(msg.from_user)
@@ -43,10 +44,11 @@ async def cmd_nearby(app: Client, msg: Message):
if not entry["user"] == msg.from_user.id: if not entry["user"] == msg.from_user.id:
user = col_users.find_one( {"user": entry["user"]} ) user = col_users.find_one( {"user": entry["user"]} )
if user is not None: if user is not None:
if user["tg_username"] not in [None, "None", ""]: # Check if user has any name if entry["user"] in jsonLoad(path.join(configGet("cache", "locations"), "group_members")):
output.append(f'• **{user["tg_name"]}** (@{user["tg_username"]}):\n - {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') if user["tg_username"] not in [None, "None", ""]: # Check if user has any name
else: output.append(f'• **{user["tg_name"]}** (@{user["tg_username"]}):\n - {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}')
output.append(f'• **{user["tg_name"]}**:\n - {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}') else:
output.append(f'• **{user["tg_name"]}**:\n - {entry["application"]["3"]["name"]}, {entry["application"]["3"]["adminName1"]}')
logWrite(f"{holo_user.id} tried to find someone nearby {location[1]} {location[0]} in the radius of {configGet('search_radius')} kilometers") logWrite(f"{holo_user.id} tried to find someone nearby {location[1]} {location[0]} in the radius of {configGet('search_radius')} kilometers")

View File

@@ -6,11 +6,11 @@ from classes.holo_user import HoloUser
from modules.logging import logWrite from modules.logging import logWrite
from modules.utils import configGet, locale, should_quote from modules.utils import configGet, locale, should_quote
from modules.handlers.welcome import welcome_pass from modules.handlers.welcome import welcome_pass
from modules.database import col_tmp from modules.database import col_tmp, col_applications
from modules import custom_filters from modules import custom_filters
# Reapply command ============================================================================================================== # Reapply command ==============================================================================================================
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"])) @app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(["reapply"], prefixes=["/"]) & ~custom_filters.banned)
async def cmd_reapply(app: Client, msg: Message): async def cmd_reapply(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user) holo_user = HoloUser(msg.from_user)
@@ -27,16 +27,20 @@ async def cmd_reapply(app: Client, msg: Message):
if member.user.id == msg.from_user.id: if member.user.id == msg.from_user.id:
left_chat = False left_chat = False
if not left_chat: if left_chat is True:
if holo_user.sponsorship_state()[0] == "fill":
await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg))
return
holo_user.application_restart(reapply=True)
await welcome_pass(app, msg, once_again=True)
else:
if holo_user.application_state()[1] is True and holo_user.application_state()[0] != "fill": if (holo_user.application_state()[1] is True and holo_user.application_state()[0] not in ["fill", "rejected"]):
await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([
[
InlineKeyboardButton(locale("reapply_old_one", "button", locale=holo_user), f"reapply_old_{msg.id}")
],
[
InlineKeyboardButton(locale("reapply_new_one", "button", locale=holo_user), f"reapply_new_{msg.id}")
]
]))
elif col_tmp.find_one({"user": holo_user.id, "type": "application"}) is None and col_applications.find_one({"user": holo_user.id}) is not None:
await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([ await msg.reply_text(locale("reapply_left_chat", "message", locale=holo_user), reply_markup=InlineKeyboardMarkup([
[ [
@@ -52,6 +56,15 @@ async def cmd_reapply(app: Client, msg: Message):
holo_user.application_restart(reapply=True) holo_user.application_restart(reapply=True)
await welcome_pass(app, msg, once_again=True) await welcome_pass(app, msg, once_again=True)
else:
if holo_user.sponsorship_state()[0] == "fill":
await msg.reply_text(locale("finish_sponsorship", "message"), quote=should_quote(msg))
return
holo_user.application_restart(reapply=True)
await welcome_pass(app, msg, once_again=True)
else: else:
await msg.reply_text(locale("reapply_in_progress", "message", locale=holo_user).format(locale("confirm", "keyboard", locale=holo_user)[1][0]), reply_markup=InlineKeyboardMarkup([ await msg.reply_text(locale("reapply_in_progress", "message", locale=holo_user).format(locale("confirm", "keyboard", locale=holo_user)[1][0]), reply_markup=InlineKeyboardMarkup([

View File

@@ -27,11 +27,24 @@ async def cmd_resetcommands(app: Client, msg: Message):
logWrite(f'Resetting commands in groups {configGet("admin", "groups")} and {configGet("users", "groups")}', debug=True) logWrite(f'Resetting commands in groups {configGet("admin", "groups")} and {configGet("users", "groups")}', debug=True)
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("admin", "groups"))) await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("admin", "groups")))
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("users", "groups"))) await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("users", "groups")))
for lc in valid_locales:
try:
logWrite(f'Resetting commands in groups {configGet("admin", "groups")} and {configGet("users", "groups")} [{lc}]', debug=True)
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("admin", "groups")), language_code=lc)
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=configGet("users", "groups")), language_code=lc)
except:
pass
for admin in configGet("admins"): for admin in configGet("admins"):
try: try:
logWrite(f'Resetting commands for admin {admin}', debug=True) logWrite(f'Resetting commands for admin {admin}', debug=True)
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=admin)) await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=admin))
for lc in valid_locales:
try:
logWrite(f'Resetting commands for admin {admin} [{lc}]', debug=True)
await app.delete_bot_commands(scope=BotCommandScopeChat(chat_id=admin), language_code=lc)
except:
pass
except bad_request_400.PeerIdInvalid: except bad_request_400.PeerIdInvalid:
pass pass

View File

@@ -36,7 +36,7 @@ class DefaultRulesMarkup(list):
# Rules command ============================================================================================================= # Rules command =============================================================================================================
@app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.private & filters.command(["rules"], prefixes=["/"])) @app.on_message(custom_filters.enabled_general & ~filters.scheduled & filters.private & ~custom_filters.banned & filters.command(["rules"], prefixes=["/"]))
async def cmd_rules(app: Client, msg: Message): async def cmd_rules(app: Client, msg: Message):
await msg.reply_text(locale("rules_msg", locale=msg.from_user), disable_web_page_preview=True, reply_markup=DefaultRulesMarkup(msg.from_user).keyboard) await msg.reply_text(locale("rules_msg", locale=msg.from_user), disable_web_page_preview=True, reply_markup=DefaultRulesMarkup(msg.from_user).keyboard)
# ============================================================================================================================== # ==============================================================================================================================

View File

@@ -6,11 +6,11 @@ from classes.errors.holo_user import UserNotFoundError, UserInvalidError
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.logging import logWrite from modules.logging import logWrite
from modules.utils import locale from modules.utils import locale
from modules.database import col_spoilers from modules.database import col_spoilers, col_applications
from modules import custom_filters from modules import custom_filters
# Spoiler command ============================================================================================================== # Spoiler command ==============================================================================================================
@app.on_message(custom_filters.member & ~filters.scheduled & filters.private & filters.command(["spoiler"], prefixes=["/"])) @app.on_message(custom_filters.enabled_spoilers & ~filters.scheduled & filters.private & ~custom_filters.banned & filters.command(["spoiler"], prefixes=["/"]))
async def cmd_spoiler(app: Client, msg: Message): async def cmd_spoiler(app: Client, msg: Message):
try: try:
@@ -18,13 +18,18 @@ async def cmd_spoiler(app: Client, msg: Message):
except (UserInvalidError, UserNotFoundError): except (UserInvalidError, UserNotFoundError):
return return
if col_applications.find_one( {"user": holo_user.id} ) is None:
await msg.reply_text(locale("not_member", "message", locale=msg.from_user))
return
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill": if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
if col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} ) is None: if col_spoilers.find_one( {"user": holo_user.id, "completed": False} ) is None:
col_spoilers.insert_one( col_spoilers.insert_one(
{ {
"user": msg.from_user.id, "user": holo_user.id,
"completed": False, "completed": False,
"category": None, "category": None,
"description": None, "description": None,

View File

@@ -8,7 +8,7 @@ from modules.utils import locale, should_quote
from modules.database import col_applications from modules.database import col_applications
# Sponsorship command ========================================================================================================== # Sponsorship command ==========================================================================================================
@app.on_message(custom_filters.enabled_sponsorships & ~filters.scheduled & filters.command(["sponsorship"], prefixes=["/"]) & (custom_filters.allowed | custom_filters.admin)) @app.on_message(custom_filters.enabled_sponsorships & ~filters.scheduled & filters.command(["sponsorship"], prefixes=["/"]) & ~custom_filters.banned & (custom_filters.allowed | custom_filters.admin))
async def cmd_sponsorship(app: Client, msg: Message): async def cmd_sponsorship(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user) holo_user = HoloUser(msg.from_user)
if holo_user.application_state()[0] == "fill": if holo_user.application_state()[0] == "fill":

View File

@@ -9,7 +9,7 @@ from bson.objectid import ObjectId
from bson.errors import InvalidId from bson.errors import InvalidId
# Start command ================================================================================================================ # Start command ================================================================================================================
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(["start"], prefixes=["/"])) @app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(["start"], prefixes=["/"]) & ~custom_filters.banned)
async def cmd_start(app: Client, msg: Message): async def cmd_start(app: Client, msg: Message):
user = col_users.find_one({"user": msg.from_user.id}) user = col_users.find_one({"user": msg.from_user.id})

View File

@@ -27,12 +27,12 @@ async def cmd_warnings(app: Client, msg: Message):
if len(list_of_users) != 0: if len(list_of_users) != 0:
target = list_of_users[0].user target = list_of_users[0].user
target_name = target.first_name target_name = target.first_name
target_id = str(target.id) target_id = target.id
else: else:
await msg.reply_text(locale("no_user_warnings", "message", locale=msg.from_user).format(msg.command[1])) await msg.reply_text(locale("no_user_warnings", "message", locale=msg.from_user).format(msg.command[1]))
return return
warns = len(list(col_warnings.find({"user": target_id}))) warns = col_warnings.count_documents({"user": target_id})
if warns == 0: if warns == 0:
await msg.reply_text(locale("no_warnings", "message", locale=msg.from_user).format(target_name, target_id), quote=should_quote(msg)) await msg.reply_text(locale("no_warnings", "message", locale=msg.from_user).format(target_name, target_id), quote=should_quote(msg))

View File

@@ -4,10 +4,11 @@ usage in context of Holo Users."""
from os import path from os import path
from app import isAnAdmin from app import isAnAdmin
from modules.utils import configGet, jsonLoad from modules.utils import configGet, jsonLoad
from modules.database import col_applications, col_tmp from modules.database import col_applications, col_tmp, col_bans
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message from pyrogram.types import Message
async def admin_func(_, __, msg: Message): async def admin_func(_, __, msg: Message):
return await isAnAdmin(msg.from_user.id) return await isAnAdmin(msg.from_user.id)
@@ -21,6 +22,11 @@ async def allowed_func(_, __, msg: Message):
output = False output = False
return output return output
async def banned_func(_, __, msg: Message):
return True if col_bans.find_one( {"user": msg.from_user.id} ) is not None else False
async def enabled_general_func(_, __, msg: Message): async def enabled_general_func(_, __, msg: Message):
return configGet("enabled", "features", "general") return configGet("enabled", "features", "general")
@@ -39,6 +45,9 @@ async def enabled_invites_check_func(_, __, msg: Message):
async def enabled_dinovoice_func(_, __, msg: Message): async def enabled_dinovoice_func(_, __, msg: Message):
return configGet("enabled", "features", "dinovoice") return configGet("enabled", "features", "dinovoice")
async def enabled_spoilers_func(_, __, msg: Message):
return configGet("enabled", "features", "spoilers")
async def filling_sponsorship_func(_, __, msg: Message): async def filling_sponsorship_func(_, __, msg: Message):
return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False
@@ -46,11 +55,14 @@ admin = filters.create(admin_func)
member = filters.create(member_func) member = filters.create(member_func)
allowed = filters.create(allowed_func) allowed = filters.create(allowed_func)
banned = filters.create(banned_func)
enabled_general = filters.create(enabled_general_func) enabled_general = filters.create(enabled_general_func)
enabled_applications = filters.create(enabled_applications_func) enabled_applications = filters.create(enabled_applications_func)
enabled_sponsorships = filters.create(enabled_sponsorships_func) enabled_sponsorships = filters.create(enabled_sponsorships_func)
enabled_warnings = filters.create(enabled_warnings_func) enabled_warnings = filters.create(enabled_warnings_func)
enabled_invites_check = filters.create(enabled_invites_check_func) enabled_invites_check = filters.create(enabled_invites_check_func)
enabled_dinovoice = filters.create(enabled_dinovoice_func) enabled_dinovoice = filters.create(enabled_dinovoice_func)
enabled_spoilers = filters.create(enabled_spoilers_func)
filling_sponsorship = filters.create(filling_sponsorship_func) filling_sponsorship = filters.create(filling_sponsorship_func)

View File

@@ -28,13 +28,15 @@ db = db_client.get_database(name=db_config["name"])
collections = db.list_collection_names() collections = db.list_collection_names()
for collection in ["tmp", "users", "context", "spoilers", "messages", "warnings", "applications", "sponsorships"]: for collection in ["tmp", "bans", "users", "context", "youtube", "spoilers", "messages", "warnings", "applications", "sponsorships"]:
if not collection in collections: if not collection in collections:
db.create_collection(collection) db.create_collection(collection)
col_tmp = db.get_collection("tmp") col_tmp = db.get_collection("tmp")
col_bans = db.get_collection("bans")
col_users = db.get_collection("users") col_users = db.get_collection("users")
col_context = db.get_collection("context") col_context = db.get_collection("context")
col_youtube = db.get_collection("youtube")
col_spoilers = db.get_collection("spoilers") col_spoilers = db.get_collection("spoilers")
col_messages = db.get_collection("messages") col_messages = db.get_collection("messages")
col_warnings = db.get_collection("warnings") col_warnings = db.get_collection("warnings")

View File

@@ -16,7 +16,7 @@ from modules import custom_filters
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])
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.private & filters.command(confirmation_1, prefixes=[""])) @app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.private & filters.command(confirmation_1, prefixes=[""]) & ~custom_filters.banned)
async def confirm_yes(app: Client, msg: Message, kind: Literal["application", "sponsorship", "unknown"] = "unknown"): async def confirm_yes(app: Client, msg: Message, kind: Literal["application", "sponsorship", "unknown"] = "unknown"):
holo_user = HoloUser(msg.from_user) holo_user = HoloUser(msg.from_user)
@@ -83,9 +83,6 @@ async def confirm_yes(app: Client, msg: Message, kind: Literal["application", "s
[ [
InlineKeyboardButton(text=str(locale("sub_no", "button")), callback_data=f"sub_no_{holo_user.id}") InlineKeyboardButton(text=str(locale("sub_no", "button")), callback_data=f"sub_no_{holo_user.id}")
], ],
[
InlineKeyboardButton(text=str(locale("sub_aggressive", "button")), callback_data=f"sub_aggressive_{holo_user.id}")
],
[ [
InlineKeyboardButton(text=str(locale("sub_russian", "button")), callback_data=f"sub_russian_{holo_user.id}") InlineKeyboardButton(text=str(locale("sub_russian", "button")), callback_data=f"sub_russian_{holo_user.id}")
] ]
@@ -148,7 +145,7 @@ async def confirm_yes(app: Client, msg: Message, kind: Literal["application", "s
confirmation_2 = [] confirmation_2 = []
for pattern in all_locales("confirm", "keyboard"): for pattern in all_locales("confirm", "keyboard"):
confirmation_2.append(pattern[1][0]) confirmation_2.append(pattern[1][0])
@app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.private & filters.command(confirmation_2, prefixes=[""])) @app.on_message((custom_filters.enabled_applications | custom_filters.enabled_sponsorships) & ~filters.scheduled & filters.private & filters.command(confirmation_2, prefixes=[""]) & ~custom_filters.banned)
async def confirm_no(app: Client, msg: Message, kind: Literal["application", "sponsorship", "unknown"] = "unknown"): async def confirm_no(app: Client, msg: Message, kind: Literal["application", "sponsorship", "unknown"] = "unknown"):
holo_user = HoloUser(msg.from_user) holo_user = HoloUser(msg.from_user)

View File

@@ -10,7 +10,7 @@ from classes.holo_user import HoloUser
from modules import custom_filters from modules import custom_filters
# Contact getting ============================================================================================================== # Contact getting ==============================================================================================================
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.contact & filters.private & (custom_filters.allowed | custom_filters.admin)) @app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.contact & filters.private & (custom_filters.allowed | custom_filters.admin) & ~custom_filters.banned)
async def get_contact(app: Client, msg: Message): async def get_contact(app: Client, msg: Message):
holo_user = HoloUser(msg.from_user) holo_user = HoloUser(msg.from_user)

View File

@@ -3,11 +3,12 @@ from app import app, isAnAdmin
import asyncio import asyncio
from ftfy import fix_text from ftfy import fix_text
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message, ForceReply, InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.types import Message, ForceReply, InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove
from pyrogram.client import Client from pyrogram.client import Client
from classes.holo_user import HoloUser from classes.holo_user import HoloUser
from modules.utils import configGet, logWrite, locale, all_locales from modules.utils import configGet, logWrite, locale, all_locales
from modules.database import col_messages, col_spoilers from modules.database import col_messages, col_spoilers
from modules import custom_filters
async def message_involved(msg: Message) -> bool: async def message_involved(msg: Message) -> bool:
message = col_messages.find_one({"destination.id": msg.reply_to_message.id, "destination.chat": msg.reply_to_message.chat.id}) message = col_messages.find_one({"destination.id": msg.reply_to_message.id, "destination.chat": msg.reply_to_message.chat.id})
@@ -22,7 +23,7 @@ async def message_context(msg: Message) -> tuple:
return 0, 0 return 0, 0
# Any other input ============================================================================================================== # Any other input ==============================================================================================================
@app.on_message(~ filters.scheduled & filters.private) @app.on_message(~ filters.scheduled & (filters.private | filters.chat(configGet("admin", "groups"))) & ~custom_filters.banned)
async def any_stage(app: Client, msg: Message): async def any_stage(app: Client, msg: Message):
if msg.via_bot is None: if msg.via_bot is None:
@@ -36,10 +37,13 @@ async def any_stage(app: Client, msg: Message):
destination_user = HoloUser(context_message.from_user) destination_user = HoloUser(context_message.from_user)
if destination_user is None:
return
await destination_user.message( await destination_user.message(
origin=context_message, origin=context_message,
context=msg, context=msg,
text=msg.text, text=str(msg.text),
caption=msg.caption, caption=msg.caption,
photo=msg.photo, photo=msg.photo,
video=msg.video, video=msg.video,
@@ -52,20 +56,26 @@ async def any_stage(app: Client, msg: Message):
return return
if msg.chat.id == configGet("admin", "groups"):
return
if msg.text is not None: if msg.text is not None:
if configGet("enabled", "features", "applications") is True: if configGet("enabled", "features", "applications") is True:
await holo_user.application_next(msg.text, msg=msg) await holo_user.application_next(str(msg.text), msg=msg)
if configGet("enabled", "features", "sponsorships") is True: if configGet("enabled", "features", "sponsorships") is True:
await holo_user.sponsorship_next(msg.text, msg) await holo_user.sponsorship_next(str(msg.text), msg)
if msg.photo is not None: if msg.photo is not None:
await holo_user.sponsorship_next(msg.text, msg=msg, photo=msg.photo) await holo_user.sponsorship_next(str(msg.text), msg=msg, photo=msg.photo)
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill": if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
if configGet("enabled", "features", "spoilers") is False:
return
spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} ) spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} )
if spoiler is None: if spoiler is None:
@@ -78,7 +88,7 @@ async def any_stage(app: Client, msg: Message):
# Find category in all locales # Find category in all locales
for lc in all_locales("spoiler_categories", "message"): for lc in all_locales("spoiler_categories", "message"):
for key in lc: for key in lc:
if lc[key] == msg.text: if lc[key] == str(msg.text):
found = True found = True
category = key category = key
@@ -98,16 +108,16 @@ async def any_stage(app: Client, msg: Message):
# return # return
if msg.text != "-": if str(msg.text) != "-":
msg.text = fix_text(msg.text) msg.text = fix_text(str(msg.text))
if len(msg.text) > 1024: if len(str(msg.text)) > 1024:
await msg.reply_text(locale("spoiler_description_too_long", "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_too_long", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("spoiler_description", "force_reply", locale=msg.from_user)))
return return
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"description": msg.text}} ) col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"description": msg.text}} )
else: else:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"description": ""}} ) col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"description": ""}} )
logWrite(f"Adding description '{msg.text}' to {msg.from_user.id}'s spoiler") logWrite(f"Adding description '{str(msg.text)}' to {msg.from_user.id}'s spoiler")
await msg.reply_text(locale("spoiler_using_description", "message", locale=msg.from_user).format(msg.text), reply_markup=ForceReply(placeholder=locale("spoiler_content", "force_reply", locale=msg.from_user))) await msg.reply_text(locale("spoiler_using_description", "message", locale=msg.from_user).format(msg.text), reply_markup=ForceReply(placeholder=locale("spoiler_content", "force_reply", locale=msg.from_user)))
return return
@@ -141,12 +151,43 @@ async def any_stage(app: Client, msg: Message):
if spoiler["photo"] is None and spoiler["video"] is None and spoiler["audio"] is None and spoiler["animation"] is None and spoiler["document"] is None and spoiler["text"] is None: if spoiler["photo"] is None and spoiler["video"] is None and spoiler["audio"] is None and spoiler["animation"] is None and spoiler["document"] is None and spoiler["text"] is None:
if msg.text is not None: if msg.text is not None:
col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"text": msg.text, "completed": True}} ) col_spoilers.find_one_and_update( {"user": msg.from_user.id, "completed": False}, {"$set": {"text": str(msg.text), "completed": True}} )
logWrite(f"Adding text '{msg.text}' to {msg.from_user.id}'s spoiler") logWrite(f"Adding text '{str(msg.text)}' to {msg.from_user.id}'s spoiler")
ready = True ready = True
if ready is True: if ready is True:
await msg.reply_text(locale("spoiler_ready", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_send", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")]])) await msg.reply_text(locale("spoiler_ready", "message", locale=msg.from_user), reply_markup=ReplyKeyboardRemove())
if configGet("allow_external", "features", "spoilers") is True:
await msg.reply_text(
locale("spoiler_send", "message", locale=msg.from_user),
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(locale("spoiler_preview", "button", locale=msg.from_user), callback_data=f"sid_{spoiler['_id'].__str__()}")
],
[
InlineKeyboardButton(locale("spoiler_send_chat", "button", locale=msg.from_user), callback_data=f"shc_{spoiler['_id'].__str__()}")
],
[
InlineKeyboardButton(locale("spoiler_send_other", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")
]
]
)
)
else:
await msg.reply_text(
locale("spoiler_send", "message", locale=msg.from_user),
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(locale("spoiler_preview", "button", locale=msg.from_user), callback_data=f"sid_{spoiler['_id'].__str__()}")
],
[
InlineKeyboardButton(locale("spoiler_send_chat", "button", locale=msg.from_user), callback_data=f"shc_{spoiler['_id'].__str__()}")
]
]
)
)
else: else:
await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user)) await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user))
@@ -155,7 +196,7 @@ async def any_stage(app: Client, msg: Message):
async def message_in_group(app: Client, msg: Message): async def message_in_group(app: Client, msg: Message):
if (msg.chat is not None) and (msg.via_bot is not None): if (msg.chat is not None) and (msg.via_bot is not None):
if (msg.via_bot.id == (await app.get_me()).id) and (msg.chat.id == configGet("users", "groups")): if (msg.via_bot.id == (await app.get_me()).id) and (msg.chat.id == configGet("users", "groups")):
if msg.text.startswith(locale("spoiler_described", "message").split()[0]) or msg.text.startswith(locale("spoiler_empty", "message").split()[0]): if str(msg.text).startswith(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:
logWrite("Forwarding spoiler to admin's group") logWrite("Forwarding spoiler to admin's group")

View File

@@ -1,10 +1,13 @@
from datetime import datetime
from app import app, isAnAdmin from app import app, isAnAdmin
from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton, ChatMemberUpdated from pyrogram.types import ChatPermissions, InlineKeyboardMarkup, InlineKeyboardButton, ChatMemberUpdated
from pyrogram.client import Client from pyrogram.client import Client
from modules.utils import configGet, locale from modules.utils import configGet, locale
from modules.logging import logWrite
from classes.holo_user import HoloUser
from modules import custom_filters from modules import custom_filters
from modules.logging import logWrite
from modules.database import col_applications
from classes.holo_user import HoloUser
from dateutil.relativedelta import relativedelta
# Filter users on join ========================================================================================================= # Filter users on join =========================================================================================================
@app.on_chat_member_updated(custom_filters.enabled_invites_check, group=configGet("users", "groups")) @app.on_chat_member_updated(custom_filters.enabled_invites_check, group=configGet("users", "groups"))
@@ -16,11 +19,61 @@ async def filter_join(app: Client, member: ChatMemberUpdated):
holo_user = HoloUser(member.from_user) holo_user = HoloUser(member.from_user)
if (holo_user.link is not None) and (holo_user.link == member.invite_link.invite_link): if (holo_user.link is not None) and (holo_user.link == member.invite_link.invite_link):
logWrite(f"User {holo_user.id} joined destination group with correct link {holo_user.link}") logWrite(f"User {holo_user.id} joined destination group with correct link {holo_user.link}")
application = col_applications.find_one({"user": holo_user.id})
application_content = []
i = 1
for question in application['application']:
if i == 2:
age = relativedelta(datetime.now(), application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application'][question]}")
i += 1
await app.send_message(configGet("users", "groups"), locale("joined_application", "message").format(member.from_user.first_name, member.from_user.username, "\n".join(application_content)))
return return
if await isAnAdmin(member.invite_link.creator.id): if await isAnAdmin(member.invite_link.creator.id):
logWrite(f"User {holo_user.id} joined destination group with link {holo_user.link} of an admin {member.invite_link.creator.id}") logWrite(f"User {holo_user.id} joined destination group with link {holo_user.link} of an admin {member.invite_link.creator.id}")
application = col_applications.find_one({"user": holo_user.id})
if application is None:
return
application_content = []
i = 1
for question in application['application']:
if i == 2:
age = relativedelta(datetime.now(), application['application']['2'])
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['2'].strftime('%d.%m.%Y')} ({age.years} р.)")
elif i == 3:
if application['application']['3']['countryCode'] == "UA":
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']}")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application']['3']['name']} ({application['application']['3']['adminName1']}, {application['application']['3']['countryName']})")
else:
application_content.append(f"{locale(f'question{i}', 'message', 'question_titles')} {application['application'][question]}")
i += 1
await app.send_message(configGet("users", "groups"), locale("joined_application", "message").format(member.from_user.first_name, member.from_user.username, "\n".join(application_content)))
return return
logWrite(f"User {holo_user.id} joined destination group with stolen/unapproved link {holo_user.link}") logWrite(f"User {holo_user.id} joined destination group with stolen/unapproved link {holo_user.link}")

View File

@@ -12,7 +12,7 @@ for pattern in all_locales("welcome", "keyboard"):
welcome_1.append(pattern[0][0]) welcome_1.append(pattern[0][0])
for pattern in all_locales("return", "keyboard"): for pattern in all_locales("return", "keyboard"):
welcome_1.append(pattern[0][0]) welcome_1.append(pattern[0][0])
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(welcome_1, prefixes=[""])) @app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(welcome_1, prefixes=[""]) & ~custom_filters.banned)
async def welcome_pass(app: Client, msg: Message, once_again: bool = False) -> None: async def welcome_pass(app: Client, msg: Message, once_again: bool = False) -> None:
"""Set user's stage to 1 and start a fresh application """Set user's stage to 1 and start a fresh application
@@ -30,13 +30,16 @@ async def welcome_pass(app: Client, msg: Message, once_again: bool = False) -> N
if once_again is False: if once_again is False:
holo_user.application_restart() holo_user.application_restart()
logWrite(f"User {msg.from_user.id} confirmed starting the application") if once_again is True:
logWrite(f"User {msg.from_user.id} confirmed starting the application")
else:
logWrite(f"User {msg.from_user.id} confirmed starting the application once again")
await msg.reply_text(locale("question1", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("question1", "force_reply", locale=msg.from_user))) await msg.reply_text(locale("question1", "message", locale=msg.from_user), reply_markup=ForceReply(placeholder=locale("question1", "force_reply", locale=msg.from_user)))
welcome_2 = [] welcome_2 = []
for pattern in all_locales("welcome", "keyboard"): for pattern in all_locales("welcome", "keyboard"):
welcome_2.append(pattern[1][0]) welcome_2.append(pattern[1][0])
@app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(welcome_2, prefixes=[""])) @app.on_message(custom_filters.enabled_applications & ~filters.scheduled & filters.private & filters.command(welcome_2, prefixes=[""]) & ~custom_filters.banned)
async def welcome_reject(app: Client, msg: Message): async def welcome_reject(app: Client, msg: Message):
logWrite(f"User {msg.from_user.id} rejected to start the application") logWrite(f"User {msg.from_user.id} rejected to start the application")

View File

@@ -22,34 +22,36 @@ async def inline_answer(client: Client, inline_query: InlineQuery):
results = [] results = []
if inline_query.query.startswith("spoiler:"): if configGet("allow_external", "features", "spoilers") is True:
try: if inline_query.query.startswith("spoiler:"):
try:
spoil = col_spoilers.find_one( {"_id": ObjectId(inline_query.query.removeprefix("spoiler:"))} ) spoil = col_spoilers.find_one( {"_id": ObjectId(inline_query.query.removeprefix("spoiler:"))} )
if spoil is not None: if spoil is not None:
desc = locale("spoiler_empty", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories")) if spoil["description"] == "" else locale("spoiler_described", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), spoil["description"]) desc = locale("spoiler_empty", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories")) if spoil["description"] == "" else locale("spoiler_described", "message", locale=inline_query.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), spoil["description"])
results = [ results = [
InlineQueryResultArticle( InlineQueryResultArticle(
title=locale("title", "inline", "spoiler", locale=inline_query.from_user), title=locale("title", "inline", "spoiler", locale=inline_query.from_user),
description=locale("description", "inline", "spoiler", locale=inline_query.from_user), description=locale("description", "inline", "spoiler", locale=inline_query.from_user),
input_message_content=InputTextMessageContent(desc, disable_web_page_preview=True), input_message_content=InputTextMessageContent(desc, disable_web_page_preview=True),
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=inline_query.from_user), callback_data=f'sid_{inline_query.query.removeprefix("spoiler:")}')]]) reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=inline_query.from_user), callback_data=f'sid_{inline_query.query.removeprefix("spoiler:")}')]])
) )
] ]
except InvalidId: except InvalidId:
results = [] results = []
await inline_query.answer( await inline_query.answer(
results=results results=results
) )
return return
if inline_query.chat_type in [ChatType.CHANNEL]: if inline_query.chat_type in [ChatType.CHANNEL]:
await inline_query.answer( await inline_query.answer(

View File

@@ -1,7 +1,9 @@
"""Automatically register commands and execute """Automatically register commands and execute
some scheduled tasks is the main idea of this module""" some scheduled tasks is the main idea of this module"""
from asyncio import sleep
from os import listdir, makedirs, path, sep from os import listdir, makedirs, path, sep
from traceback import format_exc
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ujson import dumps from ujson import dumps
@@ -10,9 +12,11 @@ from pyrogram.types import BotCommand, BotCommandScopeChat, BotCommandScopeChatA
from pyrogram.errors import bad_request_400 from pyrogram.errors import bad_request_400
from pyrogram.enums.chat_members_filter import ChatMembersFilter 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, 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 from modules.database import col_applications, col_sponsorships, col_youtube
from xmltodict import parse
from requests import get
scheduler = AsyncIOScheduler() scheduler = AsyncIOScheduler()
@@ -62,6 +66,8 @@ if configGet("enabled", "features", "applications") is True:
for entry in col_applications.find(): for entry in col_applications.find():
if entry["application"]["2"].strftime("%d.%m") == datetime.now().strftime("%d.%m"): if entry["application"]["2"].strftime("%d.%m") == datetime.now().strftime("%d.%m"):
try: try:
if entry["user"] not in jsonLoad(path.join(configGet("cache", "locations"), "group_members")):
continue
tg_user = await app.get_users(entry["user"]) tg_user = await app.get_users(entry["user"])
await app.send_message( configGet("admin", "groups"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), entry["application"]["2"], '%d.%m.%Y').years)) ) # type: ignore await app.send_message( configGet("admin", "groups"), locale("birthday", "message").format(str(tg_user.first_name), str(tg_user.username), str(relativedelta(datetime.now(), entry["application"]["2"], '%d.%m.%Y').years)) ) # type: ignore
logWrite(f"Notified admins about {entry['user']}'s birthday") logWrite(f"Notified admins about {entry['user']}'s birthday")
@@ -78,22 +84,26 @@ if configGet("enabled", "features", "sponsorships") is True:
async def check_sponsors(): async def check_sponsors():
for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()+timedelta(days=2)}}): for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()+timedelta(days=2)}}):
try: try:
if entry["user"] not in jsonLoad(path.join(configGet("cache", "locations"), "group_members")):
continue
tg_user = await app.get_users(entry["user"]) tg_user = await app.get_users(entry["user"])
until_expiry = relativedelta(datetime.now(), entry["sponsorship"]["expires"]).days until_expiry = abs(relativedelta(datetime.now(), entry["sponsorship"]["expires"]).days)+1
await app.send_message( tg_user.id, locale("sponsorships_expires", "message").format(until_expiry) ) # type: ignore await app.send_message( tg_user.id, locale("sponsorships_expires", "message").format(until_expiry) ) # type: ignore
logWrite(f"Notified user that sponsorship expires in {until_expiry} days") logWrite(f"Notified user {entry['user']} that sponsorship expires in {until_expiry} days")
except Exception as exp: except Exception as exp:
logWrite(f"Could not find user {entry['user']} notify about sponsorship expiry due to '{exp}'") logWrite(f"Could not find user {entry['user']} notify about sponsorship expiry due to '{exp}'")
continue continue
for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()}}): for entry in col_sponsorships.find({"sponsorship.expires": {"$lt": datetime.now()-timedelta(days=1)}}):
try: try:
holo_user = HoloUser(entry["user"]) holo_user = HoloUser(entry["user"])
col_sponsorships.find_one_and_delete({"user": holo_user.id})
if entry["user"] not in jsonLoad(path.join(configGet("cache", "locations"), "group_members")):
continue
await app.send_message( entry["user"], locale("sponsorships_expired", "message") ) # type: ignore await app.send_message( entry["user"], locale("sponsorships_expired", "message") ) # type: ignore
await holo_user.label_reset(configGet("users", "groups")) await holo_user.label_reset(configGet("users", "groups"))
col_sponsorships.find_one_and_delete({"user": holo_user.id})
try: try:
tg_user = await app.get_users(entry["user"]) tg_user = await app.get_users(entry["user"])
logWrite(f"Notified user that sponsorship expired") logWrite(f"Notified user {entry['user']} that sponsorship expired")
except Exception as exp: except Exception as exp:
logWrite(f"Could not find user {entry['user']} notify about sponsorship expired due to '{exp}'") logWrite(f"Could not find user {entry['user']} notify about sponsorship expired due to '{exp}'")
except Exception as exp: except Exception as exp:
@@ -233,4 +243,30 @@ async def commands_register():
if configGet("debug") is True: if configGet("debug") is True:
print(commands, flush=True) print(commands, flush=True)
logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}", debug=True) logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}", debug=True)
if configGet("enabled", "scheduler", "channels_monitor"):
@scheduler.scheduled_job(trigger="interval", minutes=configGet("interval", "scheduler", "channels_monitor"))
async def channels_monitor():
for channel in configGet("channels", "scheduler", "channels_monitor"):
if configGet("debug") is True:
logWrite(f'Processing videos of {channel["name"]} ({channel["id"]})', debug=True)
try:
req = get(f'https://www.youtube.com/feeds/videos.xml?channel_id={channel["id"]}')
parsed = parse(req.content)
if "feed" not in parsed:
continue
if "entry" not in parsed["feed"]:
continue
for entry in parsed["feed"]["entry"]:
if "yt:videoId" not in entry:
continue
if col_youtube.find_one( {"channel": channel["id"], "video": entry["yt:videoId"]} ) is None:
col_youtube.insert_one( {"channel": channel["id"], "video": entry["yt:videoId"], "date": datetime.fromisoformat(entry["published"])} )
await app.send_message(configGet("users", "groups"), locale("youtube_video", "message").format(channel["name"], channel["link"], entry["title"], entry["link"]["@href"]), disable_web_page_preview=False)
await sleep(2)
except Exception as exp:
logWrite(f'Could not get last videos of {channel["name"]} ({channel["id"]}) due to {exp}: {format_exc()}')
if configGet("debug") is True:
logWrite("Admin group caching performed", debug=True)

View File

@@ -1,4 +1,4 @@
from typing import Any, Literal, Union from typing import Any, Literal, Tuple, Union
from uuid import uuid1 from uuid import uuid1
from requests import get from requests import get
from pyrogram.enums.chat_type import ChatType from pyrogram.enums.chat_type import ChatType
@@ -212,7 +212,7 @@ def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "
file.write(bytedata) file.write(bytedata)
return path.join("tmp", filename) return path.join("tmp", filename)
async def download_tmp(app: Client, file_id: str) -> bytes: async def download_tmp(app: Client, file_id: str) -> Tuple[str, bytes]:
"""Download file by its ID and return its bytes """Download file by its ID and return its bytes
### Args: ### Args:
@@ -220,14 +220,14 @@ async def download_tmp(app: Client, file_id: str) -> bytes:
* file_id (`str`): File's unique id * file_id (`str`): File's unique id
### Returns: ### Returns:
* `bytes`: Bytes of downloaded file * `Tuple[str, bytes]`: First is a filepath and the second is file's bytes
""" """
filename = str(uuid1()) filename = str(uuid1())
makedirs("tmp", exist_ok=True) makedirs("tmp", exist_ok=True)
await app.download_media(file_id, path.join("tmp", filename)) await app.download_media(file_id, path.join("tmp", filename))
with open(path.join("tmp", filename), "rb") as f: with open(path.join("tmp", filename), "rb") as f:
bytedata = f.read() bytedata = f.read()
return bytedata return path.join("tmp", filename), bytedata
try: try:
from psutil import Process from psutil import Process

View File

@@ -1,11 +1,12 @@
APScheduler==3.9.1.post1 APScheduler==3.10.0
fastapi~=0.88.0 fastapi~=0.88.0
psutil==5.9.4 psutil==5.9.4
pymongo==4.3.3 pymongo==4.3.3
Pyrogram~=2.0.96 Pyrogram~=2.0.100
requests==2.28.1 requests==2.28.2
tgcrypto==1.2.5 tgcrypto==1.2.5
python_dateutil==2.8.2 python_dateutil==2.8.2
starlette~=0.22.0 starlette~=0.22.0
ujson~=5.7.0 ujson~=5.7.0
ftfy~=6.1.1 ftfy~=6.1.1
xmltodict~=0.13.0