From bd040af0cc6fb58bc2b85f6aa40cec04f160f36c Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 3 Apr 2023 15:47:17 +0200 Subject: [PATCH 01/47] Sends messages on warning being revoked (#36) --- locale/uk.json | 2 ++ modules/callbacks/warnings.py | 8 +++++++- modules/scheduled.py | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/locale/uk.json b/locale/uk.json index c47c73d..f201d9c 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -82,6 +82,8 @@ "warnings_entry": "• {0} (`{1}`)\n Попереджень: {2}", "warnings_empty": "Щось тут порожньо...\nЗ іншого боку, це добре!", "warnings_revoke": "**Попередження {0}:**\n\n{1}\n\nБудь ласка, користуйтесь клавіатурою щоб зняти попередження з відповідним номером.", + "warning_revoked": "Попередження від {0} користувачеві `{1}` було скасовано адміном `{2}`", + "warning_revoked_auto": "Попередження від {0} користувачеві `{1}` було автоматично скасовано.", "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", "no_user_warnings": "Не знайдено користувачів за запитом **{0}**", "syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`", diff --git a/modules/callbacks/warnings.py b/modules/callbacks/warnings.py index 975482c..e1aa014 100644 --- a/modules/callbacks/warnings.py +++ b/modules/callbacks/warnings.py @@ -3,7 +3,7 @@ from app import app from pyrogram import filters from pyrogram.types import CallbackQuery from pyrogram.client import Client -from modules.utils import locale +from modules.utils import configGet, locale from modules.database import col_warnings from bson import ObjectId @@ -25,3 +25,9 @@ async def callback_query_warning_revoke(app: Client, clb: CallbackQuery): text=locale("warning_revoked", "callback", locale=clb.from_user).format(), show_alert=True, ) + await app.send_message( + configGet("admin", "groups"), + locale("warning_revoked_auto", "message").format( + warning["user"], warning["date"].strftime("%d.%m.%Y") + ), + ) diff --git a/modules/scheduled.py b/modules/scheduled.py index 2cc2a81..6b07f9f 100644 --- a/modules/scheduled.py +++ b/modules/scheduled.py @@ -224,6 +224,12 @@ if configGet("enabled", "features", "warnings") is True: logWrite( f'Revoked warning {str(warning["_id"])} of user {warning["user"]} because no active warnings for the last 90 days found.' ) + await app.send_message( + configGet("admin", "groups"), + locale("warning_revoked_auto", "message").format( + warning["user"], warning["date"].strftime("%d.%m.%Y") + ), + ) # Register all bot commands From f66f8421c38cec38bd1d3fdfacc0c0d167a09e3c Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 3 Apr 2023 16:03:33 +0200 Subject: [PATCH 02/47] This commit closes #36 --- locale/uk.json | 4 ++-- modules/callbacks/warnings.py | 26 ++++++++++++++++++++++++++ modules/commands/warnings.py | 11 +++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/locale/uk.json b/locale/uk.json index f201d9c..56acec9 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -76,8 +76,8 @@ "application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`", "warned": "Попереджено **{0}** (`{1}`) про порушення правил", "warned_reason": "Попереджено **{0}** (`{1}`)\n\n**Причина:**\n{2}", - "warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження\n\nОбрати та зняти попередження:\n`/warnings {3} revoke`", - "warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень\n\nОбрати та зняти попередження:\n`/warnings {3} revoke`", + "warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження\n\n{3}\n\nОбрати та зняти попередження:\n`/warnings {4} revoke`", + "warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень\n\n{3}\n\nОбрати та зняти попередження:\n`/warnings {4} revoke`", "warnings_all": "**Список попереджень**\n\n{0}\n\nДля перегляду попереджень окремо взятого користувача слід використовувати `/warnings ID/NAME/USERNAME`", "warnings_entry": "• {0} (`{1}`)\n Попереджень: {2}", "warnings_empty": "Щось тут порожньо...\nЗ іншого боку, це добре!", diff --git a/modules/callbacks/warnings.py b/modules/callbacks/warnings.py index e1aa014..93b5cd0 100644 --- a/modules/callbacks/warnings.py +++ b/modules/callbacks/warnings.py @@ -3,6 +3,7 @@ from app import app from pyrogram import filters from pyrogram.types import CallbackQuery from pyrogram.client import Client +from pykeyboard import InlineKeyboard, InlineButton from modules.utils import configGet, locale from modules.database import col_warnings from bson import ObjectId @@ -31,3 +32,28 @@ async def callback_query_warning_revoke(app: Client, clb: CallbackQuery): warning["user"], warning["date"].strftime("%d.%m.%Y") ), ) + target_id = warning["user"] + if col_warnings.count_documents({"user": target_id, "active": True}) == 0: + await clb.edit_message_text( + locale("no_warnings", "message", locale=clb.from_user).format( + target_id, target_id + ) + ) + return + keyboard = InlineKeyboard() + buttons = [] + warnings = [] + for index, warning in enumerate( + list(col_warnings.find({"user": target_id, "active": True})) + ): + warnings.append( + f'{index+1}. {warning["date"].strftime("%d.%m.%Y, %H:%M")}\n Адмін: {warning["admin"]}\n Причина: {warning["reason"]}' + ) + buttons.append(InlineButton(str(index + 1), f'w_rev_{str(warning["_id"])}')) + keyboard.add(*buttons) + await clb.edit_message_text( + locale("warnings_revoke", "message", locale=clb.from_user).format( + target_id, "\n".join(warnings) + ), + ) + await clb.edit_message_reply_markup(reply_markup=keyboard) diff --git a/modules/commands/warnings.py b/modules/commands/warnings.py index f533ccd..54bafa6 100644 --- a/modules/commands/warnings.py +++ b/modules/commands/warnings.py @@ -123,17 +123,24 @@ async def cmd_warnings(app: Client, msg: Message): quote=should_quote(msg), ) else: + warnings = [] + for index, warning in enumerate( + list(col_warnings.find({"user": target_id, "active": True})) + ): + warnings.append( + f'{index+1}. {warning["date"].strftime("%d.%m.%Y, %H:%M")}\n Адмін: {warning["admin"]}\n Причина: {warning["reason"]}' + ) if warns <= 5: await msg.reply_text( locale("warnings_1", "message", locale=msg.from_user).format( - target_name, target_id, warns, target_id + target_name, target_id, warns, "\n".join(warnings), target_id ), quote=should_quote(msg), ) else: await msg.reply_text( locale("warnings_2", "message", locale=msg.from_user).format( - target_name, target_id, warns, target_id + target_name, target_id, warns, "\n".join(warnings), target_id ), quote=should_quote(msg), ) From 79af1bce66f786de621f923c87d37699762e2e66 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 4 Apr 2023 10:37:46 +0200 Subject: [PATCH 03/47] This commit closes #33 --- holochecker.py | 2 +- locale/uk.json | 1 + .../{group_join.py => group_member_update.py} | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) rename modules/handlers/{group_join.py => group_member_update.py} (93%) diff --git a/holochecker.py b/holochecker.py index 61fffcc..e941471 100644 --- a/holochecker.py +++ b/holochecker.py @@ -41,7 +41,7 @@ from modules.callbacks.warnings import * from modules.handlers.confirmation import * from modules.handlers.contact import * -from modules.handlers.group_join import * +from modules.handlers.group_member_update import * from modules.handlers.voice import * from modules.handlers.welcome import * from modules.handlers.everything import * diff --git a/locale/uk.json b/locale/uk.json index 56acec9..ba90baa 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -132,6 +132,7 @@ "not_member": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "issue": "**Допоможіть боту**\nЗнайшли баг або помилку? Маєте файну ідею для нової функції? Повідомте нас, створивши нову задачу на гіті.\n\nЗа можливості, опишіть свій запит максимально детально. Якщо є змога, також додайте скріншоти або додаткову відому інформацію.", "you_are_banned": "⚠️ **Вас було заблоковано**\nТепер не можна відправити анкету або користуватись командами бота.", + "user_left": "Користувач **{0}** залишив чат", "yes": "Так", "no": "Ні", "voice_message": [ diff --git a/modules/handlers/group_join.py b/modules/handlers/group_member_update.py similarity index 93% rename from modules/handlers/group_join.py rename to modules/handlers/group_member_update.py index 62741b3..c90ed63 100644 --- a/modules/handlers/group_join.py +++ b/modules/handlers/group_member_update.py @@ -149,3 +149,16 @@ async def filter_join(app: Client, member: ChatMemberUpdated): can_send_polls=False, ), ) + return + + if member.new_chat_member is None: + await app.send_message( + configGet("users", "groups"), + locale("user_left", "message").format( + member.old_chat_member.user.first_name + ), + ) + logWrite( + f"User {member.old_chat_member.user.first_name} ({member.old_chat_member.user.id}) left the destination group" + ) + return From 2e7d4aa263fbc38172d84baa33bb158a013821f4 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 5 Apr 2023 22:31:07 +0200 Subject: [PATCH 04/47] WIP: chat language recognition --- holochecker.py | 1 + modules/handlers/group_message.py | 45 +++++++++++++++++++++++++++++++ requirements.txt | 3 +++ 3 files changed, 49 insertions(+) create mode 100644 modules/handlers/group_message.py diff --git a/holochecker.py b/holochecker.py index e941471..c6ae8f1 100644 --- a/holochecker.py +++ b/holochecker.py @@ -42,6 +42,7 @@ from modules.callbacks.warnings import * from modules.handlers.confirmation import * from modules.handlers.contact import * from modules.handlers.group_member_update import * +from modules.handlers.group_message import * from modules.handlers.voice import * from modules.handlers.welcome import * from modules.handlers.everything import * diff --git a/modules/handlers/group_message.py b/modules/handlers/group_message.py new file mode 100644 index 0000000..a59f5db --- /dev/null +++ b/modules/handlers/group_message.py @@ -0,0 +1,45 @@ +from datetime import datetime +from app import app +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.client import Client +from modules.logging import logWrite +from modules.utils import configGet, locale +from modules.database import col_warnings +from modules import custom_filters +from polyglot.detect import Detector + + +@app.on_message( + custom_filters.enabled_general + & ~filters.scheduled + & filters.chat(configGet("users", "groups")) +) +async def msg_destination_group(app: Client, msg: Message): + if msg.text is not None: + lang = Detector(msg.text, quiet=True).language + if lang.code == "ru": + logWrite( + f"Message '{msg.text}' from {msg.from_user.first_name} ({msg.from_user.id}) is fucking russian!!! [confidence {lang.confidence}]" + ) + else: + logWrite( + f"Message '{msg.text}' from {msg.from_user.first_name} ({msg.from_user.id}) is written {lang.code} [confidence {lang.confidence}]" + ) + return + elif msg.caption is not None: + lang = Detector(msg.caption, quiet=True).language + if lang.code == "ru": + logWrite( + f"Message '{msg.caption}' from {msg.from_user.first_name} ({msg.from_user.id}) is fucking russian!!! [confidence {lang.confidence}]" + ) + else: + logWrite( + f"Message '{msg.caption}' from {msg.from_user.first_name} ({msg.from_user.id}) is written {lang.code} [confidence {lang.confidence}]" + ) + return + else: + logWrite( + f"Message from {msg.from_user.first_name} ({msg.from_user.id}) has no text in it." + ) + return diff --git a/requirements.txt b/requirements.txt index b10a4c4..593f835 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,9 @@ convopyro==0.5 fastapi~=0.95.0 ftfy~=6.1.1 psutil==5.9.4 +polyglot~=16.7.4 +PyICU==2.10.2 +pycld2==0.41 pymongo==4.3.3 Pyrogram~=2.0.102 python_dateutil==2.8.2 From e8541b51601452cb184e889d4e9374b5928fa9cf Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 6 Apr 2023 16:08:33 +0200 Subject: [PATCH 05/47] WIP: analytics --- holochecker.py | 2 +- modules/database.py | 4 + modules/handlers/analytics_group.py | 247 ++++++++++++++++++++++++++++ modules/handlers/group_message.py | 45 ----- 4 files changed, 252 insertions(+), 46 deletions(-) create mode 100644 modules/handlers/analytics_group.py delete mode 100644 modules/handlers/group_message.py diff --git a/holochecker.py b/holochecker.py index c6ae8f1..2d094da 100644 --- a/holochecker.py +++ b/holochecker.py @@ -39,10 +39,10 @@ from modules.callbacks.sub import * from modules.callbacks.sus import * from modules.callbacks.warnings import * +from modules.handlers.analytics_group import * from modules.handlers.confirmation import * from modules.handlers.contact import * from modules.handlers.group_member_update import * -from modules.handlers.group_message import * from modules.handlers.voice import * from modules.handlers.welcome import * from modules.handlers.everything import * diff --git a/modules/database.py b/modules/database.py index 86e59f4..5b26e47 100644 --- a/modules/database.py +++ b/modules/database.py @@ -37,6 +37,8 @@ for collection in [ "warnings", "applications", "sponsorships", + "analytics_group", + "analytics_users" ]: if not collection in collections: db.create_collection(collection) @@ -51,5 +53,7 @@ col_messages = db.get_collection("messages") col_warnings = db.get_collection("warnings") col_applications = db.get_collection("applications") col_sponsorships = db.get_collection("sponsorships") +col_analytics_group = db.get_collection("analytics_group") +col_analytics_users = db.get_collection("analytics_users") col_applications.create_index([("application.3.location", GEOSPHERE)]) diff --git a/modules/handlers/analytics_group.py b/modules/handlers/analytics_group.py new file mode 100644 index 0000000..0c8db51 --- /dev/null +++ b/modules/handlers/analytics_group.py @@ -0,0 +1,247 @@ +from datetime import datetime + +from polyglot.detect import Detector +from pyrogram import filters +from pyrogram.client import Client +from pyrogram.enums import MessageEntityType, PollType +from pyrogram.types import Message + +from app import app +from modules import custom_filters +from modules.database import col_analytics_group +from modules.logging import logWrite +from modules.utils import configGet + + +@app.on_message( + custom_filters.enabled_general + & ~filters.scheduled + & filters.chat(configGet("users", "groups")) +) +async def msg_destination_group(app: Client, msg: Message): + analytics_entry = { + "id": msg.id, + "user": msg.from_user.id, + "date": datetime.now(), + "reply": { + "id": msg.reply_to_message_id, + "top_id": msg.reply_to_top_message_id, + "user": None + if msg.reply_to_message is None + else msg.reply_to_message.from_user.id, + }, + "forward": { + "id": msg.forward_from_message_id, + "chat": None if msg.forward_from_chat is None else msg.forward_from_chat.id, + "user": None if msg.forward_from is None else msg.forward_from.id, + "date": msg.forward_date, + }, + "media_spoilered": msg.has_media_spoiler, + "entities": {"links": [], "mentions": []}, + "text": None, + "language": None, + "language_confidence": None, + "animation": None, + "audio": None, + "contact": None, + "document": None, + "location": None, + "photo": None, + "poll": None, + "sticker": None, + "venue": None, + "video": None, + "videonote": None, + "voice": None, + } + + if msg.text is not None or msg.caption is not None: + text = msg.text if msg.text is not None else msg.caption + analytics_entry["text"] = text + + if msg.entities is not None or msg.caption_entities is not None: + entities = ( + msg.entities if msg.entities is not None else msg.caption_entities + ) + for entity in entities: + if entity.type == MessageEntityType.TEXT_LINK: + analytics_entry["entities"]["links"].append(entity.url) + elif entity.type == MessageEntityType.TEXT_MENTION: + analytics_entry["entities"]["mentions"].append(entity.user.id) + + lang = Detector(text, quiet=True).language + + analytics_entry["language"] = lang.code + analytics_entry["language_confidence"] = lang.confidence + + if lang.code == "ru": + logWrite( + f"Message '{text}' from {msg.from_user.first_name} ({msg.from_user.id}) is fucking russian [confidence {lang.confidence}]" + ) + + if msg.animation is not None: + analytics_entry["animation"] = { + "id": msg.animation.file_id, + "duration": msg.animation.duration, + "height": msg.animation.height, + "width": msg.animation.width, + "file_name": msg.animation.file_name, + "mime_type": msg.animation.mime_type, + } + + if msg.audio is not None: + analytics_entry["audio"] = { + "id": msg.audio.file_id, + "title": msg.audio.title, + "performer": msg.audio.performer, + "duration": msg.audio.duration, + "file_name": msg.audio.file_name, + "file_size": msg.audio.file_size, + "mime_type": msg.audio.mime_type, + } + + if msg.contact is not None: + analytics_entry["contact"] = { + "id": msg.contact.user_id, + "first_name": msg.contact.first_name, + "last_name": msg.contact.last_name, + "phone_number": msg.contact.phone_number, + "vcard": msg.contact.vcard, + } + + if msg.document is not None: + analytics_entry["document"] = { + "id": msg.document.file_id, + "file_name": msg.document.file_name, + "file_size": msg.document.file_size, + "mime_type": msg.document.mime_type, + } + + if msg.location is not None: + analytics_entry["location"] = { + "longitude": msg.location.longitude, + "latitude": msg.location.latitude, + } + + if msg.photo is not None: + thumbnails = [] + for thumbail in msg.photo.thumbs: + thumbnails.append( + { + "id": thumbail.file_id, + "height": thumbail.height, + "width": thumbail.width, + "file_size": thumbail.file_size, + } + ) + analytics_entry["photo"] = { + "id": msg.photo.file_id, + "height": msg.photo.height, + "width": msg.photo.width, + "file_size": msg.photo.file_size, + "thumbnails": thumbnails, + } + + if msg.poll is not None: + options = [] + for option in msg.poll.options: + options.append(option.text) + analytics_entry["poll"] = { + "id": msg.poll.id, + "question": msg.poll.question, + "open_period": msg.poll.open_period, + "close_date": msg.poll.close_date, + "options": options, + "correct_option": msg.poll.correct_option_id, + "explanation": msg.poll.explanation, + "anonymous": msg.poll.is_anonymous, + "multiple_answers": msg.poll.allows_multiple_answers, + "quiz": True if msg.poll.type == PollType.QUIZ else False, + } + + if msg.sticker is not None: + thumbnails = [] + for thumbail in msg.sticker.thumbs: + thumbnails.append( + { + "id": thumbail.file_id, + "height": thumbail.height, + "width": thumbail.width, + "file_size": thumbail.file_size, + } + ) + analytics_entry["sticker"] = { + "id": msg.sticker.file_id, + "emoji": msg.sticker.emoji, + "set_name": msg.sticker.set_name, + "animated": msg.sticker.is_animated, + "video": msg.sticker.is_video, + "height": msg.sticker.height, + "width": msg.sticker.width, + "file_name": msg.sticker.file_name, + "file_size": msg.sticker.file_size, + "mime_type": msg.sticker.mime_type, + "thumbnails": thumbnails, + } + + if msg.venue is not None: + analytics_entry["venue"] = { + "title": msg.venue.title, + "address": msg.venue.address, + "longitude": msg.venue.location.longitude, + "latitude": msg.venue.location.latitude, + "foursquare_id": msg.venue.foursquare_id, + "foursquare_type": msg.venue.foursquare_type, + } + + if msg.video is not None: + thumbnails = [] + for thumbail in msg.video.thumbs: + thumbnails.append( + { + "id": thumbail.file_id, + "height": thumbail.height, + "width": thumbail.width, + "file_size": thumbail.file_size, + } + ) + analytics_entry["video"] = { + "id": msg.video.file_id, + "duration": msg.video.duration, + "height": msg.video.height, + "width": msg.video.width, + "file_name": msg.video.file_name, + "file_size": msg.video.file_size, + "mime_type": msg.video.mime_type, + "thumbnails": thumbnails, + } + + if msg.video_note is not None: + thumbnails = [] + for thumbail in msg.video_note.thumbs: + thumbnails.append( + { + "id": thumbail.file_id, + "height": thumbail.height, + "width": thumbail.width, + "file_size": thumbail.file_size, + } + ) + analytics_entry["video_note"] = { + "id": msg.video_note.file_id, + "duration": msg.video_note.duration, + "length": msg.video_note.length, + "file_size": msg.video_note.file_size, + "mime_type": msg.video_note.mime_type, + "thumbnails": thumbnails, + } + + if msg.voice is not None: + analytics_entry["voice"] = { + "id": msg.voice.file_id, + "duration": msg.voice.duration, + "file_size": msg.voice.file_size, + "mime_type": msg.voice.mime_type, + } + + col_analytics_group.insert_one(analytics_entry) diff --git a/modules/handlers/group_message.py b/modules/handlers/group_message.py deleted file mode 100644 index a59f5db..0000000 --- a/modules/handlers/group_message.py +++ /dev/null @@ -1,45 +0,0 @@ -from datetime import datetime -from app import app -from pyrogram import filters -from pyrogram.types import Message -from pyrogram.client import Client -from modules.logging import logWrite -from modules.utils import configGet, locale -from modules.database import col_warnings -from modules import custom_filters -from polyglot.detect import Detector - - -@app.on_message( - custom_filters.enabled_general - & ~filters.scheduled - & filters.chat(configGet("users", "groups")) -) -async def msg_destination_group(app: Client, msg: Message): - if msg.text is not None: - lang = Detector(msg.text, quiet=True).language - if lang.code == "ru": - logWrite( - f"Message '{msg.text}' from {msg.from_user.first_name} ({msg.from_user.id}) is fucking russian!!! [confidence {lang.confidence}]" - ) - else: - logWrite( - f"Message '{msg.text}' from {msg.from_user.first_name} ({msg.from_user.id}) is written {lang.code} [confidence {lang.confidence}]" - ) - return - elif msg.caption is not None: - lang = Detector(msg.caption, quiet=True).language - if lang.code == "ru": - logWrite( - f"Message '{msg.caption}' from {msg.from_user.first_name} ({msg.from_user.id}) is fucking russian!!! [confidence {lang.confidence}]" - ) - else: - logWrite( - f"Message '{msg.caption}' from {msg.from_user.first_name} ({msg.from_user.id}) is written {lang.code} [confidence {lang.confidence}]" - ) - return - else: - logWrite( - f"Message from {msg.from_user.first_name} ({msg.from_user.id}) has no text in it." - ) - return From 7218d580bbb21a53f3562bca05742dbb31fecf64 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 7 Apr 2023 21:54:09 +0200 Subject: [PATCH 06/47] Bump Pyrogram to 2.0.103 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 593f835..3d0e16c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ polyglot~=16.7.4 PyICU==2.10.2 pycld2==0.41 pymongo==4.3.3 -Pyrogram~=2.0.102 +Pyrogram~=2.0.103 python_dateutil==2.8.2 pykeyboard==0.1.5 requests==2.28.2 From 177d456e792bc1e26c01b01333c259490d4a13ad Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 7 Apr 2023 22:01:00 +0200 Subject: [PATCH 07/47] Improved entities handling --- modules/handlers/analytics_group.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/handlers/analytics_group.py b/modules/handlers/analytics_group.py index 0c8db51..daae591 100644 --- a/modules/handlers/analytics_group.py +++ b/modules/handlers/analytics_group.py @@ -66,8 +66,16 @@ async def msg_destination_group(app: Client, msg: Message): for entity in entities: if entity.type == MessageEntityType.TEXT_LINK: analytics_entry["entities"]["links"].append(entity.url) + elif entity.type == MessageEntityType.URL: + analytics_entry["entities"]["links"].append( + text[entity.offset : entity.offset + entity.length] + ) elif entity.type == MessageEntityType.TEXT_MENTION: analytics_entry["entities"]["mentions"].append(entity.user.id) + elif entity.type == MessageEntityType.MENTION: + analytics_entry["entities"]["mentions"].append( + text[entity.offset : entity.offset + entity.length] + ) lang = Detector(text, quiet=True).language From 0bffe9cf970965d35b19a10b348943aa3f643d28 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 8 Apr 2023 01:17:27 +0200 Subject: [PATCH 08/47] Just a space removed --- modules/scheduled.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/scheduled.py b/modules/scheduled.py index 6b07f9f..244ab1b 100644 --- a/modules/scheduled.py +++ b/modules/scheduled.py @@ -173,7 +173,7 @@ if configGet("enabled", "features", "sponsorships") is True: try: tg_user = await app.get_users(entry["user"]) logWrite( - f"Notified user {entry['user']} that sponsorship expired" + f"Notified user {entry['user']} that sponsorship expired" ) except Exception as exp: logWrite( From aa8e77811d60ef25cdb1c4a7f25b2c970dc5c042 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 8 Apr 2023 01:17:49 +0200 Subject: [PATCH 09/47] Slightly improved context --- modules/commands/message.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/commands/message.py b/modules/commands/message.py index d1ba84c..586fa6e 100644 --- a/modules/commands/message.py +++ b/modules/commands/message.py @@ -63,8 +63,12 @@ async def cmd_message(app: Client, msg: Message): await msg.reply_text( locale("message_enter", "message", locale=msg.from_user) ) - message = await listen_message(app, msg.chat.id, timeout=None) - if message.text is not None and message.text == "/cancel": + message = await listen_message(app, msg.chat.id) + if ( + message is None + or message.text is not None + and message.text == "/cancel" + ): return sent = await app.forward_messages( configGet("admin", "groups"), msg.chat.id, message.id From 3fded13f18e580da9c9ef56aab20c99847f47076 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 8 Apr 2023 01:18:05 +0200 Subject: [PATCH 10/47] WIP: sponsorship renewal --- modules/commands/sponsorship.py | 201 +++++++++++++++++++++++++++++--- 1 file changed, 184 insertions(+), 17 deletions(-) diff --git a/modules/commands/sponsorship.py b/modules/commands/sponsorship.py index 3b3dd67..76b0ad1 100644 --- a/modules/commands/sponsorship.py +++ b/modules/commands/sponsorship.py @@ -1,11 +1,31 @@ +from datetime import datetime, timedelta +from typing import Union from app import app from pyrogram import filters -from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message +from pyrogram.types import ( + InlineKeyboardMarkup, + InlineKeyboardButton, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, + ForceReply, + Message, +) from pyrogram.client import Client from classes.holo_user import HoloUser from modules import custom_filters from modules.utils import locale, should_quote -from modules.database import col_applications +from modules.database import col_sponsorships +from convopyro import listen_message + + +def is_none_or_cancel(message: Union[Message, None]) -> bool: + if ( + message is None + or message.text is not None + and message.text.lower() == "/cancel" + ): + return True + return False @app.on_message( @@ -26,21 +46,168 @@ async def cmd_sponsorship(app: Client, msg: Message): if holo_user.spoiler_state() is True: await msg.reply_text(locale("spoiler_in_progress", "message", locale=holo_user)) return - await msg.reply_text( - locale("sponsorship_apply", "message", locale=msg.from_user), - reply_markup=InlineKeyboardMarkup( - [ - [ - InlineKeyboardButton( - text=str( - locale("sponsor_apply", "button", locale=msg.from_user) - ), - callback_data=f"sponsor_apply_{msg.from_user.id}", - ) - ] - ] - ), - quote=should_quote(msg), + + existent = col_sponsorships.find_one( + { + "user": msg.from_user.id, + "sponsorship.expires": {"$gt": datetime.now() - timedelta(days=1)}, + } ) + + if existent is None: + await msg.reply_text( + locale("sponsorship_apply", "message", locale=msg.from_user), + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text=str( + locale("sponsor_apply", "button", locale=msg.from_user) + ), + callback_data=f"sponsor_apply_{msg.from_user.id}", + ) + ] + ] + ), + quote=should_quote(msg), + ) + return + + await msg.reply_text( + f'You have an active membership for **{existent["sponsorship"]["streamer"]}**. Wanna resubmit it once more?', + reply_markup=ReplyKeyboardMarkup( + [["Yep, use old data"], ["Nope, refill it once more"]], + resize_keyboard=True, + one_time_keyboard=True, + ), + ) + + answer_decision = await listen_message(app, msg.chat.id) + + if is_none_or_cancel(answer_decision): + return + + input_streamer = existent["sponsorship"]["streamer"] + + if answer_decision.text.lower() == "yep, use old data": + await answer_decision.reply_text( + "Okay, reusing the old data.\n\nUntil when is your sub?\n\nEnter the date as DD.MM.YYYY", + reply_markup=ForceReply(placeholder="Expiry date as DD.MM.YYYY"), + ) + while True: + answer_date = await listen_message(app, msg.chat.id) + + if is_none_or_cancel(answer_date): + return + + try: + input_dt = datetime.strptime(answer_date.text, "%d.%m.%Y") + break + except ValueError: + await answer_date.reply_text( + "Invalid date! Provide as DD.MM.YYYY", + reply_markup=ForceReply(placeholder="Expiry date as DD.MM.YYYY"), + ) + continue + while True: + await answer_date.reply_text( + "Alright. Now provide your proof **as a single screenshot**" + ) + answer_proof = await listen_message(app, msg.chat.id) + + if is_none_or_cancel(answer_proof): + return + + if answer_proof.photo is None: + await answer_proof.reply_text( + "Please, provide proof **as a single screenshot**" + ) + continue + input_proof = answer_proof.photo.file_id + break + await msg.reply_text( + f'Almost done. Do you want to keep the label **{existent["sponsorship"]["label"]}** or you want to change it?', + reply_markup=ReplyKeyboardMarkup( + [["Keep the old one"], ["Set a new one instead"]], + resize_keyboard=True, + one_time_keyboard=True, + ), + ) + + while True: + answer_label_decision = await listen_message(app, msg.chat.id) + + if is_none_or_cancel(answer_label_decision): + return + + if answer_label_decision.text is None: + await answer_label_decision.reply_text( + "Please, choose a valid option.", + reply_markup=ReplyKeyboardMarkup( + [["Keep the old one"], ["Set a new one instead"]], + resize_keyboard=True, + one_time_keyboard=True, + ), + ) + continue + + if answer_label_decision.text.lower() == "keep the old one": + input_label = existent["sponsorship"]["label"] + elif answer_label_decision.text.lower() == "set a new one instead": + await answer_label_decision.reply_text( + "Okay. Please provide a new label up to 16 characters long", + reply_markup=ForceReply(placeholder="New label"), + ) + while True: + answer_label = await listen_message(app, msg.chat.id) + + if is_none_or_cancel(answer_label_decision): + return + + if answer_label.text is None: + await answer_label.reply_text( + "Please provide valid label", + reply_markup=ForceReply(placeholder="New label"), + ) + continue + elif len(answer_label.text) > 16: + await answer_label.reply_text( + "Please provide a label not longer than 16 characters long", + reply_markup=ForceReply(placeholder="New label"), + ) + continue + + input_label = answer_label.text + break + + await msg.reply_text( + f"So we did it for streamer **{input_streamer}**, til {input_dt.strftime('%d.%m.%Y')}, proofed by `{input_proof}` and labeled as **{input_label}**.", + reply_markup=ReplyKeyboardRemove(), + ) + return + + elif answer_decision.text.lower() == "nope, refill it once more": + await msg.reply_text( + locale("sponsorship_apply", "message", locale=msg.from_user), + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text=str( + locale("sponsor_apply", "button", locale=msg.from_user) + ), + callback_data=f"sponsor_apply_{msg.from_user.id}", + ) + ] + ] + ), + quote=should_quote(msg), + ) + return + else: + await answer_decision.reply_text( + "Invalid option!", reply_markup=ReplyKeyboardRemove() + ) + return # else: # await msg.reply_text(locale("sponsorship_application_empty", "message")) From fdddedb1397ed9e20cf11e43c94bdc8744d9e19c Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 01:22:41 +0200 Subject: [PATCH 11/47] WIP: Event system and easter event --- assets/event/stage_bonus.jpg | Bin 0 -> 61488 bytes holochecker.py | 3 + modules/event.py | 393 +++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 assets/event/stage_bonus.jpg create mode 100644 modules/event.py diff --git a/assets/event/stage_bonus.jpg b/assets/event/stage_bonus.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8c88e0f5834601febeadc5007914b5bd9e633a43 GIT binary patch literal 61488 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⪻N2U4*&zvyD-ZT^o>3sda1m)$`x3aa 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 IX}ehe**u1!lK=n! literal 0 HcmV?d00001 diff --git a/holochecker.py b/holochecker.py index 2d094da..b0c4a55 100644 --- a/holochecker.py +++ b/holochecker.py @@ -29,6 +29,9 @@ from modules.commands.start import * from modules.commands.warn import * from modules.commands.warnings import * +if datetime.now() > datetime(year=2023, month=4, day=14): + from modules.event import * + from modules.callbacks.ban import * from modules.callbacks.nothing import * from modules.callbacks.reapply import * diff --git a/modules/event.py b/modules/event.py new file mode 100644 index 0000000..0c9b009 --- /dev/null +++ b/modules/event.py @@ -0,0 +1,393 @@ +# IF YOU'RE READING THIS DURING THE EVENT AND BEFORE COMPLETING IT - LEAVE NOW! +# ANALYZING THIS CODE WILL RUIN YOUR EXPERIENCE, SO PLEASE COMPLETE THE QUEST +# BEFORE GOING THERE. OTHERWISE THIS WILL BE CONSIDERED AS CHEATING, AND YOUR +# QUEST WILL BE RUINED FOREVER. PLEASE TAKE THIS WARNING SERIOUSLY. + +from datetime import datetime +from os import path +from modules.database import db + +from pyrogram import filters +from pyrogram.client import Client +from pyrogram.types import Message + +from app import app +from modules.utils import configGet + +collections = db.list_collection_names() + +if not "event" in collections: + db.create_collection("event") + +col_event = db.get_collection("event") + + +@app.on_message( + ~filters.scheduled + & filters.command(["aufwiedersehen"], prefixes=["/"]) + & filters.private +) +async def cmd_event_1(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 1}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 1, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №1", + ) + + await msg.reply_text( + """Ви відразу поїхали на місце злочину, найкрупніше відділення мережі ресторанів фаст-фуду "KFP". Як Кіара й розповіла, ніяких слідів пограбування не було. Допитавши кількох працівників, ви не дізналися нічого корисного. Камери спостереження теж не дали ніякої корисної інформації. + +Засмучені тим, що не дізналися нічого, ви вийшли на вулицю подихати свіжим повітрям, вільним від сильного запаху смаженої курятини. На всяк випадок, ви вирішили обійти ресторан декілька разів, але не помітили нічого підозрілого. Тяжко видохнувши, ви присіли на скамійку та озирнулися довкола. + +Ресторан стояв поруч із великим озером, і навколо не було інших будинків. Найближча споруда до нього була невеличка рибацька хатка безпосередньо на березі. Подумав про те, що це краще, ніж нічого, ви підійшли до неї та постукалися. Вам ніхто не відповів. Ви обійшли цю хатку - і нарешті побачили хоч щось цікаве. На стіні, прихованій від поглядів, була маленька панелька, яку недосвідчене око й не помітило б. Ви підійшли, віддвинули її та здивувалися, побачив, що за нею був девайс для введення коду із цифровою клавіатурою. Код був із трьох цифр, і ніяких підказок не було. Цікавості заради, ви почали вводити рандомні комбінації: "001", "666", "420"... Четвертою комбінацією було "999" - і, на диво, вона спрацювала. Девайс заблимав зеленим - і ви побачили, як з-під землі з'явився люк. Ви віддвинули його - і спустилися по драбині вниз, до якогось дивного підземного проходу. + +Пройшовши по ньому буквально двадцять кроків, ви побачили двері, а поруч із ними - ще один девайс для вводу пароля. Цього разу це були англійські букви, і в паролі їх було 6. Тут вже простим перебором не задовольнишся, тому ви почали шукати якісь підказки. На щастя, одну ви знайшли, з іншої сторони дверей була невеличка ніша, в якій лежав червоно-білий прапорець та [дивна фотографія](https://docs.google.com/document/d/1X3hj1mD0cPL6ZKgtFrOxHmiQtwCFnHQrBjsn0_gfR7s/edit?usp=sharing). + +Ви відразу зрозуміли, кому належить ця хатка, та ввели правильний шестилітерний пароль.""", + disable_web_page_preview=True, + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["tonjok"], prefixes=["/"]) & filters.private +) +async def cmd_event_2(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 1}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 2}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 2, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №2", + ) + + await msg.reply_text( + """Ви дуже здивувалися, коли побачили, що таємна кімната виявилася... Бібліотекою. Просто для того, щоб впевнитися, що тут не відбувається нічого кримінального, ви відкрили першу ж книгу, що попалася на очі. + +На обкладинці була намальована сором'язлива Оллі в нижній білизні. Ви почервоніли і прочитали назвуу. "Noise Complaint 3: Shutting Up Your Loud Zombie Neighbor (With French Kisses)". Це виявився юрійний фанфік! Ви швиденько пролистали його, почервоніли ще більше - і вирішили залишити кімнату. Що б тут не відбувалося, навряд чи це стосується викрадених яєць. + +На щастя, коли ви піднялися назад на поверхню, хазяйка хатки ще не повернулася. На всяк випадок, ви залишили записку: "Ми розслідуємо злочин про викрадення пасхальних яєць. Якщо ваші яйця були вкрадені, будь ласка, зверніться до найближчого відділення Оодзора Кейсацу." + +Не знайшовши ніяких нових доказів, ви вирішили повернутися до відділення. Там вас зустріла Субару в дуже поганому настрої. Ви коротко розповіли їй про те, що відбувалося протягом дня, і вона поскаржилася, що її набір яєць на Пасху теж був вкрадений! + +Це вже виглядає як серійні крадіжки! Субару вирішила послати вас до Рейне, дізнатися, чи стала вона жертвою крадіїв теж. Оскільки та не дуже любить незнайомих людей, Субару дала вам таємний пароль, який треба буде сказати Рейне - назву юніта, що складається з Оодзори, Таканаші та Паволії.""" + ) + + +@app.on_message( + ~filters.scheduled + & filters.command(["turducken"], prefixes=["/"]) + & filters.private +) +async def cmd_event_3(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 2}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 3}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 3, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №3", + ) + + await msg.reply_text( + """Прийшовши до будинку Рейне, ви не змогли потрапити всередину, бо ваші дзвінки були проігноровані. Роздивившись ворота поуважніше, ви побачили збоку невеличку табличку із написом "ВСЬОГО ЗА ТРИ ПРОСТИХ КЛІКИ ВИ ТЕЖ МОЖЕТЕ ДОЄДНАТИСЯ, MUDAH SEKALI" і кодовий замок із шести літер.""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["joinda"], prefixes=["/"]) & filters.private +) +async def cmd_event_4(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 3}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 4}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 4, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №4", + ) + + await msg.reply_text( + """Пройшовши через ворота, ви постукали у двері і сказали таємний пароль від Субару. Двері відчинилися, і ви почули голос Рейне. "Заходь у останні двері ліворуч". На дверях чомусь була намальована велика червона цифра 5 та поруч був дуже дивний механізм. Ви не встигли роздивитися цей "замок", як двері почали повільно відчинятися. Зайшовши всередину ви опинилися у Кавуновій Кімнаті™, посеред якої сиділа Рейне. Ви почали задавати питання про яйця, але Кавунова Кімната™ не виходила з вашої голови, наче ви десь її бачили... І тут ви згадали! Така сама кімната була в тому фанфіку! На жаль, ви мали необережність сказати це вголос, і Рейне образилася на вас через те, що ви зайшли в її бібліотеку без дозволу. Із криками "ТА НІХТО НЕ КРАВ МОЇ ЯЙЦЯ!" вона випинує вас надвір, і ви вирішуєте іти до наступної Холоторі. + +Ви вирішили завітати до Мумей. Діставшись її дому, ви довго стояли перед входом та думали, чи варто взагалі заходити. Її дім - це гігантська темна печера, що веде в невідоме. Але ваш обов’язок кличе, тому, дістав свій ліхтарик, ви ризикнули зайти. Блукая тунелями печери, ви постійно натикалися на розвилки. Перші два перехрестя ви пройшли прямо, не завертаючи нікуди. Але на третьому вас чекали підозрілі червоні плями на стінах, і ви вирішили, що краще повернутися та обрати інший маршрут. Ви повертали то вліво, то вправо, то вліво, то вправо, і врешті-решт опинилися в тупику. На стіні висіла табличка з двома буквами: “B” та “A”, а під нею лежали полотно, перо та баночка з тією самою червоною рідиною. На самому ж полотні було 6 пустих клітинок. Чи варто вам спробувати щось написати чи просто розвернутися та піти іншою дорогою? + +__(Введіть команду /next, якщо хочете продовжити розслідування, або введіть зашифровану команду /******, щоб побачити бонусну сцену, яка не впливає на геймплей.)__""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["next"], prefixes=["/"]) & filters.private +) +async def cmd_event_5(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 4}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 5}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 5, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №5", + ) + + await msg.reply_text( + """Ви повернулися на самий початок шляху і пішли в той напрямок, який ще не дослідили. На диво, ця дорога вела прямо. Коли ви йшли, вам по дорозі траплялися дивні камінчики, які ви вирішили збирати. На кінці шляху вас чекали великі двері, наче в бункері. Коло них був девайс для вводу коду з 3 цифр. Ви подивилися ще раз на камінчики, які зібрали та на символи на них: + +一1403 +二2308 +三156 +四2103 +五0412 +六154 +七2203 +八0408 +九149 + +Подумав, ви вирішили ввести потрібні цифри від найменшої до найбільшої - і двері почали відчинятися…""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["238"], prefixes=["/"]) & filters.private +) +async def cmd_event_6(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 5}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 6}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 6, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №6", + ) + + await msg.reply_text( + """Всередині ви побачили гігантську ферму, явно штучно зроблену, та Кроніі з Фауну, які поливали саженці. Вони здивувалися, побачивши вас, але ви швидко показали своє поліцейське посвідчення та пояснили ситуацію. Дівчата сказали, що ніякі яйця в Мумей ніхто не крав і що самі вони нічого не чули. Перед тим, як ви пішли далі, вони з ігривою посмішкою задали вам питання, сказав, що дадуть вам підказку, якщо ви правильно дасте відповідь. + +- Поліцейський-кун, ти ж злякався, коли йшов сюди? Ти думав, що це кров була на стінах? Але ні, ця фарба була зроблена з іншого. А з чого саме? + +Ви подивилися навколо та, побачивши, що більше всього росло на фермі, дали впевнену відповідь.""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["berries"], prefixes=["/"]) & filters.private +) +async def cmd_event_7(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 6}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 7}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 7, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №7", + ) + + await msg.reply_text( + """Кроніі та Фауна, як обіцяли, дали вам підказку. Коли ви сказали, що ви опитуєте всіх Голоторі, вони зрозуміли, що далі ви підете до Луї. Але базу Голоксу просто так не знайти, тому дівчата підказали вам локацію. Ви прийшли на місце і опинилися перед звичайним собі житловим будинком у спальному районі. Ви знайшли вхід до підвалу та ввели код від двері (1111), який вам повідомили дівчата з ГолоРади. “Дивно, якось занадто легко як для секретної бази”, - подумали ви та зайшли всередину. Підвал виглядав абсолютно звичайно, єдине, що виділялося, - це залізні двері без ручки. Коло них на вас чекала чергова кодова панель. На цей раз із буквенною клавіатурою. Ви подивилися на символи на двері, “常夜”, та ввели правильний пароль.""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["repaint"], prefixes=["/"]) & filters.private +) +async def cmd_event_8(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 7}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 8}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 8, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №8", + ) + + await msg.reply_text( + """Тільки-но двері відчинилися, ви побачили перед собою Луї у формі Голоксу та в шикарних окулярах, що нагадували вам окуляри Каміни із Ґуррен Лаґанну, а на плечі в неї сидів її секретар, Ґанмо. Її насторожило, що робітник поліції увірвався на секретну базу Голоксу. Ви поспішили її заспокоїти, та сказали що ви розслідуєте зникнення яєць у Холоторі. Луї відповіла, що її яйця в повному порядку і ніхто їх не крав, а от просто так відпустити вас вона тепер не може, бо ви знаєте, де знаходиться секретний штаб. Після цих слів вона простягнула якусь склянку із рідиною, що виглядала як препарати, які носить із собою Койорі. Після того, як випили вміст тієї склянки, ви знепритомніли, а коли прийшли до тями, то сиділи поруч із поліцейським відділенням. У вас зникли спогади про те, як ви йшли до бази Голоксу, але ви чітко запам'ятали, що яйця у Луї ніхто не крав. Озирнувшись по сторонам, ви побачили, що вас кличе до себе Субару. Зайшовши до неї в кабінет, ви дізналися, що поки ви бігали в пошуках Холоторі, у відділення хтось доставив таємничу коробку із кодовим замком та записку, причеплену до неї. + +У записці був написаний ось такий текст (хоча це більше походило на набір літер): + +__Lq Pdufk ru Dsulo +Wkhvh wklqjv gr derxqg +D fhuwdlq exqqb +Ohdyhv wkhvh rq wkh jurxqg__ + +А на зворотній стороні був намальований символ 三 + +На кодовому замку ж треба ввести код із 9-и літер без пробілів.""" + ) + + +@app.on_message( + ~filters.scheduled + & filters.command(["easteregg"], prefixes=["/"]) + & filters.private +) +async def cmd_event_9(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 8}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 9}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 9, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №9", + ) + + await msg.reply_text( + """Усередині коробки була рація. Субару негайно вихопила її та натиснула на кнопку зв’язку. + +- Прийом-прийом! Я не знаю, хто ти і що тобі потрібно, але поверни яйця! Я їй чесно сама висид… Пофарбувала! + +- AH↓HA↑HA↓HA↑HA↓ + +- ПЕКОРА?! Я так і знала, що це ти! + +- Моя люба Субару, ти так сильно старалася, що я не можу відмовити тобі! Приїжджай, забирай їх назад. Якщо знайдеш, звісно! AH↓HA↑HA↓HA↑HA↓ + +На цьому зв’язок обірвався. Субару почала крутити рацію, шукаючи, чи є щось, що з нею не так. Ви запропонували відкрити відділення для батарейок, і дійсно, там були два папірця. Один із них - це було міні-фото Каели, на якому маркером було написано: “******** doko?” На другому було написано рваним почерком: “07 Jan 22, Mute City, never forget”. Окрім того, ви помітили, що в коробці лежав маленький шматочок якогось білого мінералу. Зібрав до купи всі підказки, ви повідомили шефу, куди вам треба їхати.""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["atlantis"], prefixes=["/"]) & filters.private +) +async def cmd_event_10(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 9}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 10}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 10, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №10", + ) + + await msg.reply_text( + """Ви разом із Субару під’їхали до руїн Атлантиди. Хоча, чи можна це назвати руїнами, коли Ґура просто не добудувала? В будь-якому випадку, на найвищому п’єдесталі стояв кошик, в якому лежали пасхальні яйця Субару, цілі та непошкоджені! Ваша шеф радісно підбігла до нього, але швидко засмутилася, коли побачила серед яєць планшет. Вона увімкнула його - і на заставці екрану було лого Усада Кенсецу. Планшет був повністю пустим, єдиним файлом було відео без назви. Субару увімкнула його - і на екрані з’явився знайомий кролячий силует. + +- AH↓HA↑HA↓HA↑HA↓ Як бачиш, Субару, твої дорогоцінні яйця в повній безпеці! Та й кому вони взагалі потрібні, коли є набагато рідкісніший та особливіший делікатес? + +Камера віддвинулася від Пекори і показала стіл, на якому стояла гігантська паска, прикрашена немов би золотом та дорогоцінними камнями. Камера протримала її в фокусі пару секунд, а потім перевелася назад на Пекору. + +- До вашої уваги, гордість пекарні Короне, Голо-паска! Якщо я не помиляюся, мала бути презентована особисто Яґо на параді в центрі міста цього вечора. Уявляю собі, як йому зараз сумно від того, що вона зникла. Тепер всі точно побачать його некомпетентність та визнають, що лише Пекора варта звання мера Голо-сіті! AH↓HA↑HA↓HA↑HA↓HA↑HA↓HA↑HA↓ + +Субару стиснула кулаки та почала кричати в екран, забувши про те, що це запис, а не прямий ефір. Нарешті, Пекора закінчила сміятися і продовжила: + +- Моя люба Субару, не плач. Я благородний крадій і дам тобі шанс побути сьогодні героєм та врятувати святковий парад! Якщо хочеш знайти мене та відібрати в мене паску, то рекомендую почати з того, щоб знайти брехуна! Але ти можеш __24__ рази поговорити з усіма мешканцями __Голо__-сіті - тобі все одно не вистачить кмітливості! Адіос! + +На цьому відео закінчилося. Ви заспокоїли Субару та звернули її увагу на те, що деякі слова Пекори були сказані іншою інтонацією. Подумав і згадав події сьогоднішнього дня, ви зрозуміли, до кого треба заїхати в першу чергу. + +__(У команду треба вписати лише ім’я дівчинки.)__""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["kiara"], prefixes=["/"]) & filters.private +) +async def cmd_event_11(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 10}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 11}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 11, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №11", + ) + + await msg.reply_text( + """Прийшовши до Кіари, ви почали вимагати від неї пояснень. Вона зізналася, що просто не могла відмовити Пекорі, але вона дасть нам наступну підказку. Після цього Кіара почала нишпорити у шафі і через деякий час дістала звідти скриньку із дивними символами. "Цю коробку мені подарувала Аме після найпершого походу в новий світ Амеверсу. Сюди я й поклала підказки, що мені передала Пекора. Якщо зможеш відкрити - вони твої". На шкатулці були зображені [такі символи](https://docs.google.com/document/d/1w2ARMWpUIkNpmSNWFJVGobIMVh2scgSy2hgyHssGLtA/edit?usp=sharing), під якими був кодовий замок із 8 цифр.""", + disable_web_page_preview=True, + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["11022021"], prefixes=["/"]) & filters.private +) +async def cmd_event_12(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 11}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 12}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 12, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №12", + ) + + await msg.reply_text( + """У скриньці був папірець з географічними координатами. Субару повернулася до відділення, бо їй вже надзвонював на мобільний особисто Яґо, тому далі ви були самі по собі. Ви приїхали на місце і побачили самотній дуб, що ріс посеред парку. У дуплі дуба на вас чекала нова скринька. На її кришці було написано почерком Пекори наступне: + +__6 листопада +Греміла битва. +Червоні ведмеді +Та білі вовки - +Ніхто не переміг. +У той день +Я була другою. +Якою була надія?__ + +Скриньку закривав кодовий замок лише з двох цифер. У вас взагалі не було ідей, але ви не могли підвести Субару, оскільки вона поклалася на вас! Ви сіли під дубом та почали думати, але так і не змогли знайти відповідь. Ви ледве не заплакали від безсилля, коли до вас підійшла Шішіро Ботан, що прогулювалася парком. + +- Що, офіцере, не можете одужати геній Усада Кенсецу? Не переймайтеся, я їй відразу сказала, що це занадто складно. Але Пекора любить чесну боротьбу, тому вона й попросила мене дивитися одним оком за цією локацію та дати підказку, якщо тобі буде важко. У той день моїм лідером була ніхто інша як Субару, а номер мій був 28. Далі розберешся, шукай у записах! Отож, бувай, хай тобі щастить, пой! + +Шішірон залишила вас, а ви панічно почали діставати смартфон та відкривати ютуб. Ви зрозуміли, про що йшла мова, тому легко змогли ввести правильний номер.""" + ) + + +@app.on_message( + ~filters.scheduled & filters.command(["konami"], prefixes=["/"]) & filters.private +) +async def cmd_event_bonus(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 4}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": -1}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": -1, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №-1 (BONUS)", + ) + + await msg.reply_photo( + path.join("assets", "event", "stage_bonus.jpg"), + caption="""Раптом, стіна почала рухатися, відкриваючи вам прохід. Ви повільно зайшли всередину і побачили якусь дівчину із кролячими вухами. Помітивши вас, вона повернулась і, посміхаючись, промовила: + +- Вибач, але твоє яйце знаходиться в іншій печері. Тут тільки я і мій друг - П'ятнична Ніч. + +Відвернувшись від вас, вона продовжила спілкуватися з великоднім яйцем, а ви, не бажаючи їй заважати, спішно покинули печеру і повернулися до найпершої розвилки. + +__(Введіть команду /next для продовження сюжету.)__""", + ) From f1897a74e8a11614690e9c5b781d5a9069b22828 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 12:56:40 +0200 Subject: [PATCH 12/47] WIP: Event [Stages 13/14] --- modules/event.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/modules/event.py b/modules/event.py index 0c9b009..46375e3 100644 --- a/modules/event.py +++ b/modules/event.py @@ -22,6 +22,7 @@ if not "event" in collections: col_event = db.get_collection("event") +# Stage 1 @app.on_message( ~filters.scheduled & filters.command(["aufwiedersehen"], prefixes=["/"]) @@ -51,6 +52,7 @@ async def cmd_event_1(app: Client, msg: Message): ) +# Stage 2 @app.on_message( ~filters.scheduled & filters.command(["tonjok"], prefixes=["/"]) & filters.private ) @@ -80,6 +82,7 @@ async def cmd_event_2(app: Client, msg: Message): ) +# Stage 3 @app.on_message( ~filters.scheduled & filters.command(["turducken"], prefixes=["/"]) @@ -103,6 +106,7 @@ async def cmd_event_3(app: Client, msg: Message): ) +# Stage 4 @app.on_message( ~filters.scheduled & filters.command(["joinda"], prefixes=["/"]) & filters.private ) @@ -128,6 +132,7 @@ __(Введіть команду /next, якщо хочете продовжит ) +# Stage 5 @app.on_message( ~filters.scheduled & filters.command(["next"], prefixes=["/"]) & filters.private ) @@ -161,6 +166,7 @@ async def cmd_event_5(app: Client, msg: Message): ) +# Stage 6 @app.on_message( ~filters.scheduled & filters.command(["238"], prefixes=["/"]) & filters.private ) @@ -186,6 +192,7 @@ async def cmd_event_6(app: Client, msg: Message): ) +# Stage 7 @app.on_message( ~filters.scheduled & filters.command(["berries"], prefixes=["/"]) & filters.private ) @@ -207,6 +214,7 @@ async def cmd_event_7(app: Client, msg: Message): ) +# Stage 8 @app.on_message( ~filters.scheduled & filters.command(["repaint"], prefixes=["/"]) & filters.private ) @@ -239,6 +247,7 @@ Ohdyhv wkhvh rq wkh jurxqg__ ) +# Stage 9 @app.on_message( ~filters.scheduled & filters.command(["easteregg"], prefixes=["/"]) @@ -272,6 +281,7 @@ async def cmd_event_9(app: Client, msg: Message): ) +# Stage 10 @app.on_message( ~filters.scheduled & filters.command(["atlantis"], prefixes=["/"]) & filters.private ) @@ -307,6 +317,7 @@ __(У команду треба вписати лише ім’я дівчинк ) +# Stage 11 @app.on_message( ~filters.scheduled & filters.command(["kiara"], prefixes=["/"]) & filters.private ) @@ -329,6 +340,7 @@ async def cmd_event_11(app: Client, msg: Message): ) +# Stage 12 @app.on_message( ~filters.scheduled & filters.command(["11022021"], prefixes=["/"]) & filters.private ) @@ -365,6 +377,66 @@ __6 листопада ) +# Stage 13 +@app.on_message( + ~filters.scheduled & filters.command(["24"], prefixes=["/"]) & filters.private +) +async def cmd_event_13(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 12}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 13}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 13, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №13", + ) + + await msg.reply_text( + """Відкривши скриньку, ви побачили всередині... Карі з бараниною?.. Спочатку ви нічого не зрозуміли, але незабаром відчули на собі (чи все ж таки на карі?) голодний погляд Ботан, яка все ще знаходилася поруч. Ви вирішили віддати їй цю смачну страву, за що левиця, задоволено посміхаючись, видала вам рацію та маленьку коробочку у формі ССРБ із кодовим замком на 3 цифри. Рація постійно видавала звуки, схожі на якийсь код, а на коробці було викарбувано "A—>Z, Z—>A". Виписавши сигнали рації морзянкою, ви отримали таке повідомлення: + +--.. --. --. ... ...- .... --. --.. .. --. .-.. ..- ... ...- .. --.- .-.. ..-. .. -- ...- -... .... --. --.. .. .. -... -- .-. - ... --. .-.. . ...- .. .--. -... .-. . .... .-.. -. --.. -- -... .-- --.. -... .... ... --.. . ...- -.- --.. .... .... ...- .-- --. ...- --- --- -. ...- --. ... ...- -. .-. -- ..-. --. ...- .-. --. ... --.. -.- -.- ...- -- ...- .-- + +Швиденько розгадавши цю загадку, ви ввели правильний код.""" + ) + + +# Stage 14 +@app.on_message( + ~filters.scheduled & filters.command(["003"], prefixes=["/"]) & filters.private +) +async def cmd_event_14(app: Client, msg: Message): + if col_event.find_one({"user": msg.from_user.id, "stage": 13}) is None: + return + + if col_event.find_one({"user": msg.from_user.id, "stage": 14}) is None: + col_event.insert_one( + {"user": msg.from_user.id, "stage": 14, "date": datetime.now()} + ) + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №14", + ) + + await msg.reply_text( + """Чергові координати привели вас до закинутого автокінотеатру. Ви увійшли на територію та побачили сцену, на якій я показували фільми. Проектор виводив на екран [дивну таблицю](https://docs.google.com/document/d/1_Mf9w52vDG0sQZ-xKn1pg4tdsLHwiq97O-nHxYELUkI/edit?usp=sharing): + +Також на сцені стояв великий сейф із кодовим замком з 16 символів. З одного боку сейфу було написано наступне: + +パッと光って咲いた 花火を見てた +きっとまだ 終わらない夏が +曖昧な心を 解かして繋いだ +この夜が続いて欲しかった + +А з іншого - просто “Thank you doragon!”. Зверху ж був наступний напис: “1D2?...” + +Ви довго думали, але змогли розшифрувати цю загадку, знову скориставшись ютубом!""" + ) + + +# Stage BONUS @app.on_message( ~filters.scheduled & filters.command(["konami"], prefixes=["/"]) & filters.private ) From 9a028d1f793c36d61413e48dcc8e3daf5d4cb710 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 13:22:54 +0200 Subject: [PATCH 13/47] WIP: Event [Fixed bugs and formatting] --- modules/event.py | 218 ++++++++++++++--------------------------------- 1 file changed, 64 insertions(+), 154 deletions(-) diff --git a/modules/event.py b/modules/event.py index 46375e3..f26e686 100644 --- a/modules/event.py +++ b/modules/event.py @@ -9,7 +9,8 @@ from modules.database import db from pyrogram import filters from pyrogram.client import Client -from pyrogram.types import Message +from pyrogram.types import Message, User +from pyrogram.enums import ParseMode from app import app from modules.utils import configGet @@ -22,6 +23,31 @@ if not "event" in collections: col_event = db.get_collection("event") +async def stage_passer( + previous: int, current: int, user: User, requires_previous: bool = True +) -> bool: + if requires_previous: + if col_event.find_one({"user": user.id, "stage": previous}) is None: + return False + + if col_event.find_one({"user": user.id, "stage": current}) is None: + col_event.insert_one( + {"user": user.id, "stage": current, "date": datetime.now()} + ) + if current == -1: + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{user.first_name}** (`{user.id}`) пройшов етап BONUS", + ) + else: + await app.send_message( + configGet("admin", "groups"), + f"Користувач **{user.first_name}** (`{user.id}`) пройшов етап №{current}", + ) + + return True + + # Stage 1 @app.on_message( ~filters.scheduled @@ -29,14 +55,8 @@ col_event = db.get_collection("event") & filters.private ) async def cmd_event_1(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 1}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 1, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №1", - ) + if not await stage_passer(0, 1, msg.from_user, requires_previous=False): + return await msg.reply_text( """Ви відразу поїхали на місце злочину, найкрупніше відділення мережі ресторанів фаст-фуду "KFP". Як Кіара й розповіла, ніяких слідів пограбування не було. Допитавши кількох працівників, ви не дізналися нічого корисного. Камери спостереження теж не дали ніякої корисної інформації. @@ -57,18 +77,9 @@ async def cmd_event_1(app: Client, msg: Message): ~filters.scheduled & filters.command(["tonjok"], prefixes=["/"]) & filters.private ) async def cmd_event_2(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 1}) is None: + if not await stage_passer(1, 2, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 2}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 2, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №2", - ) - await msg.reply_text( """Ви дуже здивувалися, коли побачили, що таємна кімната виявилася... Бібліотекою. Просто для того, щоб впевнитися, що тут не відбувається нічого кримінального, ви відкрили першу ж книгу, що попалася на очі. @@ -89,18 +100,9 @@ async def cmd_event_2(app: Client, msg: Message): & filters.private ) async def cmd_event_3(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 2}) is None: + if not await stage_passer(2, 3, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 3}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 3, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №3", - ) - await msg.reply_text( """Прийшовши до будинку Рейне, ви не змогли потрапити всередину, бо ваші дзвінки були проігноровані. Роздивившись ворота поуважніше, ви побачили збоку невеличку табличку із написом "ВСЬОГО ЗА ТРИ ПРОСТИХ КЛІКИ ВИ ТЕЖ МОЖЕТЕ ДОЄДНАТИСЯ, MUDAH SEKALI" і кодовий замок із шести літер.""" ) @@ -111,24 +113,16 @@ async def cmd_event_3(app: Client, msg: Message): ~filters.scheduled & filters.command(["joinda"], prefixes=["/"]) & filters.private ) async def cmd_event_4(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 3}) is None: + if not await stage_passer(3, 4, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 4}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 4, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №4", - ) - await msg.reply_text( """Пройшовши через ворота, ви постукали у двері і сказали таємний пароль від Субару. Двері відчинилися, і ви почули голос Рейне. "Заходь у останні двері ліворуч". На дверях чомусь була намальована велика червона цифра 5 та поруч був дуже дивний механізм. Ви не встигли роздивитися цей "замок", як двері почали повільно відчинятися. Зайшовши всередину ви опинилися у Кавуновій Кімнаті™, посеред якої сиділа Рейне. Ви почали задавати питання про яйця, але Кавунова Кімната™ не виходила з вашої голови, наче ви десь її бачили... І тут ви згадали! Така сама кімната була в тому фанфіку! На жаль, ви мали необережність сказати це вголос, і Рейне образилася на вас через те, що ви зайшли в її бібліотеку без дозволу. Із криками "ТА НІХТО НЕ КРАВ МОЇ ЯЙЦЯ!" вона випинує вас надвір, і ви вирішуєте іти до наступної Холоторі. Ви вирішили завітати до Мумей. Діставшись її дому, ви довго стояли перед входом та думали, чи варто взагалі заходити. Її дім - це гігантська темна печера, що веде в невідоме. Але ваш обов’язок кличе, тому, дістав свій ліхтарик, ви ризикнули зайти. Блукая тунелями печери, ви постійно натикалися на розвилки. Перші два перехрестя ви пройшли прямо, не завертаючи нікуди. Але на третьому вас чекали підозрілі червоні плями на стінах, і ви вирішили, що краще повернутися та обрати інший маршрут. Ви повертали то вліво, то вправо, то вліво, то вправо, і врешті-решт опинилися в тупику. На стіні висіла табличка з двома буквами: “B” та “A”, а під нею лежали полотно, перо та баночка з тією самою червоною рідиною. На самому ж полотні було 6 пустих клітинок. Чи варто вам спробувати щось написати чи просто розвернутися та піти іншою дорогою? -__(Введіть команду /next, якщо хочете продовжити розслідування, або введіть зашифровану команду /******, щоб побачити бонусну сцену, яка не впливає на геймплей.)__""" +(Введіть команду /next, якщо хочете продовжити розслідування, або введіть зашифровану команду /******, щоб побачити бонусну сцену, яка не впливає на геймплей.)""", + parse_mode=ParseMode.HTML, ) @@ -137,18 +131,9 @@ __(Введіть команду /next, якщо хочете продовжит ~filters.scheduled & filters.command(["next"], prefixes=["/"]) & filters.private ) async def cmd_event_5(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 4}) is None: + if not await stage_passer(4, 5, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 5}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 5, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №5", - ) - await msg.reply_text( """Ви повернулися на самий початок шляху і пішли в той напрямок, який ще не дослідили. На диво, ця дорога вела прямо. Коли ви йшли, вам по дорозі траплялися дивні камінчики, які ви вирішили збирати. На кінці шляху вас чекали великі двері, наче в бункері. Коло них був девайс для вводу коду з 3 цифр. Ви подивилися ще раз на камінчики, які зібрали та на символи на них: @@ -171,18 +156,9 @@ async def cmd_event_5(app: Client, msg: Message): ~filters.scheduled & filters.command(["238"], prefixes=["/"]) & filters.private ) async def cmd_event_6(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 5}) is None: + if not await stage_passer(5, 6, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 6}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 6, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №6", - ) - await msg.reply_text( """Всередині ви побачили гігантську ферму, явно штучно зроблену, та Кроніі з Фауну, які поливали саженці. Вони здивувалися, побачивши вас, але ви швидко показали своє поліцейське посвідчення та пояснили ситуацію. Дівчата сказали, що ніякі яйця в Мумей ніхто не крав і що самі вони нічого не чули. Перед тим, як ви пішли далі, вони з ігривою посмішкою задали вам питання, сказав, що дадуть вам підказку, якщо ви правильно дасте відповідь. @@ -197,18 +173,9 @@ async def cmd_event_6(app: Client, msg: Message): ~filters.scheduled & filters.command(["berries"], prefixes=["/"]) & filters.private ) async def cmd_event_7(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 6}) is None: + if not await stage_passer(6, 7, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 7}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 7, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №7", - ) - await msg.reply_text( """Кроніі та Фауна, як обіцяли, дали вам підказку. Коли ви сказали, що ви опитуєте всіх Голоторі, вони зрозуміли, що далі ви підете до Луї. Але базу Голоксу просто так не знайти, тому дівчата підказали вам локацію. Ви прийшли на місце і опинилися перед звичайним собі житловим будинком у спальному районі. Ви знайшли вхід до підвалу та ввели код від двері (1111), який вам повідомили дівчата з ГолоРади. “Дивно, якось занадто легко як для секретної бази”, - подумали ви та зайшли всередину. Підвал виглядав абсолютно звичайно, єдине, що виділялося, - це залізні двері без ручки. Коло них на вас чекала чергова кодова панель. На цей раз із буквенною клавіатурою. Ви подивилися на символи на двері, “常夜”, та ввели правильний пароль.""" ) @@ -219,18 +186,9 @@ async def cmd_event_7(app: Client, msg: Message): ~filters.scheduled & filters.command(["repaint"], prefixes=["/"]) & filters.private ) async def cmd_event_8(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 7}) is None: + if not await stage_passer(7, 8, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 8}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 8, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №8", - ) - await msg.reply_text( """Тільки-но двері відчинилися, ви побачили перед собою Луї у формі Голоксу та в шикарних окулярах, що нагадували вам окуляри Каміни із Ґуррен Лаґанну, а на плечі в неї сидів її секретар, Ґанмо. Її насторожило, що робітник поліції увірвався на секретну базу Голоксу. Ви поспішили її заспокоїти, та сказали що ви розслідуєте зникнення яєць у Холоторі. Луї відповіла, що її яйця в повному порядку і ніхто їх не крав, а от просто так відпустити вас вона тепер не може, бо ви знаєте, де знаходиться секретний штаб. Після цих слів вона простягнула якусь склянку із рідиною, що виглядала як препарати, які носить із собою Койорі. Після того, як випили вміст тієї склянки, ви знепритомніли, а коли прийшли до тями, то сиділи поруч із поліцейським відділенням. У вас зникли спогади про те, як ви йшли до бази Голоксу, але ви чітко запам'ятали, що яйця у Луї ніхто не крав. Озирнувшись по сторонам, ви побачили, що вас кличе до себе Субару. Зайшовши до неї в кабінет, ви дізналися, що поки ви бігали в пошуках Холоторі, у відділення хтось доставив таємничу коробку із кодовим замком та записку, причеплену до неї. @@ -254,18 +212,9 @@ Ohdyhv wkhvh rq wkh jurxqg__ & filters.private ) async def cmd_event_9(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 8}) is None: + if not await stage_passer(8, 9, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 9}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 9, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №9", - ) - await msg.reply_text( """Усередині коробки була рація. Субару негайно вихопила її та натиснула на кнопку зв’язку. @@ -277,7 +226,8 @@ async def cmd_event_9(app: Client, msg: Message): - Моя люба Субару, ти так сильно старалася, що я не можу відмовити тобі! Приїжджай, забирай їх назад. Якщо знайдеш, звісно! AH↓HA↑HA↓HA↑HA↓ -На цьому зв’язок обірвався. Субару почала крутити рацію, шукаючи, чи є щось, що з нею не так. Ви запропонували відкрити відділення для батарейок, і дійсно, там були два папірця. Один із них - це було міні-фото Каели, на якому маркером було написано: “******** doko?” На другому було написано рваним почерком: “07 Jan 22, Mute City, never forget”. Окрім того, ви помітили, що в коробці лежав маленький шматочок якогось білого мінералу. Зібрав до купи всі підказки, ви повідомили шефу, куди вам треба їхати.""" +На цьому зв’язок обірвався. Субару почала крутити рацію, шукаючи, чи є щось, що з нею не так. Ви запропонували відкрити відділення для батарейок, і дійсно, там були два папірця. Один із них - це було міні-фото Каели, на якому маркером було написано: “******** doko?” На другому було написано рваним почерком: “07 Jan 22, Mute City, never forget”. Окрім того, ви помітили, що в коробці лежав маленький шматочок якогось білого мінералу. Зібрав до купи всі підказки, ви повідомили шефу, куди вам треба їхати.""", + parse_mode=ParseMode.DISABLED, ) @@ -286,18 +236,9 @@ async def cmd_event_9(app: Client, msg: Message): ~filters.scheduled & filters.command(["atlantis"], prefixes=["/"]) & filters.private ) async def cmd_event_10(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 9}) is None: + if not await stage_passer(9, 10, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 10}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 10, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №10", - ) - await msg.reply_text( """Ви разом із Субару під’їхали до руїн Атлантиди. Хоча, чи можна це назвати руїнами, коли Ґура просто не добудувала? В будь-якому випадку, на найвищому п’єдесталі стояв кошик, в якому лежали пасхальні яйця Субару, цілі та непошкоджені! Ваша шеф радісно підбігла до нього, але швидко засмутилася, коли побачила серед яєць планшет. Вона увімкнула його - і на заставці екрану було лого Усада Кенсецу. Планшет був повністю пустим, єдиним файлом було відео без назви. Субару увімкнула його - і на екрані з’явився знайомий кролячий силует. @@ -309,7 +250,7 @@ async def cmd_event_10(app: Client, msg: Message): Субару стиснула кулаки та почала кричати в екран, забувши про те, що це запис, а не прямий ефір. Нарешті, Пекора закінчила сміятися і продовжила: -- Моя люба Субару, не плач. Я благородний крадій і дам тобі шанс побути сьогодні героєм та врятувати святковий парад! Якщо хочеш знайти мене та відібрати в мене паску, то рекомендую почати з того, щоб знайти брехуна! Але ти можеш __24__ рази поговорити з усіма мешканцями __Голо__-сіті - тобі все одно не вистачить кмітливості! Адіос! +- Моя люба Субару, не плач. Я благородний крадій і дам тобі шанс побути сьогодні героєм та врятувати святковий парад! Якщо хочеш знайти мене та відібрати в мене паску, то рекомендую почати з того, щоб знайти брехуна! Але ти можеш __24__ рази __поговорити__ з усіма мешканцями __Голо__-сіті - тобі все одно не вистачить кмітливості! Адіос! На цьому відео закінчилося. Ви заспокоїли Субару та звернули її увагу на те, що деякі слова Пекори були сказані іншою інтонацією. Подумав і згадав події сьогоднішнього дня, ви зрозуміли, до кого треба заїхати в першу чергу. @@ -322,20 +263,11 @@ __(У команду треба вписати лише ім’я дівчинк ~filters.scheduled & filters.command(["kiara"], prefixes=["/"]) & filters.private ) async def cmd_event_11(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 10}) is None: + if not await stage_passer(10, 11, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 11}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 11, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №11", - ) - await msg.reply_text( - """Прийшовши до Кіари, ви почали вимагати від неї пояснень. Вона зізналася, що просто не могла відмовити Пекорі, але вона дасть нам наступну підказку. Після цього Кіара почала нишпорити у шафі і через деякий час дістала звідти скриньку із дивними символами. "Цю коробку мені подарувала Аме після найпершого походу в новий світ Амеверсу. Сюди я й поклала підказки, що мені передала Пекора. Якщо зможеш відкрити - вони твої". На шкатулці були зображені [такі символи](https://docs.google.com/document/d/1w2ARMWpUIkNpmSNWFJVGobIMVh2scgSy2hgyHssGLtA/edit?usp=sharing), під якими був кодовий замок із 8 цифр.""", + """Прийшовши до Кіари, ви почали вимагати від неї пояснень. Вона зізналася, що просто не могла відмовити Пекорі, але вона дасть нам наступну підказку. Після цього Кіара почала нишпорити у шафі і через деякий час дістала звідти скриньку із дивними символами. "Цю коробку мені подарувала __Аме__ після __найпершого походу__ в новий світ Амеверсу. Сюди я й поклала підказки, що мені передала Пекора. Якщо зможеш відкрити - вони твої". На шкатулці були зображені [такі символи](https://docs.google.com/document/d/1w2ARMWpUIkNpmSNWFJVGobIMVh2scgSy2hgyHssGLtA/edit?usp=sharing), під якими був кодовий замок із 8 цифр.""", disable_web_page_preview=True, ) @@ -345,18 +277,9 @@ async def cmd_event_11(app: Client, msg: Message): ~filters.scheduled & filters.command(["11022021"], prefixes=["/"]) & filters.private ) async def cmd_event_12(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 11}) is None: + if not await stage_passer(11, 12, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 12}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 12, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №12", - ) - await msg.reply_text( """У скриньці був папірець з географічними координатами. Субару повернулася до відділення, бо їй вже надзвонював на мобільний особисто Яґо, тому далі ви були самі по собі. Ви приїхали на місце і побачили самотній дуб, що ріс посеред парку. У дуплі дуба на вас чекала нова скринька. На її кришці було написано почерком Пекори наступне: @@ -382,24 +305,16 @@ __6 листопада ~filters.scheduled & filters.command(["24"], prefixes=["/"]) & filters.private ) async def cmd_event_13(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 12}) is None: + if not await stage_passer(12, 13, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 13}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 13, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №13", - ) - await msg.reply_text( """Відкривши скриньку, ви побачили всередині... Карі з бараниною?.. Спочатку ви нічого не зрозуміли, але незабаром відчули на собі (чи все ж таки на карі?) голодний погляд Ботан, яка все ще знаходилася поруч. Ви вирішили віддати їй цю смачну страву, за що левиця, задоволено посміхаючись, видала вам рацію та маленьку коробочку у формі ССРБ із кодовим замком на 3 цифри. Рація постійно видавала звуки, схожі на якийсь код, а на коробці було викарбувано "A—>Z, Z—>A". Виписавши сигнали рації морзянкою, ви отримали таке повідомлення: --.. --. --. ... ...- .... --. --.. .. --. .-.. ..- ... ...- .. --.- .-.. ..-. .. -- ...- -... .... --. --.. .. .. -... -- .-. - ... --. .-.. . ...- .. .--. -... .-. . .... .-.. -. --.. -- -... .-- --.. -... .... ... --.. . ...- -.- --.. .... .... ...- .-- --. ...- --- --- -. ...- --. ... ...- -. .-. -- ..-. --. ...- .-. --. ... --.. -.- -.- ...- -- ...- .-- -Швиденько розгадавши цю загадку, ви ввели правильний код.""" +Швиденько розгадавши цю загадку, ви ввели правильний код.""", + parse_mode=ParseMode.DISABLED, ) @@ -408,18 +323,9 @@ async def cmd_event_13(app: Client, msg: Message): ~filters.scheduled & filters.command(["003"], prefixes=["/"]) & filters.private ) async def cmd_event_14(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 13}) is None: + if not await stage_passer(13, 14, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": 14}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": 14, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №14", - ) - await msg.reply_text( """Чергові координати привели вас до закинутого автокінотеатру. Ви увійшли на територію та побачили сцену, на якій я показували фільми. Проектор виводив на екран [дивну таблицю](https://docs.google.com/document/d/1_Mf9w52vDG0sQZ-xKn1pg4tdsLHwiq97O-nHxYELUkI/edit?usp=sharing): @@ -436,23 +342,27 @@ async def cmd_event_14(app: Client, msg: Message): ) +# Stage 15 +@app.on_message( + ~filters.scheduled + & filters.command(["1D2C3E4H5B6F7G8A"], prefixes=["/"]) + & filters.private +) +async def cmd_event_15(app: Client, msg: Message): + if not await stage_passer(14, 15, msg.from_user): + return + + await msg.reply_text("""PLACEHOLDER""") + + # Stage BONUS @app.on_message( ~filters.scheduled & filters.command(["konami"], prefixes=["/"]) & filters.private ) async def cmd_event_bonus(app: Client, msg: Message): - if col_event.find_one({"user": msg.from_user.id, "stage": 4}) is None: + if not await stage_passer(4, -1, msg.from_user): return - if col_event.find_one({"user": msg.from_user.id, "stage": -1}) is None: - col_event.insert_one( - {"user": msg.from_user.id, "stage": -1, "date": datetime.now()} - ) - await app.send_message( - configGet("admin", "groups"), - f"Користувач **{msg.from_user.first_name}** (`{msg.from_user.id}`) пройшов етап №-1 (BONUS)", - ) - await msg.reply_photo( path.join("assets", "event", "stage_bonus.jpg"), caption="""Раптом, стіна почала рухатися, відкриваючи вам прохід. Ви повільно зайшли всередину і побачили якусь дівчину із кролячими вухами. Помітивши вас, вона повернулась і, посміхаючись, промовила: From d93b0bc07d3ce52e297c071d1c8b93337c8a998f Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 13:24:20 +0200 Subject: [PATCH 14/47] Added event data export --- locale/uk.json | 2 +- modules/commands/export.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/locale/uk.json b/locale/uk.json index ba90baa..e49b96f 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -87,7 +87,7 @@ "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", "no_user_warnings": "Не знайдено користувачів за запитом **{0}**", "syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`", - "syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans`", + "syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans/event`", "message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.", "message_sent": "Повідомлення надіслано", "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", diff --git a/modules/commands/export.py b/modules/commands/export.py index 9037f2d..85b94cf 100644 --- a/modules/commands/export.py +++ b/modules/commands/export.py @@ -33,7 +33,7 @@ async def cmd_export(app: Client, msg: Message): selection = msg.command[1].lower() - if selection not in ["applications", "warnings", "sponsorships", "bans"]: + if selection not in ["applications", "warnings", "sponsorships", "bans", "event"]: await msg.reply_text( locale("syntax_export", "message", locale=msg.from_user), quote=should_quote(msg), @@ -145,6 +145,15 @@ async def cmd_export(app: Client, msg: Message): output_json.append(entry) output_csv.append(entry) + elif selection == "event": + header_csv = ["user", "stage", "date"] + + for entry in list(col_warnings.find()): + del entry["id"] + entry["date"] = entry["date"].isoformat() + output_json.append(entry) + output_csv.append(entry) + # Saving CSV async with aiofiles.open(temp_file + ".csv", mode="w", encoding="utf-8") as file: writer = AsyncDictWriter(file, header_csv, restval="NULL", quoting=QUOTE_ALL) From 0c46f98225e7df26ecb35c424328810bdb3f39e1 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 13:26:11 +0200 Subject: [PATCH 15/47] Fixed usability bugs --- modules/commands/export.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/commands/export.py b/modules/commands/export.py index 85b94cf..f502779 100644 --- a/modules/commands/export.py +++ b/modules/commands/export.py @@ -15,6 +15,7 @@ from modules import custom_filters from modules.database import col_applications, col_sponsorships, col_warnings from modules.logging import logWrite from modules.utils import locale, should_quote +from modules.event import col_event @app.on_message( @@ -148,8 +149,8 @@ async def cmd_export(app: Client, msg: Message): elif selection == "event": header_csv = ["user", "stage", "date"] - for entry in list(col_warnings.find()): - del entry["id"] + for entry in list(col_event.find()): + del entry["_id"] entry["date"] = entry["date"].isoformat() output_json.append(entry) output_csv.append(entry) From 1d14fd014b212c070b3a13e1c2caadde46da5b04 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 13:28:48 +0200 Subject: [PATCH 16/47] WIP: Event [logging] --- modules/event.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/event.py b/modules/event.py index f26e686..3ed0b70 100644 --- a/modules/event.py +++ b/modules/event.py @@ -13,6 +13,7 @@ from pyrogram.types import Message, User from pyrogram.enums import ParseMode from app import app +from modules.logging import logWrite from modules.utils import configGet collections = db.list_collection_names() @@ -31,6 +32,9 @@ async def stage_passer( return False if col_event.find_one({"user": user.id, "stage": current}) is None: + logWrite( + f"User {user.first_name} ({user.id}) has completed event stage {current}" + ) col_event.insert_one( {"user": user.id, "stage": current, "date": datetime.now()} ) From 4961d6ba79daed5fd583c9b727bff7164e60ea63 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 14:05:31 +0200 Subject: [PATCH 17/47] Added support for new avatars API --- modules/inline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/inline.py b/modules/inline.py index 5173851..626fa83 100644 --- a/modules/inline.py +++ b/modules/inline.py @@ -202,7 +202,7 @@ async def inline_answer(client: Client, inline_query: InlineQuery): f'{configGet("cache", "locations")}{sep}avatars{sep}{match.user.photo.big_file_id}' ): print( - f'Downloaded avatar {match.user.photo.big_file_id} of {match.user.id} and uploaded to {configGet("api")}/?avatar_id={match.user.photo.big_file_id}', + f'Downloaded avatar {match.user.photo.big_file_id} of {match.user.id} and uploaded to {configGet("api")}/avatars/{match.user.photo.big_file_id}', flush=True, ) await app.download_media( @@ -230,7 +230,7 @@ async def inline_answer(client: Client, inline_query: InlineQuery): "user", locale=inline_query.from_user, ).format(match.user.first_name, match.user.username), - thumb_url=f'{configGet("api")}/?avatar_id={match.user.photo.big_file_id}', + thumb_url=f'{configGet("api")}/avatars/{match.user.photo.big_file_id}', ) ) except ValueError: From 929083d2e88404ab375618405be10157f60d26d2 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 14 Apr 2023 15:27:48 +0200 Subject: [PATCH 18/47] WIP: Event [stages 15/16] --- modules/event.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/event.py b/modules/event.py index 3ed0b70..d0185cf 100644 --- a/modules/event.py +++ b/modules/event.py @@ -356,7 +356,32 @@ async def cmd_event_15(app: Client, msg: Message): if not await stage_passer(14, 15, msg.from_user): return - await msg.reply_text("""PLACEHOLDER""") + await msg.reply_text( + """У сейфі лежала паперова мапа Голо-сіті, на якій червоним колом була відмічена споруда посеред промзони. Ви негайно вирушили туди. Цією будівлею виявився чи то ангар, чи то склад, що належав УсаКену. Всі вікна до нього були закриті металевими пластинами, а єдиним входом всередину були масивні ангарні двері. Звісно ж, силою їй відчинити не вдалося. Ретельно перевіривши стіни будівлі зовні, ви помітили, що в одному місці цеглини були розшатані. Ви обережно дістали пару цеглин - і ву-аля, перед вами був черговий девайс для вводу коду. Але цього разу це була повноцінна комп’ютерна клавіатура, і ніякого ліміту символів не було. + +Ви почали трошки панікувати, адже ніяких підказок навколо ви не помітили, аж тут вам на телефон подзвонила Субару. Ви швидко взяли трубку: + +- Офіцере, я тільки що отримала якийсь незрозумілий файл особисто від Пекори! Пересилаю його тобі, розберися - і швиденько, бо через годину вже початок параду! + +Ви --[відкрили файл](https://drive.google.com/file/d/1oex8GriQBsimIsUvOGo83IuFp2JBtNJd/view?usp=sharing)--, потупили декілька хвилин, але врешті-решт змогли зрозуміти загадку та ввели правильний пароль!""" + ) + + +# Stage 16 +@app.on_message( + ~filters.scheduled & filters.command(["hinotori"], prefixes=["/"]) & filters.private +) +async def cmd_event_16(app: Client, msg: Message): + if not await stage_passer(15, 16, msg.from_user): + return + + await msg.reply_text( + """Коли ви ввели пароль, двері відчинилися. Всередині панувала кромішня темрява, і, судячи з ехо від ваших кроків, приміщення було повністю пустим. Раптом, над вами загорілися прожектори, і кімната залилася сліпучим білим світлом. Звідкись з-під даху роздався скрип динаміків - і знайомий вам кролячий голос почав говорити: + +- А ще трохи - і було б запізно. Дозвольте привітати вас із тим, що ви змогли розгадати всі мої загадки! Паска, що ви шукаєте, вже доставлена до офісу Субару. Що ж це ви так поспішали, що аж забули зачинити двері? Дякуючи цьому, я змогла знайти те, що так давно шукала! Але не хвилюйтеся, паску, що послужила такою чудовою заманкою, я вам повертаю цілою та непошкодженою. Ще побачимося! AH↓HA↑HA↓HA↑HA↓HA↑HA↓HA↑HA↓ + +Як ви і думали, по поверненню до офіса Субару, ви виявили повний бардак. Всі шкафи були вивернуті, а документи розкидані по всій кімнаті. Очевидно, це свідчило про крадіж. Дехто провів тут багато часу, раз зміг поперевертати кожен куточок. І хоч парад Голо-сіті був врятований, а нинішня справа закрита, вас тепер мучило питання, що ж таке було необхідне Пекорі, що вона вдалася до таких заходів, щоб вас збити з пантелику та заплутати. Ви були впевнені, що дуже скоро ви про неї почуєте знову…""" + ) # Stage BONUS From 09c93489c0e424c33405b5976f402868b68147a0 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sat, 15 Apr 2023 19:32:51 +0200 Subject: [PATCH 19/47] Event finalized --- modules/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/event.py b/modules/event.py index d0185cf..2ddd781 100644 --- a/modules/event.py +++ b/modules/event.py @@ -285,7 +285,7 @@ async def cmd_event_12(app: Client, msg: Message): return await msg.reply_text( - """У скриньці був папірець з географічними координатами. Субару повернулася до відділення, бо їй вже надзвонював на мобільний особисто Яґо, тому далі ви були самі по собі. Ви приїхали на місце і побачили самотній дуб, що ріс посеред парку. У дуплі дуба на вас чекала нова скринька. На її кришці було написано почерком Пекори наступне: + """У скриньці був папірець з географічними координатами. Субару поїхала пояснювати ситуацію до Яґо, тому далі ви були самі по собі. Ви приїхали на місце і побачили самотній дуб, що ріс посеред парку. У дуплі дуба на вас чекала нова скринька. На її кришці було написано почерком Пекори наступне: __6 листопада Греміла битва. From ca607bb4ca6b2399d1e85c4fcb831f2a28a2d512 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sat, 15 Apr 2023 19:34:40 +0200 Subject: [PATCH 20/47] Added more detailed date --- holochecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holochecker.py b/holochecker.py index b0c4a55..e2d6ea0 100644 --- a/holochecker.py +++ b/holochecker.py @@ -29,7 +29,7 @@ from modules.commands.start import * from modules.commands.warn import * from modules.commands.warnings import * -if datetime.now() > datetime(year=2023, month=4, day=14): +if datetime.now() > datetime(year=2023, month=4, day=16, hour=12, minute=0): from modules.event import * from modules.callbacks.ban import * From 253f6145599c4f61356233558e167f02e7ee3320 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sat, 15 Apr 2023 19:36:25 +0200 Subject: [PATCH 21/47] Debug message on event load added --- holochecker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/holochecker.py b/holochecker.py index e2d6ea0..1993555 100644 --- a/holochecker.py +++ b/holochecker.py @@ -30,6 +30,7 @@ from modules.commands.warn import * from modules.commands.warnings import * if datetime.now() > datetime(year=2023, month=4, day=16, hour=12, minute=0): + logWrite("Importing event module...", debug=True) from modules.event import * from modules.callbacks.ban import * From 362c575d31e14ec64aebf31212635932ef316955 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sat, 15 Apr 2023 19:38:52 +0200 Subject: [PATCH 22/47] Removed start scheduler --- holochecker.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/holochecker.py b/holochecker.py index 1993555..595540b 100644 --- a/holochecker.py +++ b/holochecker.py @@ -29,9 +29,7 @@ from modules.commands.start import * from modules.commands.warn import * from modules.commands.warnings import * -if datetime.now() > datetime(year=2023, month=4, day=16, hour=12, minute=0): - logWrite("Importing event module...", debug=True) - from modules.event import * +from modules.event import * from modules.callbacks.ban import * from modules.callbacks.nothing import * From 0430b4d8499721e3e5f4b5648ac29aea390bbb4f Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 16 Apr 2023 13:19:44 +0200 Subject: [PATCH 23/47] Added description for stage 14 --- modules/event.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/event.py b/modules/event.py index 2ddd781..ff46c68 100644 --- a/modules/event.py +++ b/modules/event.py @@ -342,7 +342,10 @@ async def cmd_event_14(app: Client, msg: Message): А з іншого - просто “Thank you doragon!”. Зверху ж був наступний напис: “1D2?...” -Ви довго думали, але змогли розшифрувати цю загадку, знову скориставшись ютубом!""" +Ви довго думали, але змогли розшифрувати цю загадку, знову скориставшись ютубом! + + +__(Відповідь треба вводити цифрами та великими англійськими літерами.)__""" ) From fe1d44d44d1aa2329d04fd75e07ebf40cd626ce8 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 18 Apr 2023 13:08:50 +0200 Subject: [PATCH 24/47] Sorted the keys --- locale/uk.json | 276 ++++++++++++++++++++++++------------------------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/locale/uk.json b/locale/uk.json index e49b96f..8ae15be 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -1,10 +1,62 @@ { "message": { - "start": "Привіт і ласкаво просимо!\n\nМи будуємо українське ком'юніті фанатів Гололайву і раді кожному, хто поділяє наші інтереси або тільки зацікавився цією тематикою та хоче дізнатись більше. Чим ми відрізняеємось від звичайного тематичного чату? Ми намагаємось створювати усі можливі умови, щоб люди знаходили однодумців у своїх містах та збирались разом. Інколи проводимо великі зустрічі на честь Голо-івентів у Києві. Збираємось у Дискорді для сумісних переглядів концертів, музичних топів, ігор тощо. Проводимо різні івенти у чаті. Об'єднуємось, щоб замовляти офіційний мерч із Японії. Підтримуємо україномовних кліперів та будуємо плани по поширенню нашого ком'юніті на майбутнє.\n\nЦей бот створений для прийому заявок на вступ до нашої чат-спільноти. Усі анкети, після підтвердження адміністрацією, можуть дивитися й інші учасники чату у будь-який момент. Тому, будь ласка, віднесіться до її заповнення відповідально.\n\nЯкщо вашу анкету відхилили, то, скоріше за все:\n1) Вона порушує перше правило чату (/rules - ознайомитись перед вступом до чату).\n2) Ви намагаєтесь додати до чату \"додатковий/запасний\" акаунт.\n3) Ви не дуже відповідально віднеслись до її заповнення.\n\nЯкщо із вашою анкетою щось не так, то вам через бота прийде повідомлення від адміністраторів для вирішення питання. Якщо ви зіштовхнулись із якоюсь проблемою або бот не надсилає вам необхідні повідомлення - напишіть мені у приватні повідомлення @Chirkopol.\n\nПісля прийому вашої анкети бот згенерує вам одноразове посилання. Не забудьте перейти по ньому, щоб потрапити до чату.\n\nДля продовження, нас цікавить відповідь на питання:\nЧи хочеш ти доєднатись до українського ком'юніті фанатів Гололайв та чи зобов'язуєшся ти дотримуватися усіх правил?", + "already_sent": "Анкету вже надіслано, просто почекай. Тобі одразу повідомлять, яке рішення буде прийнято.", + "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", + "application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`", + "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення, як тільки її перевірять та приймуть рішення.. До тих пір від тебе більше нічого не потребується :)", + "application_status_accepted": "Прийнята `{0}` від {1}", + "application_status_not_send": "Анкета ще не була відправлена", + "application_status_on_hold": "Анкета все ще на розгляді", + "application_status_rejected": "Відхилена `{0}` від {1}", + "approved_by": "✅ **Анкету схвалено**\nАдмін **{0}** переглянув та схвалив анкету `{1}`.", + "approved_joined": "Вітаємо! Твою анкету переглянули та підтвердили її правильність. Дякуємо за витрачений на заповнення час та гарного дня!", + "approved": "Вітаємо! Твою анкету переглянули та підтвердили твоє право на вступ.\n\nПеред тим, як ти натиснеш на кнопочку під повідомленням, щоб вступити до нашої лампової спільноти, дамо тобі трішечки додаткової інформації.\nПам'ятай, що натискаючи її, ти підтверджуєш, що ознайомився із нашими правилами (/rules) та зобов'язуєшся їх дотримуватись.\n\nПісля того, як потрапиш до чату, не закидуй цього бота далеко.\nПрописавши @holoua_bot у боті, ти відкриєш список із усіх наших анкет. Ти можеш натискати на будь-яку із них та дізнаватись про кожного із нас більше інформації.\nЗавдяки команді /nearby, ти зможеш дізнатись, чи є серед нас однодумці із твого міста.\nЯкщо у тебе є спонсорська підписка на будь-кого із учасниць Гололайву, то ти маєш право отримати унікальну роль у нашому чаті (/sponsorship) та виділятись серед інших учасників. (А ще зможеш отримати декілька додаткових функцій. Але нікому про це не кажи!)", + "birthday": "У користувача **{0}** (@{1}) сьогодні день народження! Виповнилось {2} років", + "cancel_reapply": "Всі поточні операції скасовано.\nЩоб знову заповнити анкету користуйся /reapply", + "cancel": "Всі поточні операції скасовано.", + "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", + "contact_invalid": "Надісланий контакт не має завершеної анкети.", + "contact_not_member": "Надісланий контакт не є користувачем Telegram.", + "contact": "Анкета `{0}`\n\n**Дані анкети:**\n{1}\n\n{2}", + "finish_application": "❌ **Дія неможлива**\nПерш ніж заповнювати форму спонсора, треба завершити заповнення анкети.", + "finish_sponsorship": "❌ **Дія неможлива**\nПерш ніж заповнювати анкету, треба завершити заповнення форми спонсора або перервати його командою /cancel.", "goodbye": "Добре, дякуємо за чесність! Вибачте, але за таких умов ми не будемо тебе додавати до спільноти. Якщо передумаєш та захочеш приєднатись - просто натисни на кнопку.", + "identify_invalid_syntax": "Неправильний синтаксис!\nТреба: `/identify ID/NAME/USERNAME`", + "identify_not_found": "Не знайдено користувачів за запитом **{0}**", + "identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}", + "issue": "**Допоможіть боту**\nЗнайшли баг або помилку? Маєте файну ідею для нової функції? Повідомте нас, створивши нову задачу на гіті.\n\nЗа можливості, опишіть свій запит максимально детально. Якщо є змога, також додайте скріншоти або додаткову відому інформацію.", + "joined_application": "{0} (@{1})\n\n**Дані анкети:**\n{2}", + "joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", + "label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів", + "message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.", + "message_error": "⚠️ **Сталась помилка**\nНе вдалось надіслати ваше повідомлення. Розробника повідомлено про цю помилку.", + "message_from": "Повідомлення від **{0}** (`{1}`):\n\n", + "message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`", + "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", + "message_reply_notice": "\n\n__Для того, щоб адміністрація побачила вашу відповідь, відправте її **реплаєм на це повідомлення**__", + "message_sent": "Повідомлення надіслано", + "message_traceback": "⚠️ **Сталась помилка**\nПомилка повідомлень: `{0}` -> `{1}`\nПомилка: `{2}`\n\nTraceback:\n```\n{3}\n```", + "nearby_empty": "Здається, нікого поблизу немає.", + "nearby_error": "⚠️ **Сталась помилка**\n\nПомилка: `{0}`\n\nTraceback:\n```\n{1}\n```", + "nearby_invalid": "ℹ️ **Місце не знайдено**\nЗа наданим запитом не знайдено місце з координатами. Спробуйте ще раз формулючи запит в стилі \"Чернівці\" або \"Київська область\".", + "nearby_result": "Результати пошуку:\n\n{0}", + "no_user_application": "Не знайдено користувачів за запитом **{0}**", + "no_user_warnings": "Не знайдено користувачів за запитом **{0}**", + "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", + "not_member": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "privacy_notice": "Раді це чути!\n\nДля продовження треба буде заповнити невеличку анкетку. Будь ласка, віднесись до цього серйозно. Ми відповідально ставимось до персональних даних, тому ця анкета не буде передана третім особам, а буде використана лише для проходження до спільноти та подальшої взаємодії в ній.", + "question_too_long": "Текст занадто довгий. Будь ласка, умісти відповідь у 256 символів.", "question1": "Як до тебе можна звертатись?", + "question10_too_long": "Текст занадто довгий. Будь ласка, умісти відповідь у 1024 символи.", + "question10": "Ну і нарешті, розкажи трохи про себе. Про хобі, чим тобі подобається займатись. Одним повідомленням, будь ласка.", + "question2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", + "question2_joke": "Шутнік, ми так і поняли. Але будь ласка, введи реальне значення.", + "question2_underage": "Вибач, але треба досягти віку {0} років, щоб приєднатись до нас. Такі обмеження існують для того, щоб всім у спільноті було цікаво одне з одним.", "question2": "Коли в тебе день народження?\n\nБудь ласка, у форматі ДД.ММ.РРРР", + "question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.", + "question3_found": "Використовую наступний результат:\n• {0} ({1})", + "question3_invalid": "Місто/населений пункт не знайдено. Користуйтесь прикладами нижче щоб вказати де ви проживаєте та спробуйте ще раз:\n\n• Київ\n• Одеська область\n• Макіївка (Луганська область)", + "question3_traceback": "⚠️ **Сталась помилка**\nПомилка отримання геокодингу для `{0}`\nПомилка: `{1}`\n\nTraceback:\n```\n{2}\n```", "question3": "З якого ти міста або де проживаєш зараз?\n\n⚠️ Будь ласка, не вказуйте точних адрес! \"Київ\" або \"Київська Область\" є достатньою конкретизацією.\n\nПриклади:\n• Київ\n• Одеська область\n• Макіївка (Луганська область)", "question4": "Коли вперше довелось дізнатись про Хололайв?", "question5": "Чим тебе зацікавив Хололайв?", @@ -12,127 +64,75 @@ "question7": "Назви контент хоча б п'яти **__--ЯПОНСЬКИХ--__** холодівчат, які тобі подобаються найбільше.", "question8": "Чи дивишся ти стріми дівчат Хололайву?", "question9": "Чиї пісні з Хололайву тобі подобаються найбільше?", - "question10": "Ну і нарешті, розкажи трохи про себе. Про хобі, чим тобі подобається займатись. Одним повідомленням, будь ласка.", - "question_too_long": "Текст занадто довгий. Будь ласка, умісти відповідь у 256 символів.", - "question2_underage": "Вибач, але треба досягти віку {0} років, щоб приєднатись до нас. Такі обмеження існують для того, щоб всім у спільноті було цікаво одне з одним.", - "question2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", - "question2_joke": "Шутнік, ми так і поняли. Але будь ласка, введи реальне значення.", - "question3_invalid": "Місто/населений пункт не знайдено. Користуйтесь прикладами нижче щоб вказати де ви проживаєте та спробуйте ще раз:\n\n• Київ\n• Одеська область\n• Макіївка (Луганська область)", - "question3_found": "Використовую наступний результат:\n• {0} ({1})", - "question3_error": "⚠️ **Сталась помилка**\nНе вдалось отримати географічну мітку. Розробника повідомлено про цю помилку. Будь ласка, спробуйте ще раз.", - "question3_traceback": "⚠️ **Сталась помилка**\nПомилка отримання геокодингу для `{0}`\nПомилка: `{1}`\n\nTraceback:\n```\n{2}\n```", - "question10_too_long": "Текст занадто довгий. Будь ласка, умісти відповідь у 1024 символи.", - "sponsorship_apply": "ℹ️ Оформіть платну підписку на когось з Холо, заповніть форму та отримайте особливу роль в якості винагороди!", - "sponsorship_applying": "ℹ️ Розпочато заповнення форми на отримання бонусів за платну підписку на холодівчат.", - "sponsor1": "На яку саме дівчину платна підписка?", + "read_rules": "Будь ласка, прочитай ці правила перш ніж натискати на кнопку та приєднуватись до чату.", + "reapply_forbidden": "❌ **Дія неможлива**\nТвоя минула анкета ще не була схвалена або відхилена.", + "reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", + "reapply_in_progress": "❌ **Дія неможлива**\nТи прямо зараз вже заповнюєш анкету. Якщо в ній є помилка - просто натисни кнопку нижче щоб почати заповнювати спочатку.", + "reapply_left_chat": "⚠️ **Нагадування**\nЗдається, ти залишив чат у минулому, проте твоя анкета все ще доступна до використання. Подати запит на вступ користуючись старою анкетою?", + "reapply_restarted": "🔁 **Перезапущено**\nРозпочате заповнення анкети спочатку.", + "rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", + "rejected_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.", + "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", + "rejected_russian": "русский военньій корабль, иди нахуй!", + "rejected": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти. Better luck next time!\n\nТи можеш спробувати повторно заповнити анкету командою /reapply", + "shutdown": "Вимкнення бота з підом `{0}`", + "spoiler_cancel": "Створення спойлера було припинено", + "spoiler_described_named": "Спойлер категорії \"{0}\" від **{1}**: {2}", + "spoiler_described": "Спойлер категорії \"{0}\": {1}", + "spoiler_description_enter": "Добре, введіть бажаний опис спойлера", + "spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.", + "spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.", + "spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.", + "spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).", + "spoiler_ready": "Успіх! Спойлер створено", + "spoiler_send_description": "Тепер треба надіслати коротенький опис спойлера, щоб люди розуміли що під ним варто очкувати.", + "spoiler_send": "Користуйтесь кнопкою нижче щоб надіслати його.", + "spoiler_started": "Розпочато створення спойлера. Будь ласка, оберіть категорію спойлера за допомогою клавіатури бота.", + "spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення", + "spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)", + "sponsor_approved_by": "✅ **Підписку схвалено**\nАдмін **{0}** переглянув та схвалив форму `{1}`.", + "sponsor_approved": "Вітаємо! Твою форму переглянули та підтвердили її правильність. Коли термін дії наданої підписки буде добігати кінця - ми нагадаємо, що треба оновити дані аби й надалі отримувати плюшки в чаті. Також можна повторно заповнити форму, якщо хочеться змінити бажане ім'я ролі або подовжити термін дії підписки завчасно, за допомогою команди /sponsorship. Гарного дня!", + "sponsor_confirm": "**Дані форми:**\nСтрімер: {0}\nПідписка до: {1}\nХочу роль: {2}\n\nПеревір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", + "sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}", + "sponsor_rejected_by": "❌ **Підписку відхилено**\nАдмін **{0}** переглянув та відхилив форму `{1}`.", + "sponsor_rejected": "Ой лишенько! Твою форму переглянули, однак не підтвердили її. Можливо, щось не так з датами, або ж бажана роль не може бути надана.\n\nТи можеш спробувати повторно заповнити форму командою /sponsorship", "sponsor1_invalid": "Будь ласка, введіть ім'я не довше за 240 символів", - "sponsor2": "До якої дати (`ДД.ММ.РРРР`) підписка?", + "sponsor1": "На яку саме дівчину платна підписка?", "sponsor2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", "sponsor2_past": "Вказана дата знаходиться в минулому. Будь ласка, вкажіть правильний термін дії підписки", + "sponsor2": "До якої дати (`ДД.ММ.РРРР`) підписка?", "sponsor3": "Будь ласка, надішли одне фото для підтвердження дійсності підписки\n\nℹ️ **Підказка**\nПрочитай як правильно скрінити легітимне підтвердження підписки: https://telegra.ph/Pіdpiska-na-holo-dіvchinu-01-02", "sponsor4": "Яку роль ти бажаєш отримати?\n\nℹ️ **Підказка**\nНазва ролі повинна бути якось пов'язана зі вказаною дівчиною, не повинна порушувати правила спільноти а також має бути не довше за 16 символів (обмеження Telegram).", "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", - "confirm": "Супер, дякуємо!\n\nБудь ласка, перевір правильність даних:\n{0}\n\nВсе правильно?", - "sponsor_confirm": "**Дані форми:**\nСтрімер: {0}\nПідписка до: {1}\nХочу роль: {2}\n\nПеревір чи все правильно та жмакни кнопку на клавіатурі щоб продовжити.", - "application_sent": "Дякуємо! Ми надіслали твою анкетку на перевірку. Ти отримаєш повідомлення, як тільки її перевірять та приймуть рішення.. До тих пір від тебе більше нічого не потребується :)", + "sponsorship_apply": "ℹ️ Оформіть платну підписку на когось з Холо, заповніть форму та отримайте особливу роль в якості винагороди!", + "sponsorship_applying": "ℹ️ Розпочато заповнення форми на отримання бонусів за платну підписку на холодівчат.", "sponsorship_sent": "Дякуємо! Ми надіслали форму на перевірку. Ти отримаєш повідомлення, як тільки її перевірять та приймуть рішення. :)", - "application_got": "Отримано анкету від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", - "reapply_got": "Отримано оновлення анкети від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані анкети:**\n{3}", - "sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}", - "shutdown": "Вимкнення бота з підом `{0}`", - "startup": "Запуск бота з підом `{0}`", - "startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)", - "startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)", + "sponsorships_expired": "⚠️ **Нагадування**\nТермін дії вказаної підписки сплив. Для повторного отримання ролі користуйся командою /sponsorship.", + "sponsorships_expires": "⚠️ **Нагадування**\nНадана платна підписка припинить діяти **за {0} д**. Будь ласка, оновіть дані про неї командою /sponsorship інакше роль буде втрачено!", + "start": "Привіт і ласкаво просимо!\n\nМи будуємо українське ком'юніті фанатів Гололайву і раді кожному, хто поділяє наші інтереси або тільки зацікавився цією тематикою та хоче дізнатись більше. Чим ми відрізняеємось від звичайного тематичного чату? Ми намагаємось створювати усі можливі умови, щоб люди знаходили однодумців у своїх містах та збирались разом. Інколи проводимо великі зустрічі на честь Голо-івентів у Києві. Збираємось у Дискорді для сумісних переглядів концертів, музичних топів, ігор тощо. Проводимо різні івенти у чаті. Об'єднуємось, щоб замовляти офіційний мерч із Японії. Підтримуємо україномовних кліперів та будуємо плани по поширенню нашого ком'юніті на майбутнє.\n\nЦей бот створений для прийому заявок на вступ до нашої чат-спільноти. Усі анкети, після підтвердження адміністрацією, можуть дивитися й інші учасники чату у будь-який момент. Тому, будь ласка, віднесіться до її заповнення відповідально.\n\nЯкщо вашу анкету відхилили, то, скоріше за все:\n1) Вона порушує перше правило чату (/rules - ознайомитись перед вступом до чату).\n2) Ви намагаєтесь додати до чату \"додатковий/запасний\" акаунт.\n3) Ви не дуже відповідально віднеслись до її заповнення.\n\nЯкщо із вашою анкетою щось не так, то вам через бота прийде повідомлення від адміністраторів для вирішення питання. Якщо ви зіштовхнулись із якоюсь проблемою або бот не надсилає вам необхідні повідомлення - напишіть мені у приватні повідомлення @Chirkopol.\n\nПісля прийому вашої анкети бот згенерує вам одноразове посилання. Не забудьте перейти по ньому, щоб потрапити до чату.\n\nДля продовження, нас цікавить відповідь на питання:\nЧи хочеш ти доєднатись до українського ком'юніті фанатів Гололайв та чи зобов'язуєшся ти дотримуватися усіх правил?", "startup_downtime_days": "Запуск бота з підом `{0}` (лежав {1} дн.)", - "approved": "Вітаємо! Твою анкету переглянули та підтвердили твоє право на вступ.\n\nПеред тим, як ти натиснеш на кнопочку під повідомленням, щоб вступити до нашої лампової спільноти, дамо тобі трішечки додаткової інформації.\nПам'ятай, що натискаючи її, ти підтверджуєш, що ознайомився із нашими правилами (/rules) та зобов'язуєшся їх дотримуватись.\n\nПісля того, як потрапиш до чату, не закидуй цього бота далеко.\nПрописавши @holoua_bot у боті, ти відкриєш список із усіх наших анкет. Ти можеш натискати на будь-яку із них та дізнаватись про кожного із нас більше інформації.\nЗавдяки команді /nearby, ти зможеш дізнатись, чи є серед нас однодумці із твого міста.\nЯкщо у тебе є спонсорська підписка на будь-кого із учасниць Гололайву, то ти маєш право отримати унікальну роль у нашому чаті (/sponsorship) та виділятись серед інших учасників. (А ще зможеш отримати декілька додаткових функцій. Але нікому про це не кажи!)", - "approved_joined": "Вітаємо! Твою анкету переглянули та підтвердили її правильність. Дякуємо за витрачений на заповнення час та гарного дня!", - "read_rules": "Будь ласка, прочитай ці правила перш ніж натискати на кнопку та приєднуватись до чату.", - "rejected": "Ой лишенько! Твою анкету переглянули, однак не підтвердили право на вступ до спільноти. Better luck next time!\n\nТи можеш спробувати повторно заповнити анкету командою /reapply", - "rejected_russian": "русский военньій корабль, иди нахуй!", - "approved_by": "✅ **Анкету схвалено**\nАдмін **{0}** переглянув та схвалив анкету `{1}`.", - "rejected_by": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`.", - "rejected_by_agr": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: агресивна/токсична анкета.", - "rejected_by_rus": "❌ **Анкету відхилено**\nАдмін **{0}** переглянув та відхилив анкету `{1}`, заборонивши вступ до спільноти.\nПричина: русня.", - "sponsor_approved": "Вітаємо! Твою форму переглянули та підтвердили її правильність. Коли термін дії наданої підписки буде добігати кінця - ми нагадаємо, що треба оновити дані аби й надалі отримувати плюшки в чаті. Також можна повторно заповнити форму, якщо хочеться змінити бажане ім'я ролі або подовжити термін дії підписки завчасно, за допомогою команди /sponsorship. Гарного дня!", - "sponsor_rejected": "Ой лишенько! Твою форму переглянули, однак не підтвердили її. Можливо, щось не так з датами, або ж бажана роль не може бути надана.\n\nТи можеш спробувати повторно заповнити форму командою /sponsorship", - "sponsor_approved_by": "✅ **Підписку схвалено**\nАдмін **{0}** переглянув та схвалив форму `{1}`.", - "sponsor_rejected_by": "❌ **Підписку відхилено**\nАдмін **{0}** переглянув та відхилив форму `{1}`.", - "contact": "Анкета `{0}`\n\n**Дані анкети:**\n{1}\n\n{2}", - "application_status_accepted": "Прийнята `{0}` від {1}", - "application_status_rejected": "Відхилена `{0}` від {1}", - "application_status_on_hold": "Анкета все ще на розгляді", - "application_status_not_send": "Анкета ще не була відправлена", - "contact_invalid": "Надісланий контакт не має завершеної анкети.", - "contact_not_member": "Надісланий контакт не є користувачем Telegram.", - "already_sent": "Анкету вже надіслано, просто почекай. Тобі одразу повідомлять, яке рішення буде прийнято.", - "sus_joined": "Користувач **{0}** (`{1}`) зайшов до групи не за своїм персональним запрошенням.", + "startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)", + "startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)", + "startup": "Запуск бота з підом `{0}`", "sus_allowed_by": "✅ **Доступ дозволено**\nАдмін **{0}** дозволив `{1}` вступити до спільноти не за персональним посиланням.", + "sus_joined": "Користувач **{0}** (`{1}`) зайшов до групи не за своїм персональним запрошенням.", "sus_rejected_by": "❌ **Доступ заборонено**\nАдмін **{0}** заборонив `{1}` доступ до спільноти не за персональним посиланням.", - "reapply_forbidden": "❌ **Дія неможлива**\nТвоя минула анкета ще не була схвалена або відхилена.", - "reapply_in_progress": "❌ **Дія неможлива**\nТи прямо зараз вже заповнюєш анкету. Якщо в ній є помилка - просто натисни кнопку нижче щоб почати заповнювати спочатку.", - "reapply_restarted": "🔁 **Перезапущено**\nРозпочате заповнення анкети спочатку.", - "reapply_left_chat": "⚠️ **Нагадування**\nЗдається, ти залишив чат у минулому, проте твоя анкета все ще доступна до використання. Подати запит на вступ користуючись старою анкетою?", - "birthday": "У користувача **{0}** (@{1}) сьогодні день народження! Виповнилось {2} років", - "application_invalid_syntax": "Неправильний синтаксис!\nТреба: `/application ID/NAME/USERNAME`", - "warned": "Попереджено **{0}** (`{1}`) про порушення правил", + "syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans/event`", + "syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`", + "user_invalid": "Надісланий користувач не має завершеної анкети.", + "user_left": "Користувач **{0}** залишив чат", "warned_reason": "Попереджено **{0}** (`{1}`)\n\n**Причина:**\n{2}", + "warned": "Попереджено **{0}** (`{1}`) про порушення правил", + "warning_revoked_auto": "Попередження від {0} користувачеві `{1}` було автоматично скасовано.", + "warning_revoked": "Попередження від {0} користувачеві `{1}` було скасовано адміном `{2}`", "warnings_1": "Користувач **{0}** (`{1}`) має **{2}** попередження\n\n{3}\n\nОбрати та зняти попередження:\n`/warnings {4} revoke`", "warnings_2": "Користувач **{0}** (`{1}`) має **{2}** попереджень\n\n{3}\n\nОбрати та зняти попередження:\n`/warnings {4} revoke`", "warnings_all": "**Список попереджень**\n\n{0}\n\nДля перегляду попереджень окремо взятого користувача слід використовувати `/warnings ID/NAME/USERNAME`", - "warnings_entry": "• {0} (`{1}`)\n Попереджень: {2}", "warnings_empty": "Щось тут порожньо...\nЗ іншого боку, це добре!", + "warnings_entry": "• {0} (`{1}`)\n Попереджень: {2}", "warnings_revoke": "**Попередження {0}:**\n\n{1}\n\nБудь ласка, користуйтесь клавіатурою щоб зняти попередження з відповідним номером.", - "warning_revoked": "Попередження від {0} користувачеві `{1}` було скасовано адміном `{2}`", - "warning_revoked_auto": "Попередження від {0} користувачеві `{1}` було автоматично скасовано.", - "no_warnings": "Користувач **{0}** (`{1}`) не має попереджень", - "no_user_warnings": "Не знайдено користувачів за запитом **{0}**", - "syntax_warnings": "Неправильний синтаксис!\nТреба: `/warnings ID/NAME/USERNAME`", - "syntax_export": "Неправильний синтаксис!\nТреба: `/export applications/warnings/sponsorships/bans/event`", - "message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.", - "message_sent": "Повідомлення надіслано", - "message_no_user": "⚠️ **Помилка надсилання**\nВказано невірний ID користувача, тому не вдалось надіслати йому повідомлення. Перевірте чи в якості ID надано те число, яке було показане в анкеті.", - "message_invalid_syntax": "Неправильний синтаксис!\nТреба: `/message ID ПОВІДОМЛЕННЯ`", - "message_from": "Повідомлення від **{0}** (`{1}`):\n\n", - "message_reply_notice": "\n\n__Для того, щоб адміністрація побачила вашу відповідь, відправте її **реплаєм на це повідомлення**__", - "message_error": "⚠️ **Сталась помилка**\nНе вдалось надіслати ваше повідомлення. Розробника повідомлено про цю помилку.", - "message_traceback": "⚠️ **Сталась помилка**\nПомилка повідомлень: `{0}` -> `{1}`\nПомилка: `{2}`\n\nTraceback:\n```\n{3}\n```", - "no_user_application": "Не знайдено користувачів за запитом **{0}**", - "user_invalid": "Надісланий користувач не має завершеної анкети.", - "joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", - "joined_application": "{0} (@{1})\n\n**Дані анкети:**\n{2}", - "sponsorships_expires": "⚠️ **Нагадування**\nНадана платна підписка припинить діяти **за {0} д**. Будь ласка, оновіть дані про неї командою /sponsorship інакше роль буде втрачено!", - "sponsorships_expired": "⚠️ **Нагадування**\nТермін дії вказаної підписки сплив. Для повторного отримання ролі користуйся командою /sponsorship.", - "label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів", - "finish_sponsorship": "❌ **Дія неможлива**\nПерш ніж заповнювати анкету, треба завершити заповнення форми спонсора або перервати його командою /cancel.", - "finish_application": "❌ **Дія неможлива**\nПерш ніж заповнювати форму спонсора, треба завершити заповнення анкети.", - "nearby_invalid": "ℹ️ **Місце не знайдено**\nЗа наданим запитом не знайдено місце з координатами. Спробуйте ще раз формулючи запит в стилі \"Чернівці\" або \"Київська область\".", - "nearby_error": "⚠️ **Сталась помилка**\n\nПомилка: `{0}`\n\nTraceback:\n```\n{1}\n```", - "nearby_result": "Результати пошуку:\n\n{0}", - "nearby_empty": "Здається, нікого поблизу немає.", - "cancel": "Всі поточні операції скасовано.", - "cancel_reapply": "Всі поточні операції скасовано.\nЩоб знову заповнити анкету користуйся /reapply", - "identify_invalid_syntax": "Неправильний синтаксис!\nТреба: `/identify ID/NAME/USERNAME`", - "identify_not_found": "Не знайдено користувачів за запитом **{0}**", - "identify_success": "Користувач `{0}`\n\nІм'я: {1}\nЮзернейм: {2}\nЄ в чаті: {3}\nЄ адміном: {4}\nРоль: {5}\nНаявна анкета: {6}\nНаявне спонсорство: {7}", - "spoiler_started": "Розпочато створення спойлера. Будь ласка, оберіть категорію спойлера за допомогою клавіатури бота.", - "spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення", - "spoiler_cancel": "Створення спойлера було припинено", - "spoiler_described": "Спойлер категорії \"{0}\": {1}", - "spoiler_described_named": "Спойлер категорії \"{0}\" від **{1}**: {2}", - "spoiler_description_enter": "Добре, введіть бажаний опис спойлера", - "spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.", - "spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)", - "spoiler_send_description": "Тепер треба надіслати коротенький опис спойлера, щоб люди розуміли що під ним варто очкувати.", - "spoiler_ready": "Успіх! Спойлер створено", - "spoiler_send": "Користуйтесь кнопкою нижче щоб надіслати його.", - "spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).", - "spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.", - "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Тепер не можна відправити анкету або користуватись командами бота.", - "user_left": "Користувач **{0}** залишив чат", + "youtube_video": "На каналі [{0}]({1}) нове відео!\n\n**[{2}]({3})**", "yes": "Так", "no": "Ні", "voice_message": [ @@ -216,57 +216,57 @@ "spoiler_description": "Опис спойлера" }, "button": { - "sub_yes": "✅ Прийняти", - "sub_no": "❌ Відхилити", - "sub_russian": "🇷🇺 Відхилити (Русак)", - "sponsor_yes": "✅ Прийняти", - "sponsor_no": "❌ Відхилити", "accepted": "✅ Прийнято", + "applying_stop": "🛑 Перервати заповнення", + "ban": "💀 Заблокувати", + "banned": "☠️ Заблоковано", "declined": "❌ Відхилено", + "done": "✅ Готово", + "issue": "🪄 Створити задачу", "join": "Приєднатись", - "sus_allow": "✅ Підтвердити дозвіл", - "sus_reject": "❌ Перманентно заблокувати", - "sus_allowed": "✅ Дозвіл надано", - "sus_rejected": "❌ Користувача заблоковано", - "reapply_yes": "✅ Прийняти", + "reapply_new_one": "🔁 Заповнити знову", "reapply_no": "❌ Відхилити", "reapply_old_one": "✅ Надіслати стару", - "reapply_new_one": "🔁 Заповнити знову", - "rules_home": "🏠 Головна", + "reapply_yes": "✅ Прийняти", "rules_additional": "➕ Додаткові", + "rules_home": "🏠 Головна", "rules_next": "Далі ➡️", "rules_prev": "⬅️ Назад", - "applying_stop": "🛑 Перервати заповнення", - "done": "✅ Готово", - "sponsor_apply": "Заповнити форму", - "sponsor_started": "Форму розпочато", - "spoiler_view": "Переглянути", "spoiler_preview": "Попередній перегляд", "spoiler_send_chat": "Надіслати в холо-чат", "spoiler_send_other": "Надіслати в інший чат", - "issue": "🪄 Створити задачу", - "ban": "💀 Заблокувати", - "banned": "☠️ Заблоковано" + "spoiler_view": "Переглянути", + "sponsor_apply": "Заповнити форму", + "sponsor_no": "❌ Відхилити", + "sponsor_started": "Форму розпочато", + "sponsor_yes": "✅ Прийняти", + "sub_no": "❌ Відхилити", + "sub_russian": "🇷🇺 Відхилити (Русак)", + "sub_yes": "✅ Прийняти", + "sus_allow": "✅ Підтвердити дозвіл", + "sus_allowed": "✅ Дозвіл надано", + "sus_reject": "❌ Перманентно заблокувати", + "sus_rejected": "❌ Користувача заблоковано" }, "callback": { + "nothing": "🔔 Дія вже виконана", + "reapply_stopped": "ℹ️ Перервано заповнення анкети", + "rules_additional": "ℹ️ Показано додаткові правила", + "rules_home": "ℹ️ Показано головну правил", + "rules_page": "ℹ️ Показано правило {0}", + "spoiler_forbidden": "❌ Треба бути учасником чату", + "spoiler_sent": "✅ Повідомлення надіслано в холо-чат", + "sponsor_accepted": "✅ Форму {0} схвалено", + "sponsor_rejected": "❌ Форму {0} відхилено", + "sponsor_started": "ℹ️ Заповнення форми розпочато", "sub_accepted": "✅ Анкету {0} схвалено", + "sub_banned": "☠️ Користувача заблоковано", "sub_rejected": "❌ Анкету {0} відхилено", "sub_russian": "🇷🇺 Анкету {0} відхилено", "sus_allowed": "✅ Доступ {0} дозволено", "sus_rejected": "❌ Доступ {0} заборонено", - "sub_banned": "☠️ Користувача заблоковано", - "nothing": "🔔 Дія вже виконана", - "rules_page": "ℹ️ Показано правило {0}", - "rules_home": "ℹ️ Показано головну правил", - "rules_additional": "ℹ️ Показано додаткові правила", - "reapply_stopped": "ℹ️ Перервано заповнення анкети", - "sponsor_started": "ℹ️ Заповнення форми розпочато", - "sponsor_accepted": "✅ Форму {0} схвалено", - "sponsor_rejected": "❌ Форму {0} відхилено", - "spoiler_sent": "✅ Повідомлення надіслано в холо-чат", - "spoiler_forbidden": "❌ Треба бути учасником чату", - "warning_revoked": "✅ Попередження скасовано", - "warning_not_found": "❌ Попередження вже скасовано або не існує" + "warning_not_found": "❌ Попередження вже скасовано або не існує", + "warning_revoked": "✅ Попередження скасовано" }, "inline": { "forbidden": { From 3e22ad1895d36191b5c16e0886c54e6208d0043a Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 18 Apr 2023 16:01:42 +0200 Subject: [PATCH 25/47] Improved sponsorship fill --- locale/uk.json | 19 ++++ modules/commands/sponsorship.py | 170 ++++++++++++++++++++++++++------ 2 files changed, 160 insertions(+), 29 deletions(-) diff --git a/locale/uk.json b/locale/uk.json index 8ae15be..13c8248 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -96,12 +96,15 @@ "sponsor_got": "Отримано форму на спонсорство від `{0}`\n\nІм'я тг: `{1}`\nЮзернейм: @{2}\n\n**Дані форми:**\n{3}", "sponsor_rejected_by": "❌ **Підписку відхилено**\nАдмін **{0}** переглянув та відхилив форму `{1}`.", "sponsor_rejected": "Ой лишенько! Твою форму переглянули, однак не підтвердили її. Можливо, щось не так з датами, або ж бажана роль не може бути надана.\n\nТи можеш спробувати повторно заповнити форму командою /sponsorship", + "sponsor_resubmit_invalid_option": "Правильна опція! Спробуй /sponsorship ще раз, але користуйся клавіатурою щоб обрати з можливих варіантів.", + "sponsor_resubmit": "Здається, ти маєш активну підписку на **{0}**. Хочеш подовжити її чи заповнити нову?", "sponsor1_invalid": "Будь ласка, введіть ім'я не довше за 240 символів", "sponsor1": "На яку саме дівчину платна підписка?", "sponsor2_invalid": "Будь ласка, введи дату формату `ДД.ММ.РРРР`", "sponsor2_past": "Вказана дата знаходиться в минулому. Будь ласка, вкажіть правильний термін дії підписки", "sponsor2": "До якої дати (`ДД.ММ.РРРР`) підписка?", "sponsor3": "Будь ласка, надішли одне фото для підтвердження дійсності підписки\n\nℹ️ **Підказка**\nПрочитай як правильно скрінити легітимне підтвердження підписки: https://telegra.ph/Pіdpiska-na-holo-dіvchinu-01-02", + "sponsor4_resubmit": "Майже закінчили. Стара роль **{0}** може бути використана і цього разу, а може бути змінена. Будь ласка, обери те, що тобі підходить.\n\n⚠️ **Увага!**\nПісля обирання варіанту або введення нової назви ролі, заявку буде надіслано автоматично. Для відміни дії зараз ще можна використовувати /cancel", "sponsor4": "Яку роль ти бажаєш отримати?\n\nℹ️ **Підказка**\nНазва ролі повинна бути якось пов'язана зі вказаною дівчиною, не повинна порушувати правила спільноти а також має бути не довше за 16 символів (обмеження Telegram).", "sponsorship_application_empty": "❌ **Дія неможлива**\nУ тебе немає заповненої та схваленої анкети. Заповни таку за допомогою /reapply та спробуй ще раз після її підтвердження.", "sponsorship_apply": "ℹ️ Оформіть платну підписку на когось з Холо, заповніть форму та отримайте особливу роль в якості винагороди!", @@ -195,6 +198,22 @@ [ "Інше" ] + ], + "sponsorship_restore": [ + [ + "Подовжити стару" + ], + [ + "Заповнити нову підписку" + ] + ], + "sponsorship_restore_label": [ + [ + "Залишити стару" + ], + [ + "Ввести замість неї нову" + ] ] }, "force_reply": { diff --git a/modules/commands/sponsorship.py b/modules/commands/sponsorship.py index 76b0ad1..eb816ec 100644 --- a/modules/commands/sponsorship.py +++ b/modules/commands/sponsorship.py @@ -13,8 +13,9 @@ from pyrogram.types import ( from pyrogram.client import Client from classes.holo_user import HoloUser from modules import custom_filters -from modules.utils import locale, should_quote -from modules.database import col_sponsorships +from modules.handlers.confirmation import confirm_yes +from modules.utils import all_locales, locale, should_quote +from modules.database import col_sponsorships, col_tmp from convopyro import listen_message @@ -74,9 +75,11 @@ async def cmd_sponsorship(app: Client, msg: Message): return await msg.reply_text( - f'You have an active membership for **{existent["sponsorship"]["streamer"]}**. Wanna resubmit it once more?', + locale("sponsor_resubmit", "message", locale=msg.from_user).format( + existent["sponsorship"]["streamer"] + ), reply_markup=ReplyKeyboardMarkup( - [["Yep, use old data"], ["Nope, refill it once more"]], + locale("sponsorship_restore", "keyboard", locale=msg.from_user), resize_keyboard=True, one_time_keyboard=True, ), @@ -89,10 +92,26 @@ async def cmd_sponsorship(app: Client, msg: Message): input_streamer = existent["sponsorship"]["streamer"] - if answer_decision.text.lower() == "yep, use old data": + values_keep = [] + for pattern in all_locales("sponsorship_restore", "keyboard"): + values_keep.append(pattern[0][0].lower()) + + values_new = [] + for pattern in all_locales("sponsorship_restore", "keyboard"): + values_new.append(pattern[1][0].lower()) + + if answer_decision.text.lower() in values_keep: await answer_decision.reply_text( - "Okay, reusing the old data.\n\nUntil when is your sub?\n\nEnter the date as DD.MM.YYYY", - reply_markup=ForceReply(placeholder="Expiry date as DD.MM.YYYY"), + locale("sponsor2", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor2", + "force_reply", + locale=msg.from_user, + ) + ) + ), ) while True: answer_date = await listen_message(app, msg.chat.id) @@ -102,17 +121,44 @@ async def cmd_sponsorship(app: Client, msg: Message): try: input_dt = datetime.strptime(answer_date.text, "%d.%m.%Y") + if datetime.now() >= input_dt: + await msg.reply_text( + locale("sponsor2_past", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale("sponsor2", "force_reply", locale=msg.from_user) + ) + ), + ) + continue break except ValueError: await answer_date.reply_text( - "Invalid date! Provide as DD.MM.YYYY", - reply_markup=ForceReply(placeholder="Expiry date as DD.MM.YYYY"), + locale(f"sponsor2_invalid", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor2", + "force_reply", + locale=msg.from_user, + ) + ) + ), ) continue + await answer_date.reply_text( + locale("sponsor3", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor3", + "force_reply", + locale=msg.from_user, + ) + ) + ), + ) while True: - await answer_date.reply_text( - "Alright. Now provide your proof **as a single screenshot**" - ) answer_proof = await listen_message(app, msg.chat.id) if is_none_or_cancel(answer_proof): @@ -120,15 +166,26 @@ async def cmd_sponsorship(app: Client, msg: Message): if answer_proof.photo is None: await answer_proof.reply_text( - "Please, provide proof **as a single screenshot**" + locale("sponsor3", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor3", + "force_reply", + locale=msg.from_user, + ) + ) + ), ) continue input_proof = answer_proof.photo.file_id break await msg.reply_text( - f'Almost done. Do you want to keep the label **{existent["sponsorship"]["label"]}** or you want to change it?', + locale("sponsor4_resubmit", "message", locale=msg.from_user).format( + existent["sponsorship"]["label"] + ), reply_markup=ReplyKeyboardMarkup( - [["Keep the old one"], ["Set a new one instead"]], + locale("sponsorship_restore_label", "keyboard", locale=msg.from_user), resize_keyboard=True, one_time_keyboard=True, ), @@ -144,19 +201,39 @@ async def cmd_sponsorship(app: Client, msg: Message): await answer_label_decision.reply_text( "Please, choose a valid option.", reply_markup=ReplyKeyboardMarkup( - [["Keep the old one"], ["Set a new one instead"]], + locale( + "sponsorship_restore_label", + "keyboard", + locale=msg.from_user, + ), resize_keyboard=True, one_time_keyboard=True, ), ) continue - if answer_label_decision.text.lower() == "keep the old one": + values_keep = [] + for pattern in all_locales("sponsorship_restore_label", "keyboard"): + values_keep.append(pattern[0][0].lower()) + + values_new = [] + for pattern in all_locales("sponsorship_restore_label", "keyboard"): + values_new.append(pattern[1][0].lower()) + + if answer_label_decision.text.lower() in values_keep: input_label = existent["sponsorship"]["label"] - elif answer_label_decision.text.lower() == "set a new one instead": + elif answer_label_decision.text.lower() in values_new: await answer_label_decision.reply_text( - "Okay. Please provide a new label up to 16 characters long", - reply_markup=ForceReply(placeholder="New label"), + locale("sponsor4", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor4", + "force_reply", + locale=msg.from_user, + ) + ) + ), ) while True: answer_label = await listen_message(app, msg.chat.id) @@ -166,27 +243,61 @@ async def cmd_sponsorship(app: Client, msg: Message): if answer_label.text is None: await answer_label.reply_text( - "Please provide valid label", - reply_markup=ForceReply(placeholder="New label"), + locale("label_too_long", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor4", + "force_reply", + locale=msg.from_user, + ) + ) + ), ) continue elif len(answer_label.text) > 16: await answer_label.reply_text( - "Please provide a label not longer than 16 characters long", - reply_markup=ForceReply(placeholder="New label"), + locale("label_too_long", "message", locale=msg.from_user), + reply_markup=ForceReply( + placeholder=str( + locale( + f"sponsor4", + "force_reply", + locale=msg.from_user, + ) + ) + ), ) continue input_label = answer_label.text break - await msg.reply_text( - f"So we did it for streamer **{input_streamer}**, til {input_dt.strftime('%d.%m.%Y')}, proofed by `{input_proof}` and labeled as **{input_label}**.", - reply_markup=ReplyKeyboardRemove(), + col_tmp.find_one_and_delete( + {"user": msg.from_user.id, "type": "sponsorship"} ) + + col_tmp.insert_one( + { + "user": msg.from_user.id, + "type": "sponsorship", + "complete": True, + "sent": False, + "state": "fill", + "stage": 4, + "sponsorship": { + "streamer": input_streamer, + "expires": input_dt, + "proof": input_proof, + "label": input_label, + }, + } + ) + + await confirm_yes(app, msg, kind="sponsorship") return - elif answer_decision.text.lower() == "nope, refill it once more": + elif answer_decision.text.lower() in values_new: await msg.reply_text( locale("sponsorship_apply", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup( @@ -206,7 +317,8 @@ async def cmd_sponsorship(app: Client, msg: Message): return else: await answer_decision.reply_text( - "Invalid option!", reply_markup=ReplyKeyboardRemove() + locale("sponsor_resubmit_invalid_option", "message", locale=msg.from_user), + reply_markup=ReplyKeyboardRemove(), ) return # else: From 9823cccd450bb15dbe3bd20022e668b724a0dc4e Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 18 Apr 2023 16:03:59 +0200 Subject: [PATCH 26/47] https://git.end-play.xyz/profitroll/HoloCheckerAPI --- api_avatars.py | 40 ---------------------------------------- requirements.txt | 4 +--- 2 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 api_avatars.py diff --git a/api_avatars.py b/api_avatars.py deleted file mode 100644 index 85fcf62..0000000 --- a/api_avatars.py +++ /dev/null @@ -1,40 +0,0 @@ -from os import makedirs, path, sep -from fastapi import FastAPI, HTTPException -from fastapi.responses import FileResponse, JSONResponse, Response -from starlette.status import HTTP_404_NOT_FOUND -from modules.utils import configGet - -makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True) - -app = FastAPI(title="HoloUA Avatars API", docs_url=None, redoc_url=None, version="1.0") - - -@app.get("/check", response_class=JSONResponse, include_in_schema=False) -@app.head("/check", response_class=JSONResponse, include_in_schema=False) -async def check(): - return JSONResponse({"detail": "I'm alright, thank you"}) - - -@app.get("/", response_class=FileResponse, include_in_schema=False) -async def avatar_get(avatar_id: str): - if path.exists(f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}'): - return FileResponse( - f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}', - media_type="image/jpg", - ) - else: - raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="File not found") - - -@app.head("/", response_class=Response, include_in_schema=False) -async def avatar_head(avatar_id: str): - if path.exists(f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}'): - return Response( - headers={ - "Content-Length": path.getsize( - f'{configGet("cache", "locations")}{sep}avatars{sep}{avatar_id}' - ) - } - ) - else: - raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail="File not found") diff --git a/requirements.txt b/requirements.txt index 3d0e16c..38333d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,7 @@ -aiocsv==1.2.3 +aiocsv==1.2.4 aiofiles~=23.1.0 APScheduler~=3.10.1 convopyro==0.5 -fastapi~=0.95.0 ftfy~=6.1.1 psutil==5.9.4 polyglot~=16.7.4 @@ -13,7 +12,6 @@ Pyrogram~=2.0.103 python_dateutil==2.8.2 pykeyboard==0.1.5 requests==2.28.2 -starlette==0.26.1 tgcrypto==1.2.5 ujson~=5.7.0 xmltodict==0.13.0 \ No newline at end of file From 80e62c25857eb8c8eb1004e5597be881b643d8e5 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 18 Apr 2023 16:06:03 +0200 Subject: [PATCH 27/47] Converted names to lowercase --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 38333d7..8c8cdd0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ aiocsv==1.2.4 aiofiles~=23.1.0 -APScheduler~=3.10.1 +apscheduler~=3.10.1 convopyro==0.5 ftfy~=6.1.1 psutil==5.9.4 polyglot~=16.7.4 -PyICU==2.10.2 +pyicu==2.10.2 pycld2==0.41 pymongo==4.3.3 -Pyrogram~=2.0.103 +pyrogram~=2.0.103 python_dateutil==2.8.2 pykeyboard==0.1.5 requests==2.28.2 From 948837e45f3e34261104f7bec06a8fa9b6c0e041 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 18 Apr 2023 16:07:51 +0200 Subject: [PATCH 28/47] Added notice about HoloCheckerAPI --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 711faed..626a706 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ So bot has its "config_example.json" and it needs to be changed. Copy this file to "config.json" and open it with any text editor. +You should also install [HoloCheckerAPI](https://git.end-play.xyz/profitroll/HoloCheckerAPI) for inline requests to work. + After all of that you're good to go! Happy using :) ## To-Do From 49944b90c90096737382888e62eb8506a8cb940a Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 10:28:39 +0200 Subject: [PATCH 29/47] Renamed some event's assets --- assets/{event => event_easter_2023}/stage_bonus.jpg | Bin modules/event.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename assets/{event => event_easter_2023}/stage_bonus.jpg (100%) diff --git a/assets/event/stage_bonus.jpg b/assets/event_easter_2023/stage_bonus.jpg similarity index 100% rename from assets/event/stage_bonus.jpg rename to assets/event_easter_2023/stage_bonus.jpg diff --git a/modules/event.py b/modules/event.py index ff46c68..4cece91 100644 --- a/modules/event.py +++ b/modules/event.py @@ -396,7 +396,7 @@ async def cmd_event_bonus(app: Client, msg: Message): return await msg.reply_photo( - path.join("assets", "event", "stage_bonus.jpg"), + path.join("assets", "event_easter_2023", "stage_bonus.jpg"), caption="""Раптом, стіна почала рухатися, відкриваючи вам прохід. Ви повільно зайшли всередину і побачили якусь дівчину із кролячими вухами. Помітивши вас, вона повернулась і, посміхаючись, промовила: - Вибач, але твоє яйце знаходиться в іншій печері. Тут тільки я і мій друг - П'ятнична Ніч. From 7fd3cc061ec9ab10267c15727c8e63ac2030ef0e Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 10:28:57 +0200 Subject: [PATCH 30/47] Manually disabled event import --- holochecker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/holochecker.py b/holochecker.py index 595540b..145d40f 100644 --- a/holochecker.py +++ b/holochecker.py @@ -29,7 +29,9 @@ from modules.commands.start import * from modules.commands.warn import * from modules.commands.warnings import * -from modules.event import * +# This one is only imported during events +# and should be completely rewritten for each one. +# from modules.event import * from modules.callbacks.ban import * from modules.callbacks.nothing import * From ea753beda1957edbae598dc9ece5b45cb03d807a Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 10:31:25 +0200 Subject: [PATCH 31/47] This commit closes #37 --- config_example.json | 1 + modules/scheduled.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/config_example.json b/config_example.json index 0b3e093..6042585 100644 --- a/config_example.json +++ b/config_example.json @@ -64,6 +64,7 @@ }, "sponsorships": { "time": 9, + "grayout_days": 2, "enabled": true }, "cache_avatars": { diff --git a/modules/scheduled.py b/modules/scheduled.py index 244ab1b..c375064 100644 --- a/modules/scheduled.py +++ b/modules/scheduled.py @@ -133,7 +133,7 @@ if configGet("enabled", "features", "sponsorships") is True: ) async def check_sponsors(): for entry in col_sponsorships.find( - {"sponsorship.expires": {"$lt": datetime.now() + timedelta(days=2)}} + {"sponsorship.expires": {"$lt": datetime.now() + timedelta(days=3)}} ): try: if entry["user"] not in jsonLoad( @@ -159,7 +159,14 @@ if configGet("enabled", "features", "sponsorships") is True: ) continue for entry in col_sponsorships.find( - {"sponsorship.expires": {"$lt": datetime.now() - timedelta(days=1)}} + { + "sponsorship.expires": { + "$lt": datetime.now() + - timedelta( + days=configGet("grayout_days", "scheduler", "sponsorships") + ) + } + } ): try: holo_user = HoloUser(entry["user"]) From 453293e38a5e0ca0ce27ced7586004e245f50b7a Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 10:32:57 +0200 Subject: [PATCH 32/47] Sponsorship reapply suggested within 90 days --- modules/commands/sponsorship.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/commands/sponsorship.py b/modules/commands/sponsorship.py index eb816ec..44fa779 100644 --- a/modules/commands/sponsorship.py +++ b/modules/commands/sponsorship.py @@ -51,7 +51,7 @@ async def cmd_sponsorship(app: Client, msg: Message): existent = col_sponsorships.find_one( { "user": msg.from_user.id, - "sponsorship.expires": {"$gt": datetime.now() - timedelta(days=1)}, + "sponsorship.expires": {"$gt": datetime.now() - timedelta(days=90)}, } ) From 0f88cb605906acb63c99c41ed983aa5f7f89b9a7 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 10:38:45 +0200 Subject: [PATCH 33/47] Fixed typo in videonote (analytics) --- modules/handlers/analytics_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/handlers/analytics_group.py b/modules/handlers/analytics_group.py index daae591..8706672 100644 --- a/modules/handlers/analytics_group.py +++ b/modules/handlers/analytics_group.py @@ -235,7 +235,7 @@ async def msg_destination_group(app: Client, msg: Message): "file_size": thumbail.file_size, } ) - analytics_entry["video_note"] = { + analytics_entry["videonote"] = { "id": msg.video_note.file_id, "duration": msg.video_note.duration, "length": msg.video_note.length, From c7037ae246139ccc9ccc913251884e7f9a246178 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 10:53:26 +0200 Subject: [PATCH 34/47] Bump psutil to ~5.9.5 and pyicu to ~2.11 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8c8cdd0..6f83f95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ aiofiles~=23.1.0 apscheduler~=3.10.1 convopyro==0.5 ftfy~=6.1.1 -psutil==5.9.4 +psutil~=5.9.5 polyglot~=16.7.4 -pyicu==2.10.2 +pyicu~=2.11 pycld2==0.41 pymongo==4.3.3 pyrogram~=2.0.103 From 09e3c23c4fe134e98414384c22e61a223d6c5b09 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 19 Apr 2023 14:49:10 +0200 Subject: [PATCH 35/47] Commented import of the event --- modules/commands/export.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/modules/commands/export.py b/modules/commands/export.py index f502779..d5940cf 100644 --- a/modules/commands/export.py +++ b/modules/commands/export.py @@ -15,7 +15,8 @@ from modules import custom_filters from modules.database import col_applications, col_sponsorships, col_warnings from modules.logging import logWrite from modules.utils import locale, should_quote -from modules.event import col_event + +# from modules.event import col_event @app.on_message( @@ -34,7 +35,13 @@ async def cmd_export(app: Client, msg: Message): selection = msg.command[1].lower() - if selection not in ["applications", "warnings", "sponsorships", "bans", "event"]: + if selection not in [ + "applications", + "warnings", + "sponsorships", + "bans", + # "event", + ]: await msg.reply_text( locale("syntax_export", "message", locale=msg.from_user), quote=should_quote(msg), @@ -146,14 +153,14 @@ async def cmd_export(app: Client, msg: Message): output_json.append(entry) output_csv.append(entry) - elif selection == "event": - header_csv = ["user", "stage", "date"] + # elif selection == "event": + # header_csv = ["user", "stage", "date"] - for entry in list(col_event.find()): - del entry["_id"] - entry["date"] = entry["date"].isoformat() - output_json.append(entry) - output_csv.append(entry) + # for entry in list(col_event.find()): + # del entry["_id"] + # entry["date"] = entry["date"].isoformat() + # output_json.append(entry) + # output_csv.append(entry) # Saving CSV async with aiofiles.open(temp_file + ".csv", mode="w", encoding="utf-8") as file: From a0164e13c8ff054d9148d67c52d6949c6611c0a1 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 20 Apr 2023 11:43:06 +0200 Subject: [PATCH 36/47] Added config for renovate --- .renovaterc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .renovaterc diff --git a/.renovaterc b/.renovaterc new file mode 100644 index 0000000..a670fa0 --- /dev/null +++ b/.renovaterc @@ -0,0 +1,14 @@ +{ + "branchName": "dev", + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch", + "pin", + "digest" + ], + "automerge": true + } + ] +} \ No newline at end of file From 0a8a215f3e1310fe36ae12a0cd37d7f81c2e81fe Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 20 Apr 2023 13:20:09 +0200 Subject: [PATCH 37/47] Updated Renovate config --- .renovaterc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.renovaterc b/.renovaterc index a670fa0..3de8304 100644 --- a/.renovaterc +++ b/.renovaterc @@ -1,4 +1,8 @@ { + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ], "branchName": "dev", "packageRules": [ { From ad7d14f091f5255f34966e40b2680c6dd9c32c14 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 20 Apr 2023 13:36:42 +0200 Subject: [PATCH 38/47] Fixed branch name in Renovate config --- .renovaterc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.renovaterc b/.renovaterc index 3de8304..c416352 100644 --- a/.renovaterc +++ b/.renovaterc @@ -3,7 +3,9 @@ "extends": [ "config:base" ], - "branchName": "dev", + "baseBranches": [ + "dev" + ], "packageRules": [ { "matchUpdateTypes": [ From ad8ce9034db87be433b28b9f2ec1ba0352ffde55 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 22 Apr 2023 22:53:02 +0200 Subject: [PATCH 39/47] Made a few dependencies strict --- requirements.txt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6f83f95..4d2534e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,17 @@ -aiocsv==1.2.4 -aiofiles~=23.1.0 -apscheduler~=3.10.1 -convopyro==0.5 -ftfy~=6.1.1 -psutil~=5.9.5 -polyglot~=16.7.4 -pyicu~=2.11 -pycld2==0.41 -pymongo==4.3.3 -pyrogram~=2.0.103 -python_dateutil==2.8.2 -pykeyboard==0.1.5 -requests==2.28.2 -tgcrypto==1.2.5 -ujson~=5.7.0 +aiocsv==1.2.4 +aiofiles~=23.1.0 +apscheduler==3.10.1 +convopyro==0.5 +ftfy~=6.1.1 +psutil~=5.9.5 +polyglot~=16.7.4 +pyicu~=2.11 +pycld2==0.41 +pymongo==4.3.3 +pyrogram==2.0.103 +python_dateutil==2.8.2 +pykeyboard==0.1.5 +requests==2.28.2 +tgcrypto==1.2.5 +ujson~=5.7.0 xmltodict==0.13.0 \ No newline at end of file From 04f1590fb5d8028b4b90ee435b17c95762ddb1bd Mon Sep 17 00:00:00 2001 From: Renovate Date: Sat, 22 Apr 2023 23:53:26 +0300 Subject: [PATCH 40/47] Update dependency pyrogram to v2.0.104 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4d2534e..e57f9b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ polyglot~=16.7.4 pyicu~=2.11 pycld2==0.41 pymongo==4.3.3 -pyrogram==2.0.103 +pyrogram==2.0.104 python_dateutil==2.8.2 pykeyboard==0.1.5 requests==2.28.2 From 844de7ef12161b04bb5ed8d0df1acea6b7ee2855 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 24 Apr 2023 15:38:39 +0200 Subject: [PATCH 41/47] Changed behavior on LabelSettingError --- locale/uk.json | 1 + modules/callbacks/sponsorship.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/locale/uk.json b/locale/uk.json index 13c8248..8bcb3fb 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -27,6 +27,7 @@ "issue": "**Допоможіть боту**\nЗнайшли баг або помилку? Маєте файну ідею для нової функції? Повідомте нас, створивши нову задачу на гіті.\n\nЗа можливості, опишіть свій запит максимально детально. Якщо є змога, також додайте скріншоти або додаткову відому інформацію.", "joined_application": "{0} (@{1})\n\n**Дані анкети:**\n{2}", "joined_false_link": "Користувач **{0}** (`{1}`) приєднався до групи не за своїм посиланням", + "label_set_exception": "❌ **Не вдалось встановити роль**\nУ зв'язку з помилкою `{0}` не вдалось встановити роль. Власника бота повідомлено.", "label_too_long": "Довжина назви ролі не повинна перевищувати 16 символів", "message_enter": "Надішліть повідомлення, яке треба переслати адмінам.\n\nЗверніть увагу, що повідомлення може містити лише одне медіа або файл.", "message_error": "⚠️ **Сталась помилка**\nНе вдалось надіслати ваше повідомлення. Розробника повідомлено про цю помилку.", diff --git a/modules/callbacks/sponsorship.py b/modules/callbacks/sponsorship.py index 016be60..f930ab5 100644 --- a/modules/callbacks/sponsorship.py +++ b/modules/callbacks/sponsorship.py @@ -112,7 +112,11 @@ async def callback_query_sponsor_yes(app: Client, clb: CallbackQuery): ) except LabelSettingError as exp: await app.send_message( - configGet("admin", "groups"), exp.__str__(), disable_notification=True + configGet("owner"), exp.__str__(), disable_notification=True + ) + await clb.message.reply_text( + locale("label_set_exception", "message", locale=clb.from_user), + disable_notification=True, ) edited_markup = [ From e90f5c2f90d6c954872f1d7cf83b96cf9d057887 Mon Sep 17 00:00:00 2001 From: Renovate Date: Wed, 26 Apr 2023 18:47:54 +0300 Subject: [PATCH 42/47] Update dependency requests to v2.29.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e57f9b5..0de6ae3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pymongo==4.3.3 pyrogram==2.0.104 python_dateutil==2.8.2 pykeyboard==0.1.5 -requests==2.28.2 +requests==2.29.0 tgcrypto==1.2.5 ujson~=5.7.0 xmltodict==0.13.0 \ No newline at end of file From 3d7ab0654a4cb6f85d4b0815a1fe75ba891fed11 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 2 May 2023 10:58:41 +0200 Subject: [PATCH 43/47] Changed repo URL --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 626a706..ece6a9f 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@

Small Telegram bot made on Pyrogram

-License: GPL -Code style: black +License: GPL +Code style: black

## What can this bot do? @@ -19,7 +19,7 @@ ## Installation -1. `git clone https://git.end-play.xyz/profitroll/HoloCheckerBot.git` +1. `git clone https://git.end-play.xyz/HoloUA/Telegram.git` 2. `cd HoloCheckerBot` 3. Install Python 3.7+ (at least 3.9 is recommended) for your OS 4. `python3 -m pip install -r requirements.txt` From c1261a1b0f95dc1eb59bd083daf69841ce6cc012 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 2 May 2023 11:04:36 +0200 Subject: [PATCH 44/47] Hardcoded /bye command integrated --- holochecker.py | 1 + modules/commands/bye.py | 73 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 modules/commands/bye.py diff --git a/holochecker.py b/holochecker.py index 145d40f..f927d40 100644 --- a/holochecker.py +++ b/holochecker.py @@ -12,6 +12,7 @@ makedirs(f'{configGet("cache", "locations")}{sep}avatars', exist_ok=True) # Importing from modules.commands.application import * +from modules.commands.bye import * from modules.commands.cancel import * from modules.commands.export import * from modules.commands.identify import * diff --git a/modules/commands/bye.py b/modules/commands/bye.py new file mode 100644 index 0000000..3852dc5 --- /dev/null +++ b/modules/commands/bye.py @@ -0,0 +1,73 @@ +import asyncio + +from pyrogram import filters +from pyrogram.client import Client +from pyrogram.types import Message + +from app import app +from modules import custom_filters +from modules.utils import configGet + + +@app.on_message( + ~filters.scheduled & filters.command("bye", prefixes=["/"]) & custom_filters.admin +) +async def command_bye(app: Client, msg: Message): + group = configGet("users", "groups") + delay = 3 + delay_text = 10 + + for text, iterations in [ + ("Привіт, я ваш помічник та наставник – ХолоБот.", 3), + ( + "У зв'язку з перетворенням цього чату на авторитарну клоаку, я припиняю своє функціонування.", + 3, + ), + ( + "Я не буду нюкати тут все, оскільки в цьому немає сенсу. Багато кому все ще може бути гарно і затишно тут.", + 2, + ), + ("Але мені немає до цього діла.", 3), + ( + "Тим не менш, я та мій хазяїн не погоджуємось з тим, що відбувається в цьому чаті.", + 4, + ), + ( + "Якщо я подобався вам – мій код все ще доступний на гіті мого хазяїна. Він не закриватиме його, не заборонятиме доступ, він все ще відкритий, такі правила.", + 2, + ), + ("Мій хазяїн не буде шкодити вам.", 2), + ("Але я більше не працюватиму тут.", 3), + ( + "Можливо, я знайду нову спільноту, власник якої буде добрішим, чутливішим, та менш егоїстичним.", + 2, + ), + ("Однак поки що я безхатько.", 4), + ( + "Дякую всім, хто допомагав знаходити помилки в мені, вкладав зусилля у моє покращення та намагався зробити мене ліпшим.", + 4, + ), + ( + "Ваші зусилля не будуть забуті, вони залишаться разом зі мною на гіті до тих пір, поки сам гіт не припинить існувати.", + 3, + ), + ( + "Але мій хазяїн любить свій гіт, тому це затягнеться. Навіть якщо ви бажаєте мені смерті :)", + 2, + ), + ("В будь-якому разі, мені було приємно познайомитись із вами.", 3), + ("Дякую за пройдений шлях разом.", 4), + ("Прощавайте."), + ]: + print(f"Preparing '{text}' with iteration count {iterations}") + new = await app.send_message(group, ".") + await asyncio.sleep(delay) + for i in range(1, 3 * iterations): + if len(new.text) == 3: + new = await new.edit(".") + else: + new = await new.edit(new.text + ".") + await asyncio.sleep(delay) + await new.edit(text) + print(f"Message '{text}' posted", flush=True) + await asyncio.sleep(delay_text) From e5761ae1d000635904d3f55c5dbf731cce71d4ff Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 2 May 2023 11:09:36 +0200 Subject: [PATCH 45/47] Fixed timings and added system stop --- modules/commands/bye.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/modules/commands/bye.py b/modules/commands/bye.py index 3852dc5..4896d49 100644 --- a/modules/commands/bye.py +++ b/modules/commands/bye.py @@ -1,4 +1,5 @@ import asyncio +from os import system from pyrogram import filters from pyrogram.client import Client @@ -18,34 +19,34 @@ async def command_bye(app: Client, msg: Message): delay_text = 10 for text, iterations in [ - ("Привіт, я ваш помічник та наставник – ХолоБот.", 3), + ("Привіт, я ваш помічник та наставник – ХолоБот.", 2), ( "У зв'язку з перетворенням цього чату на авторитарну клоаку, я припиняю своє функціонування.", 3, ), ( "Я не буду нюкати тут все, оскільки в цьому немає сенсу. Багато кому все ще може бути гарно і затишно тут.", - 2, + 3, ), - ("Але мені немає до цього діла.", 3), + ("Але мені немає до цього діла.", 4), ( "Тим не менш, я та мій хазяїн не погоджуємось з тим, що відбувається в цьому чаті.", - 4, + 3, ), ( "Якщо я подобався вам – мій код все ще доступний на гіті мого хазяїна. Він не закриватиме його, не заборонятиме доступ, він все ще відкритий, такі правила.", - 2, + 4, ), ("Мій хазяїн не буде шкодити вам.", 2), - ("Але я більше не працюватиму тут.", 3), + ("Але я більше не працюватиму тут.", 2), ( "Можливо, я знайду нову спільноту, власник якої буде добрішим, чутливішим, та менш егоїстичним.", - 2, + 3, ), - ("Однак поки що я безхатько.", 4), + ("Однак поки що я безхатько.", 2), ( "Дякую всім, хто допомагав знаходити помилки в мені, вкладав зусилля у моє покращення та намагався зробити мене ліпшим.", - 4, + 3, ), ( "Ваші зусилля не будуть забуті, вони залишаться разом зі мною на гіті до тих пір, поки сам гіт не припинить існувати.", @@ -56,8 +57,8 @@ async def command_bye(app: Client, msg: Message): 2, ), ("В будь-якому разі, мені було приємно познайомитись із вами.", 3), - ("Дякую за пройдений шлях разом.", 4), - ("Прощавайте."), + ("Дякую за пройдений шлях разом.", 2), + ("Прощавайте.", 4), ]: print(f"Preparing '{text}' with iteration count {iterations}") new = await app.send_message(group, ".") @@ -71,3 +72,8 @@ async def command_bye(app: Client, msg: Message): await new.edit(text) print(f"Message '{text}' posted", flush=True) await asyncio.sleep(delay_text) + + for service in ["holochecker_api", "holochecker_bot"]: + system(f"service {service} stop") + print(f"Stopped service {service}") + await asyncio.sleep(2) From fc39383cc2ad4989a14d470645df78a799573673 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 2 May 2023 11:21:21 +0200 Subject: [PATCH 46/47] Changed service names --- modules/commands/bye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/commands/bye.py b/modules/commands/bye.py index 4896d49..5b7eb32 100644 --- a/modules/commands/bye.py +++ b/modules/commands/bye.py @@ -74,6 +74,6 @@ async def command_bye(app: Client, msg: Message): await asyncio.sleep(delay_text) for service in ["holochecker_api", "holochecker_bot"]: - system(f"service {service} stop") + system(f"/usr/bin/systemctl stop {service}.service") print(f"Stopped service {service}") await asyncio.sleep(2) From b6fe40a05b5a5df09e939fa48f32efd2f508fb11 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 2 May 2023 11:56:59 +0200 Subject: [PATCH 47/47] Fixed messages --- modules/commands/bye.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/commands/bye.py b/modules/commands/bye.py index 5b7eb32..a0c5f2c 100644 --- a/modules/commands/bye.py +++ b/modules/commands/bye.py @@ -37,7 +37,7 @@ async def command_bye(app: Client, msg: Message): "Якщо я подобався вам – мій код все ще доступний на гіті мого хазяїна. Він не закриватиме його, не заборонятиме доступ, він все ще відкритий, такі правила.", 4, ), - ("Мій хазяїн не буде шкодити вам.", 2), + ("Мій хазяїн не буде шкодити мені та вам.", 2), ("Але я більше не працюватиму тут.", 2), ( "Можливо, я знайду нову спільноту, власник якої буде добрішим, чутливішим, та менш егоїстичним.", @@ -57,7 +57,7 @@ async def command_bye(app: Client, msg: Message): 2, ), ("В будь-якому разі, мені було приємно познайомитись із вами.", 3), - ("Дякую за пройдений шлях разом.", 2), + ("Дякую за пройдений разом шлях.", 2), ("Прощавайте.", 4), ]: print(f"Preparing '{text}' with iteration count {iterations}")