From 30b48c12f339d2082f4485b48bb310d147582e34 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 28 Aug 2023 15:41:20 +0200 Subject: [PATCH 1/4] /setup command implemented --- classes/pyroclient.py | 5 +- config_example.json | 3 + modules/search_name.py | 73 ++++++++++++++++++++++++ modules/search_nearby.py | 62 ++++++++++++++++++++ plugins/commands/setup.py | 115 ++++++++++++++------------------------ 5 files changed, 182 insertions(+), 76 deletions(-) create mode 100644 modules/search_name.py create mode 100644 modules/search_nearby.py diff --git a/classes/pyroclient.py b/classes/pyroclient.py index b99a9ea..f4b5ebc 100644 --- a/classes/pyroclient.py +++ b/classes/pyroclient.py @@ -2,7 +2,7 @@ from typing import List, Union from apscheduler.triggers.cron import CronTrigger from libbot.pyrogram.classes import PyroClient as LibPyroClient -from pymongo import ASCENDING, GEO2D +from pymongo import ASCENDING, GEOSPHERE, TEXT from pyrogram.types import User from classes.location import Location @@ -24,9 +24,10 @@ class PyroClient(LibPyroClient): [("id", ASCENDING)], name="location_id", unique=True ) await col_locations.create_index( - [("location", GEO2D)], + [("location", GEOSPHERE)], name="location_location", ) + await col_locations.create_index([("name", TEXT)], name="location_name") return await super().start(**kwargs) async def find_user(self, user: Union[int, User]) -> PyroUser: diff --git a/config_example.json b/config_example.json index a8b98b5..25a463b 100644 --- a/config_example.json +++ b/config_example.json @@ -15,6 +15,9 @@ "port": 27017, "name": "garbagebot" }, + "search": { + "radius": 0.1 + }, "reports": { "chat_id": "owner" }, diff --git a/modules/search_name.py b/modules/search_name.py new file mode 100644 index 0000000..527daa4 --- /dev/null +++ b/modules/search_name.py @@ -0,0 +1,73 @@ +from typing import Union + +from convopyro import listen_message +from pykeyboard import ReplyButton, ReplyKeyboard +from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove + +from classes.location import Location +from classes.pyroclient import PyroClient +from modules.database import col_locations + + +async def search_name(app: PyroClient, message: Message) -> Union[Location, None]: + location: Union[Location, None] = None + + await message.reply_text( + "Please, send me a location name. It should be the name used in your local authorities' garbage collection schedule. This usually is a name of the district or even the town itself.", + reply_markup=ForceReply(placeholder="Location name"), + ) + + while location is None: + answer = await listen_message(app, message.chat.id, 300) + + if answer is None or answer.text == "/cancel": + await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + return + + if answer.text is None: + await message.reply_text( + "Please, send the name of the location as a text. You can also abort this operation with /cancel command.", + reply_markup=ForceReply(placeholder="Location name"), + ) + continue + + query = {"$text": {"$search": answer.text}} + + locations = await col_locations.find(query).limit(6).to_list() + + if len(locations) == 0: + await message.reply_text( + "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule. You can also abort this operation with /cancel command.", + reply_markup=ForceReply(placeholder="Location name"), + ) + continue + + keyboard = ReplyKeyboard(resize_keyboard=True, row_width=2) + keyboard.add(*[ReplyButton(db_record["name"]) for db_record in locations]) + + await message.reply_text( + "Select the location using the keyboard", reply_markup=keyboard + ) + + while True: + answer = await listen_message(app, message.chat.id, 300) + + if answer is None or answer.text == "/cancel": + await message.reply_text( + "Cancelled.", reply_markup=ReplyKeyboardRemove() + ) + return + + for db_record in locations: + if answer.text == db_record["name"]: + location = Location(**db_record) + + if answer.text is None or location is None: + await answer.reply_text( + "Please, select a valid location using keyboard provided. Use /cancel if you want to cancel this operation.", + ) + continue + + break + + return location diff --git a/modules/search_nearby.py b/modules/search_nearby.py new file mode 100644 index 0000000..1e5f49d --- /dev/null +++ b/modules/search_nearby.py @@ -0,0 +1,62 @@ +from typing import Union + +from bson.son import SON +from convopyro import listen_message +from pykeyboard import ReplyButton, ReplyKeyboard +from pyrogram.types import Message, ReplyKeyboardRemove + +from classes.location import Location +from classes.pyroclient import PyroClient +from modules.database import col_locations +from modules.search_name import search_name + + +async def search_nearby(app: PyroClient, message: Message) -> Union[Location, None]: + query = { + "location": { + "$within": { + "$center": [ + [message.location.longitude, message.location.latitude], + app.config["search"]["radius"], + ] + } + } + } + + locations = await col_locations.find(query).limit(6).to_list() + + if len(locations) == 0: + await message.reply_text( + "Could not find any locations nearby. Let's try using the name search." + ) + return await search_name(app, message) + + keyboard = ReplyKeyboard(resize_keyboard=True, row_width=2) + keyboard.add(*[ReplyButton(db_record["name"]) for db_record in locations]) + + await message.reply_text( + "Select the location using the keyboard", reply_markup=keyboard + ) + + while True: + answer = await listen_message(app, message.chat.id, 300) + location: Union[Location, None] = None + + if answer is None or answer.text == "/cancel": + await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + return + + for db_record in locations: + if answer.text == db_record["name"]: + location = Location(**db_record) + break + + if answer.text is None or location is None: + await answer.reply_text( + "Please, select a valid location using keyboard provided. Use /cancel if you want to cancel this operation." + ) + continue + + break + + return location or await search_name(app, message) diff --git a/plugins/commands/setup.py b/plugins/commands/setup.py index b8cfcbc..0754296 100644 --- a/plugins/commands/setup.py +++ b/plugins/commands/setup.py @@ -1,4 +1,5 @@ import logging +from datetime import datetime from convopyro import listen_message from libbot import i18n @@ -7,6 +8,8 @@ from pyrogram import filters from pyrogram.types import Message, ReplyKeyboardRemove from classes.pyroclient import PyroClient +from modules.search_name import search_name +from modules.search_nearby import search_nearby logger = logging.getLogger(__name__) @@ -17,88 +20,52 @@ logger = logging.getLogger(__name__) async def command_setup(app: PyroClient, message: Message): user = await app.find_user(message.from_user) - await message.reply_text( - "Holy... This one is still WIP...", reply_markup=ReplyKeyboardRemove() + keyboard_type = ReplyKeyboard(resize_keyboard=True, row_width=1) + keyboard_type.add( + ReplyButton("Search nearby locations", request_location=True), + ReplyButton("Search by location name"), ) - # # City selection - # city_names = [city_iter.name for city_iter in await app.get_cities()] - # keyboard_cities = ReplyKeyboard(resize_keyboard=True, row_width=2) - # keyboard_cities.add(*[ReplyButton(name) for name in city_names]) + await message.reply_text( + "Let's begin configuration with the search for your location. Please, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send to the bot will **NOT** be saved anywhere and is only used for location lookup in the database.", + reply_markup=keyboard_type, + ) - # await message.reply_text( - # "Alright. Please, use the keyboard provided to choose your town.", - # reply_markup=keyboard_cities, - # ) + while True: + answer_type = await listen_message(app, message.chat.id, 300) - # while True: - # answer_city = await listen_message(app, message.chat.id, 300) + if answer_type is None or answer_type.text == "/cancel": + await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + return - # if answer_city is None or answer_city.text == "/cancel": - # await message.reply_text("Cancelled.") - # return + if answer_type.location is None and answer_type.text not in [ + "Search by location name", + ]: + await answer_type.reply_text( + "Please, select a valid option using keyboard provided. Use /cancel if you want to cancel this operation." + ) + continue - # if answer_city.text not in city_names: - # await answer_city.reply_text( - # "Please, select a valid town using keyboard provided. Use /cancel if you want to cancel this operation." - # ) - # continue + break - # break + location = ( + await search_name(app, answer_type) + if answer_type.location is None + else await search_nearby(app, answer_type) + ) - # # City recognition - # city = await app.find_city(name=answer_city.text) + if location is None: + await answer_type.reply_text( + "If you want try selecting the location again, use the /setup command.", + reply_markup=ReplyKeyboardRemove(), + ) + return - # # District selection - # district_names = [district_iter.name for district_iter in city.districts] - # keyboard_districts = ReplyKeyboard(resize_keyboard=True, row_width=2) - # keyboard_districts.add(*[ReplyButton(name) for name in district_names]) + await user.update_location(location.id) - # await message.reply_text( - # "Alright. Please, use the keyboard provided to choose your district.", - # reply_markup=keyboard_districts, - # ) + user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute) - # while True: - # answer_district = await listen_message(app, message.chat.id, 300) - - # if answer_district is None or answer_district.text == "/cancel": - # await message.reply_text("Cancelled.") - # return - - # if answer_district.text not in district_names: - # await answer_district.reply_text( - # "Please, select a valid district using keyboard provided. Use /cancel if you want to cancel this operation." - # ) - # continue - - # break - - # # District recognition - # district_results = city.find_district(answer_district.text) - - # if len(district_results) == 0: - # await answer_district.reply_text( - # "Something went wrong. Could not find this district in the database.", - # reply_markup=ReplyKeyboardRemove(), - # ) - # return - - # district = district_results[0] - - # await user.update_city(city.id) - # await user.update_district(district.id) - - # logger.info( - # "User %s has finished the location set up with city %s and district %s selected", - # user.id, - # city.id, - # district.id, - # ) - - # notice = "" if user.enabled else "Execute /toggle to enable notifications." - - # await answer_district.reply_text( - # f"All set! You will now receive notification about garbage collection in district **{district.name}** of the town **{city.name}**. {notice}", - # reply_markup=ReplyKeyboardRemove(), - # ) + await message.reply_text( + f"You will now receive the notifications for **{location.name}** at {user_time.strftime(app._('time', 'formats', locale=user.locale))}, {user.offset} d. before collection.", + reply_markup=ReplyKeyboardRemove(), + ) From 029a965860025500a546319a7912e49c1aadc504 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 28 Aug 2023 16:06:15 +0200 Subject: [PATCH 2/4] Slight locale changes --- locale/en.json | 5 +++-- locale/uk.json | 5 +++-- modules/search_name.py | 2 +- plugins/commands/help.py | 4 +++- plugins/commands/setup.py | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/locale/en.json b/locale/en.json index 20c570d..519ed52 100644 --- a/locale/en.json +++ b/locale/en.json @@ -33,7 +33,7 @@ "remove_commands": "Unregister all commands" }, "messages": { - "help": "Help message here, lol.", + "help": "This bot sends you the notifications about garbage collection according to your local schedule.\n\n**Available commands**\n/help - Show this message\n/setup - Select the location\n/toggle - Disable/enable the reminders\n/set_time - Set the reminders' time\n/set_offset - Set offset between reminders and collection\n/upcoming - Show the upcoming collection\n/language - Select the bot's language\n/checkout - Export or remove your data\n\nYou can also suggest adding your town/district to the bot by contacting the admins using [this link]({url_contact}) and providing your schedule.\n\nWant to host this bot yourself or make some changes? It's open-source, so you can basically fork it. Take a look at [bot's repository]({url_repo}) for details.\n\nHappy using!", "start": "👋 Welcome!\n\nThis small open-source bot is made to simplify your life a bit easier by sending you notifications about upcoming garbage collection in your location.\n\nBy using this bot you accept [Privacy Policy]({privacy_policy}), otherwise please block and remove this bot before further interaction.\n\nNow the official part is over so you can dive into the bot.", "locale_choice": "Alright. Please choose the language using keyboard below." }, @@ -42,5 +42,6 @@ }, "callbacks": { "locale_set": "Your language now is: {locale}" - } + }, + "force_replies": {} } \ No newline at end of file diff --git a/locale/uk.json b/locale/uk.json index f61a429..718bed6 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -34,7 +34,7 @@ }, "messages": { "help": "Привіт! Я твій бот!", - "start": "Hi! By using this bot you accept **terms of service** and **privacy policy**, otherwise please block and remove this bot before further interaction.", + "start": "👋 Вітання!\n\nЦей невеличкий бот з відкритим вихідним кодом створений для того, щоб трохи спростити Вам життя, надсилаючи сповіщення про вивіз сміття у вашому регіоні.\n\nКористуючись цим ботом, Ви приймаєте [Політику конфіденційності]({privacy_policy}), в іншому випадку, будь ласка, заблокуйте та видаліть цього бота перед подальшою взаємодією.\n\nТепер офіційна частина закінчена, тож Ви можете зануритися в бота.", "locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче." }, "buttons": { @@ -42,5 +42,6 @@ }, "callbacks": { "locale_set": "Встановлено мову: {locale}" - } + }, + "force_replies": {} } \ No newline at end of file diff --git a/modules/search_name.py b/modules/search_name.py index 527daa4..8f902cd 100644 --- a/modules/search_name.py +++ b/modules/search_name.py @@ -37,7 +37,7 @@ async def search_name(app: PyroClient, message: Message) -> Union[Location, None if len(locations) == 0: await message.reply_text( - "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule. You can also abort this operation with /cancel command.", + "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule.\n\nYou can also abort this operation with /cancel command.", reply_markup=ForceReply(placeholder="Location name"), ) continue diff --git a/plugins/commands/help.py b/plugins/commands/help.py index 2864ec2..5cdf133 100644 --- a/plugins/commands/help.py +++ b/plugins/commands/help.py @@ -10,4 +10,6 @@ from classes.pyroclient import PyroClient async def command_help(app: PyroClient, message: Message): user = await app.find_user(message.from_user) - await message.reply_text(app._("help", "messages", locale=user.locale)) + await message.reply_text( + app._("help", "messages", locale=user.locale), disable_web_page_preview=True + ) diff --git a/plugins/commands/setup.py b/plugins/commands/setup.py index 0754296..1873ed2 100644 --- a/plugins/commands/setup.py +++ b/plugins/commands/setup.py @@ -27,7 +27,7 @@ async def command_setup(app: PyroClient, message: Message): ) await message.reply_text( - "Let's begin configuration with the search for your location. Please, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send to the bot will **NOT** be saved anywhere and is only used for location lookup in the database.", + "Let's begin configuration with the search for your location.\n\nPlease, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send will **NOT** be saved anywhere and is only used for location lookup in the database.", reply_markup=keyboard_type, ) @@ -66,6 +66,6 @@ async def command_setup(app: PyroClient, message: Message): user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute) await message.reply_text( - f"You will now receive the notifications for **{location.name}** at {user_time.strftime(app._('time', 'formats', locale=user.locale))}, {user.offset} d. before collection.", + f"✅ Finished! Your location is now **{location.name}**. You will receive the notifications about garbage collection {user.offset} d. in advance at {user_time.strftime(app._('time', 'formats', locale=user.locale))}.", reply_markup=ReplyKeyboardRemove(), ) From 9cda8859da296755eca656f5d50f482f3b1a1b89 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 29 Aug 2023 16:32:37 +0200 Subject: [PATCH 3/4] WIP: Locale strings --- config_example.json | 6 ++++- locale/en.json | 44 ++++++++++++++++++++++++++++--- modules/reminder.py | 4 +-- modules/search_name.py | 43 +++++++++++++++++++++++------- modules/search_nearby.py | 17 ++++++++---- plugins/commands/checkout.py | 42 +++++++++++++++-------------- plugins/commands/help.py | 6 ++++- plugins/commands/import.py | 36 +++++++++++++++++++------ plugins/commands/set_offset.py | 5 +++- plugins/commands/set_time.py | 5 +++- plugins/commands/setup.py | 9 +++++-- plugins/commands/start.py | 48 ++++++++++++++++++++++------------ plugins/commands/toggle.py | 17 +++++++++--- plugins/commands/upcoming.py | 10 ++++--- 14 files changed, 214 insertions(+), 78 deletions(-) diff --git a/config_example.json b/config_example.json index 25a463b..40c264a 100644 --- a/config_example.json +++ b/config_example.json @@ -21,5 +21,9 @@ "reports": { "chat_id": "owner" }, - "disabled_plugins": [] + "disabled_plugins": [], + "strings": { + "url_repo": "https://git.end-play.xyz/GarbageReminder/TelegramBot", + "url_contact": "https://git.end-play.xyz/GarbageReminder/TelegramBot/issues" + } } \ No newline at end of file diff --git a/locale/en.json b/locale/en.json index 519ed52..9740982 100644 --- a/locale/en.json +++ b/locale/en.json @@ -33,15 +33,51 @@ "remove_commands": "Unregister all commands" }, "messages": { - "help": "This bot sends you the notifications about garbage collection according to your local schedule.\n\n**Available commands**\n/help - Show this message\n/setup - Select the location\n/toggle - Disable/enable the reminders\n/set_time - Set the reminders' time\n/set_offset - Set offset between reminders and collection\n/upcoming - Show the upcoming collection\n/language - Select the bot's language\n/checkout - Export or remove your data\n\nYou can also suggest adding your town/district to the bot by contacting the admins using [this link]({url_contact}) and providing your schedule.\n\nWant to host this bot yourself or make some changes? It's open-source, so you can basically fork it. Take a look at [bot's repository]({url_repo}) for details.\n\nHappy using!", + "cancel": "Use /cancel if you want to cancel this operation.", + "cancelled": "Operation has been cancelled.", + "help": "🔔 This bot sends you notifications about garbage collection according to your local schedule.\n\n**Available commands**\n/help - Show this message\n/setup - Select the location\n/toggle - Disable/enable the reminders\n/set_time - Set the reminders' time\n/set_offset - Set offset between reminders and collection\n/upcoming - Show the upcoming collection\n/language - Select the bot's language\n/checkout - Export or remove your data\n\n💭 You can also suggest adding your town/district to the bot by contacting the admins using [this link]({url_contact}) and providing your schedule.\n\n⚙️ Want to host this bot yourself or make some changes? It's open-source, so you can basically fork it. Take a look at [bot's repository]({url_repo}) for details.\n\nHappy using! 🤗", + "import_finished": "You have successfully inserted {count} entries.", + "import_invalid_date": "Entries contain invalid date formats. Use **ISO 8601** date format.", + "import_invalid_filetype": "Invalid input. Please, send me a JSON file with entries. {cancel_notice}", + "import_invalid": "This is not a valid garbage collection JSON.", + "import": "Alright. Send me a valid JSON.", + "locale_choice": "Alright. Please choose the language using keyboard below.", + "start_code_invalid": "🚫 You have started the bot by the link containing a location, but it does not seem to be a valid one. Please, use the command /setup to manually configure the location.", + "start_code": "ℹ️ You have started the bot by the link containing a location **{name}**.\n\nPlease, confirm whether you want to use it as your location.", + "start_configure": "📍 Let's configure your location. Press the button on pop-up keyboard to start the process.", + "selection_invalid": "Please, select a valid option using the keyboard provided. {cancel_notice}", + "start_selection_no": "Alright, you're on your own now. Please, use the command /setup to configure your location and start receiving reminders.", + "start_selection_yes": "✅ Finished! Your location is now **{name}**. You will receive reminders about garbage collection {offset} d. in advance at {time}.\n\nPlease, visit /help if you want to know how to change notifications time or disable them.", "start": "👋 Welcome!\n\nThis small open-source bot is made to simplify your life a bit easier by sending you notifications about upcoming garbage collection in your location.\n\nBy using this bot you accept [Privacy Policy]({privacy_policy}), otherwise please block and remove this bot before further interaction.\n\nNow the official part is over so you can dive into the bot.", - "locale_choice": "Alright. Please choose the language using keyboard below." + "checkout_deleted": "🗑️ Your data has been deleted. If you want to start using this bot again, please use /setup command. Otherwise delete/block the bot and do not interact with it anymore.", + "checkout": "Here's pretty much all the data bot has. Please, use these buttons to choose whether you want to delete your data from the bot.", + "checkout_deletion": "Alright. Please, confirm that you want to delete your data from the bot.\n\nFollowing data will be deleted:\n• Selected location\n• Preferred language of all messages\n• Time of the notifications\n• Offset of the notifications\n\nUse keyboard provided to confirm and continue or /cancel to abort this operation.", + "toggle_disabled": "🔕 Notifications have been disabled.", + "toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.", + "toggle_enabled_location": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time} at the **{name}**.", + "upcoming_empty_location": "You have no location set. Use /setup to select your location.", + "upcoming_empty": "No garbage collection entries found for the next 30 days at **{name}**", + "upcoming": "Upcoming garbage collection:\n\n{entries}", + "reminder": "**Garbage Collection**\n\nType: {type}\nDate: {date}\n\nDon't forget to prepare your bin for collection!", + "search_nearby_empty": "Could not find any locations nearby. Let's try using the name search.", + "location_select": "Select the location using the keyboard provided.", + "location_name": "Please, send me a location name. It should be the name used in your local authorities' garbage collection schedule. This usually is a name of the district or even the town itself.", + "location_name_invalid": "Please, send the name of the location as a text. {cancel_notice}", + "location_name_empty": "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule.\n\n{cancel_notice}" }, "buttons": { - "configure": "Let's configure the bot" + "start_configure": "⚙️ Let's configure the bot", + "start_code_yes": "✅ Yes, I want to use it", + "start_code_no": "❌ No, I don't want to use it", + "delete_yes": "✅ Yes, I want to delete it", + "delete_no": "❌ No, I don't want to delete it", + "delete_confirm": "I agree and want to proceed" }, "callbacks": { "locale_set": "Your language now is: {locale}" }, - "force_replies": {} + "force_replies": { + "import": "JSON with garbage entries", + "location_name": "Location name" + } } \ No newline at end of file diff --git a/modules/reminder.py b/modules/reminder.py index 05d084c..b845303 100644 --- a/modules/reminder.py +++ b/modules/reminder.py @@ -22,8 +22,6 @@ async def remind(app: PyroClient) -> None: for user_db in users: user = PyroUser(**user_db) - logger.info("Processing %s...", user.id) - if not user.enabled or user.location is None: continue @@ -61,7 +59,7 @@ async def remind(app: PyroClient) -> None: await app.send_message( user.id, - "**Garbage Collection**\n\nType: {type}\nDate: {date}\n\nDon't forget to prepare your bin for collection!".format( + app._("reminder", "messages", locale=user.locale).format( type=garbage_type, date=garbage_date ), ) diff --git a/modules/search_name.py b/modules/search_name.py index 8f902cd..8d497be 100644 --- a/modules/search_name.py +++ b/modules/search_name.py @@ -10,24 +10,37 @@ from modules.database import col_locations async def search_name(app: PyroClient, message: Message) -> Union[Location, None]: + user = await app.find_user(message.from_user) + location: Union[Location, None] = None await message.reply_text( - "Please, send me a location name. It should be the name used in your local authorities' garbage collection schedule. This usually is a name of the district or even the town itself.", - reply_markup=ForceReply(placeholder="Location name"), + app._("location_request_name", "messages", locale=user.locale), + reply_markup=ForceReply( + placeholder=app._("location_name", "force_replies", locale=user.locale) + ), ) while location is None: answer = await listen_message(app, message.chat.id, 300) if answer is None or answer.text == "/cancel": - await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + await message.reply_text( + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return if answer.text is None: await message.reply_text( - "Please, send the name of the location as a text. You can also abort this operation with /cancel command.", - reply_markup=ForceReply(placeholder="Location name"), + app._("location_name_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ), + reply_markup=ForceReply( + placeholder=app._( + "location_name", "force_replies", locale=user.locale + ) + ), ) continue @@ -37,8 +50,14 @@ async def search_name(app: PyroClient, message: Message) -> Union[Location, None if len(locations) == 0: await message.reply_text( - "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule.\n\nYou can also abort this operation with /cancel command.", - reply_markup=ForceReply(placeholder="Location name"), + app._("location_name_empty", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ), + reply_markup=ForceReply( + placeholder=app._( + "location_name", "force_replies", locale=user.locale + ) + ), ) continue @@ -46,7 +65,8 @@ async def search_name(app: PyroClient, message: Message) -> Union[Location, None keyboard.add(*[ReplyButton(db_record["name"]) for db_record in locations]) await message.reply_text( - "Select the location using the keyboard", reply_markup=keyboard + app._("location_select", "messages", locale=user.locale), + reply_markup=keyboard, ) while True: @@ -54,7 +74,8 @@ async def search_name(app: PyroClient, message: Message) -> Union[Location, None if answer is None or answer.text == "/cancel": await message.reply_text( - "Cancelled.", reply_markup=ReplyKeyboardRemove() + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), ) return @@ -64,7 +85,9 @@ async def search_name(app: PyroClient, message: Message) -> Union[Location, None if answer.text is None or location is None: await answer.reply_text( - "Please, select a valid location using keyboard provided. Use /cancel if you want to cancel this operation.", + app._("selection_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue diff --git a/modules/search_nearby.py b/modules/search_nearby.py index 1e5f49d..be94d8e 100644 --- a/modules/search_nearby.py +++ b/modules/search_nearby.py @@ -1,6 +1,5 @@ from typing import Union -from bson.son import SON from convopyro import listen_message from pykeyboard import ReplyButton, ReplyKeyboard from pyrogram.types import Message, ReplyKeyboardRemove @@ -12,6 +11,8 @@ from modules.search_name import search_name async def search_nearby(app: PyroClient, message: Message) -> Union[Location, None]: + user = await app.find_user(message.from_user) + query = { "location": { "$within": { @@ -27,7 +28,7 @@ async def search_nearby(app: PyroClient, message: Message) -> Union[Location, No if len(locations) == 0: await message.reply_text( - "Could not find any locations nearby. Let's try using the name search." + app._("search_nearby_empty", "messages", locale=user.locale) ) return await search_name(app, message) @@ -35,7 +36,8 @@ async def search_nearby(app: PyroClient, message: Message) -> Union[Location, No keyboard.add(*[ReplyButton(db_record["name"]) for db_record in locations]) await message.reply_text( - "Select the location using the keyboard", reply_markup=keyboard + app._("location_select", "messages", locale=user.locale), + reply_markup=keyboard, ) while True: @@ -43,7 +45,10 @@ async def search_nearby(app: PyroClient, message: Message) -> Union[Location, No location: Union[Location, None] = None if answer is None or answer.text == "/cancel": - await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + await message.reply_text( + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return for db_record in locations: @@ -53,7 +58,9 @@ async def search_nearby(app: PyroClient, message: Message) -> Union[Location, No if answer.text is None or location is None: await answer.reply_text( - "Please, select a valid location using keyboard provided. Use /cancel if you want to cancel this operation." + app._("selection_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue diff --git a/plugins/commands/checkout.py b/plugins/commands/checkout.py index e7ff747..e5d2528 100644 --- a/plugins/commands/checkout.py +++ b/plugins/commands/checkout.py @@ -32,12 +32,12 @@ async def command_checkout(app: PyroClient, message: Message): row_width=1, resize_keyboard=True, one_time_keyboard=True ) keyboard_delete.add( - ReplyButton("Yes, I want to delete it"), - ReplyButton("No, I don't want to delete it"), + ReplyButton(app._("delete_yes", "buttons", locale=user.locale)), + ReplyButton(app._("delete_no", "buttons", locale=user.locale)), ) await message.reply_text( - "Here's pretty much all the data bot has. Please, use these buttons to choose whether you want to delete your data from the bot.", + app._("checkout", "messages", locale=user.locale), reply_markup=keyboard_delete, ) @@ -46,25 +46,25 @@ async def command_checkout(app: PyroClient, message: Message): if answer_delete is None or answer_delete.text == "/cancel": await message.reply_text( - "Cancelled.", + app._("cancelled", "messages", locale=user.locale), reply_markup=ReplyKeyboardRemove(), ) return - if answer_delete.text not in [ - "Yes, I want to delete it", - "No, I don't want to delete it", - ]: + if answer_delete.text not in app._( + "delete_yes", "buttons", locale=user.locale + ) + app._("delete_no", "buttons", locale=user.locale): await answer_delete.reply_text( - "Invalid answer provided. Use /cancel if you want to cancel this operation." + app._("selection_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue - if answer_delete.text in [ - "No, I don't want to delete it", - ]: + if answer_delete.text in app._("delete_no", "buttons", locale=user.locale): await answer_delete.reply_text( - "Alright, cancelled.", reply_markup=ReplyKeyboardRemove() + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), ) return @@ -74,10 +74,12 @@ async def command_checkout(app: PyroClient, message: Message): keyboard_confirm = ReplyKeyboard( row_width=1, resize_keyboard=True, one_time_keyboard=True ) - keyboard_confirm.add(ReplyButton("I agree and want to proceed")) + keyboard_confirm.add( + ReplyButton(app._("delete_confirm", "buttons", locale=user.locale)) + ) await message.reply_text( - "Alright. Please, confirm that you want to delete your data from the bot.\n\nFollowing data will be deleted:\nSelected location, preferred language of the messages, notifications time and your notifications offset.", + app._("checkout_deletion", "messages", locale=user.locale), reply_markup=keyboard_confirm, ) @@ -86,14 +88,16 @@ async def command_checkout(app: PyroClient, message: Message): if answer_confirm is None or answer_confirm.text == "/cancel": await message.reply_text( - "Cancelled.", + app._("cancelled", "messages", locale=user.locale), reply_markup=ReplyKeyboardRemove(), ) return - if answer_confirm.text not in ["I agree and want to proceed"]: + if answer_confirm.text not in app.in_all_locales("delete_confirm", "buttons"): await answer_confirm.reply_text( - "Invalid answer provided. Use /cancel if you want to cancel this operation." + app._("selection_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue @@ -101,6 +105,6 @@ async def command_checkout(app: PyroClient, message: Message): await user.delete() await answer_confirm.reply_text( - "Your data has been deleted. If you want to start using this bot again, please use /setup command. Otherwise delete/block the bot and do not interact with it anymore.", + app._("checkout_deleted", "messages", locale=user.locale), reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/help.py b/plugins/commands/help.py index 5cdf133..6e211e9 100644 --- a/plugins/commands/help.py +++ b/plugins/commands/help.py @@ -11,5 +11,9 @@ async def command_help(app: PyroClient, message: Message): user = await app.find_user(message.from_user) await message.reply_text( - app._("help", "messages", locale=user.locale), disable_web_page_preview=True + app._("help", "messages", locale=user.locale).format( + url_contact=app.config["strings"]["url_contact"], + url_repo=app.config["strings"]["url_repo"], + ), + disable_web_page_preview=True, ) diff --git a/plugins/commands/import.py b/plugins/commands/import.py index c87c04c..df32cb2 100644 --- a/plugins/commands/import.py +++ b/plugins/commands/import.py @@ -3,7 +3,7 @@ from typing import List, Mapping, Union from convopyro import listen_message from pyrogram import filters -from pyrogram.types import Message +from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove from ujson import loads from classes.pyroclient import PyroClient @@ -15,18 +15,28 @@ from modules.database import col_entries ~filters.scheduled & filters.private & custom_filters.owner & filters.command(["import"], prefixes=["/"]) # type: ignore ) async def command_import(app: PyroClient, message: Message): - await message.reply_text("Alright. Send me a valid JSON.") + user = await app.find_user(message.from_user) + + await message.reply_text( + app._("import", "messages", locale=user.locale), + reply_markup=ForceReply(placeholder=""), + ) while True: answer = await listen_message(app, message.chat.id, 300) if answer is None or answer.text == "/cancel": - await message.reply_text("Cancelled.") + await message.reply_text( + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return if answer.document is None or answer.document.mime_type != "application/json": await answer.reply_text( - "Invalid input. Please, send me a JSON file with entries." + app._("import_invalid_filetype", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue @@ -38,7 +48,10 @@ async def command_import(app: PyroClient, message: Message): for entry in entries: if not isinstance(entries, list): - await answer.reply_text("This is not a valid garbage collection JSON.") + await answer.reply_text( + app._("import_invalid", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return for key in ("locations", "garbage_type", "date"): @@ -47,14 +60,18 @@ async def command_import(app: PyroClient, message: Message): or (key == "garbage_type" and not isinstance(entry[key], int)) or (key == "locations" and not isinstance(entry[key], list)) ): - await answer.reply_text("This is not a valid garbage collection JSON.") + await answer.reply_text( + app._("import_invalid", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return if key == "date": try: datetime.fromisoformat(str(entry[key])) except (ValueError, TypeError): await answer.reply_text( - "Entries contain invalid date formats. Use **ISO 8601** date format." + app._("import_invalid_date", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), ) return @@ -70,5 +87,8 @@ async def command_import(app: PyroClient, message: Message): await col_entries.insert_many(entries_clean) await answer.reply_text( - f"You have successfully inserted {len(entries_clean)} entries." + app._("import_finished", "messages", locale=user.locale).format( + count=len(entries_clean) + ), + reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/set_offset.py b/plugins/commands/set_offset.py index d073027..d807758 100644 --- a/plugins/commands/set_offset.py +++ b/plugins/commands/set_offset.py @@ -25,7 +25,10 @@ async def command_set_offset(app: PyroClient, message: Message): answer = await listen_message(app, message.chat.id, 300) if answer is None or answer.text == "/cancel": - await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + await message.reply_text( + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return try: diff --git a/plugins/commands/set_time.py b/plugins/commands/set_time.py index 242db7e..84c22c9 100644 --- a/plugins/commands/set_time.py +++ b/plugins/commands/set_time.py @@ -25,7 +25,10 @@ async def command_set_time(app: PyroClient, message: Message): answer = await listen_message(app, message.chat.id, 300) if answer is None or answer.text == "/cancel": - await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + await message.reply_text( + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return try: diff --git a/plugins/commands/setup.py b/plugins/commands/setup.py index 1873ed2..6deec52 100644 --- a/plugins/commands/setup.py +++ b/plugins/commands/setup.py @@ -35,14 +35,19 @@ async def command_setup(app: PyroClient, message: Message): answer_type = await listen_message(app, message.chat.id, 300) if answer_type is None or answer_type.text == "/cancel": - await message.reply_text("Cancelled.", reply_markup=ReplyKeyboardRemove()) + await message.reply_text( + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), + ) return if answer_type.location is None and answer_type.text not in [ "Search by location name", ]: await answer_type.reply_text( - "Please, select a valid option using keyboard provided. Use /cancel if you want to cancel this operation." + app._("selection_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue diff --git a/plugins/commands/start.py b/plugins/commands/start.py index 69a09a0..cb5ee98 100644 --- a/plugins/commands/start.py +++ b/plugins/commands/start.py @@ -27,21 +27,27 @@ async def command_start(app: PyroClient, message: Message): location = await app.get_location(int(join_code)) except ValueError: await message.reply_text( - "🚫 You have provided the location but it does not seem to be a valid one. Please, use the command /setup to manually configure the location." + app._("start_code_invalid", "messages", locale=user.locale) ) return keyboard = ReplyKeyboardMarkup( [ - [KeyboardButton("Yes, I want to use it")], - [KeyboardButton("No, I don't want to use it")], + [ + KeyboardButton( + app._("start_code_yes", "buttons", locale=user.locale) + ) + ], + [KeyboardButton(app._("start_code_no", "buttons", locale=user.locale))], ], resize_keyboard=True, one_time_keyboard=True, ) await message.reply_text( - f"ℹ️ You have started the bot by the link containing a location **{location.name}**.\n\nPlease, confirm whether you want to use it as your location.", + app._("start_code", "messages", locale=user.locale).format( + name=location.name + ), reply_markup=keyboard, ) @@ -50,24 +56,24 @@ async def command_start(app: PyroClient, message: Message): if answer is None or answer.text == "/cancel": await message.reply_text( - "Cancelled.", reply_markup=ReplyKeyboardRemove() + app._("cancelled", "messages", locale=user.locale), + reply_markup=ReplyKeyboardRemove(), ) return - if answer.text not in [ - "Yes, I want to use it", - "No, I don't want to use it", - ]: + if answer.text not in app.in_all_locales( + "start_code_yes", "buttons" + ) + app.in_all_locales("start_code_no", "buttons"): await answer.reply_text( - "Please, select a valid location using keyboard provided. Use /cancel if you want to cancel this operation." + app._("selection_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue - if answer.text in [ - "No, I don't want to use it", - ]: + if answer.text in app.in_all_locales("start_code_no", "buttons"): await answer.reply_text( - "Alright, you're on your own now. Please, use the command /setup to configure your location and start receiving reminders.", + app._("start_selection_no", "messages", locale=user.locale), reply_markup=ReplyKeyboardRemove(), ) return @@ -80,16 +86,24 @@ async def command_start(app: PyroClient, message: Message): app._("time", "formats", locale=user.locale) ) await answer.reply_text( - f"✅ Finished! Your location is now **{location.name}**. You will receive reminders about garbage collection {user.offset} d. in advance at {user_time}.\n\nPlease, visit /help if you want to know how to change notifications time or disable them.", + app._("start_selection_yes", "messages", locale=user.locale).format( + name=location.name, offset=user.offset, time=user_time + ), reply_markup=ReplyKeyboardRemove(), ) return if user.location is None: await message.reply_text( - "📍 Let's configure your location. Press the button on pop-up keyboard to start the process.", + app._("start_configure", "messages", locale=user.locale), reply_markup=ReplyKeyboardMarkup( - [[KeyboardButton(app._("configure", "buttons", locale=user.locale))]], + [ + [ + KeyboardButton( + app._("start_configure", "buttons", locale=user.locale) + ) + ] + ], resize_keyboard=True, one_time_keyboard=True, ), diff --git a/plugins/commands/toggle.py b/plugins/commands/toggle.py index 5f43bec..91263e0 100644 --- a/plugins/commands/toggle.py +++ b/plugins/commands/toggle.py @@ -15,15 +15,26 @@ async def command_toggle(app: PyroClient, message: Message): await user.update_state(not user.enabled) if user.enabled: - await message.reply_text("Notifications have been disabled.") + await message.reply_text( + app._("toggle_disabled", "messages", locale=user.locale) + ) return + user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime("%H:%M") + if user.location is None: await message.reply_text( - f"Notifications have been enabled {user.offset} d. before garbage collection at {datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime('%H:%M')}. Use /setup to select your location." + app._("toggle_enabled", "messages", locale=user.locale).format( + offset=user.offset, + time=user_time, + ), ) return await message.reply_text( - f"Notifications have been enabled {user.offset} d. before garbage collection at {datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime('%H:%M')} at the **{user.location.name}**." + app._("toggle_enabled_location", "messages", locale=user.locale).format( + offset=user.offset, + time=user_time, + name=user.location.name, + ) ) diff --git a/plugins/commands/upcoming.py b/plugins/commands/upcoming.py index 3f85153..262e53b 100644 --- a/plugins/commands/upcoming.py +++ b/plugins/commands/upcoming.py @@ -17,7 +17,7 @@ async def command_upcoming(app: PyroClient, message: Message): if user.location is None: await message.reply_text( - "You have no location set. Use /setup to select your location." + app._("upcoming_empty_location", "messages", locale=user.locale) ) return @@ -52,8 +52,12 @@ async def command_upcoming(app: PyroClient, message: Message): if not entries: await message.reply_text( - f"No garbage collection entries found for the next 30 days at **{user.location.name}**" + app._("upcoming_empty", "messages", locale=user.locale).format( + name=user.location.name + ) ) return - await message.reply_text(f"Upcoming garbage collection:\n\n{entries_text}") + await message.reply_text( + app._("upcoming", "messages", locale=user.locale).format(entries=entries_text) + ) From 9403c087ccdd1b279b522708047d229260c875e2 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 30 Aug 2023 11:01:59 +0200 Subject: [PATCH 4/4] Updated locale strings --- locale/en.json | 51 +++++++++++++++++++---------- plugins/callbacks/callback.py | 11 ------- plugins/commands/remove_commands.py | 4 ++- plugins/commands/set_offset.py | 20 +++++++---- plugins/commands/set_time.py | 20 +++++++---- plugins/commands/setup.py | 26 ++++++++++----- 6 files changed, 81 insertions(+), 51 deletions(-) delete mode 100644 plugins/callbacks/callback.py diff --git a/locale/en.json b/locale/en.json index 9740982..9f96544 100644 --- a/locale/en.json +++ b/locale/en.json @@ -35,6 +35,10 @@ "messages": { "cancel": "Use /cancel if you want to cancel this operation.", "cancelled": "Operation has been cancelled.", + "checkout_deleted": "🗑️ Your data has been deleted. If you want to start using this bot again, please use /setup command. Otherwise delete/block the bot and do not interact with it anymore.", + "checkout_deletion": "Alright. Please, confirm that you want to delete your data from the bot.\n\nFollowing data will be deleted:\n• Selected location\n• Preferred language of all messages\n• Time of the notifications\n• Offset of the notifications\n\nUse keyboard provided to confirm and continue or /cancel to abort this operation.", + "checkout": "Here's pretty much all the data bot has. Please, use these buttons to choose whether you want to delete your data from the bot.", + "commands_removed": "✅ All currently registered commands have been unregistered. Commands will be registered again on bot's start.", "help": "🔔 This bot sends you notifications about garbage collection according to your local schedule.\n\n**Available commands**\n/help - Show this message\n/setup - Select the location\n/toggle - Disable/enable the reminders\n/set_time - Set the reminders' time\n/set_offset - Set offset between reminders and collection\n/upcoming - Show the upcoming collection\n/language - Select the bot's language\n/checkout - Export or remove your data\n\n💭 You can also suggest adding your town/district to the bot by contacting the admins using [this link]({url_contact}) and providing your schedule.\n\n⚙️ Want to host this bot yourself or make some changes? It's open-source, so you can basically fork it. Take a look at [bot's repository]({url_repo}) for details.\n\nHappy using! 🤗", "import_finished": "You have successfully inserted {count} entries.", "import_invalid_date": "Entries contain invalid date formats. Use **ISO 8601** date format.", @@ -42,42 +46,53 @@ "import_invalid": "This is not a valid garbage collection JSON.", "import": "Alright. Send me a valid JSON.", "locale_choice": "Alright. Please choose the language using keyboard below.", + "location_name_empty": "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule.\n\n{cancel_notice}", + "location_name_invalid": "Please, send the name of the location as a text. {cancel_notice}", + "location_name": "Please, send me a location name. It should be the name used in your local authorities' garbage collection schedule. This usually is a name of the district or even the town itself.", + "location_select": "Select the location using the keyboard provided.", + "reminder": "**Garbage Collection**\n\nType: {type}\nDate: {date}\n\nDon't forget to prepare your bin for collection!", + "search_nearby_empty": "Could not find any locations nearby. Let's try using the name search.", + "selection_invalid": "Please, select a valid option using the keyboard provided. {cancel_notice}", + "set_offset_finished": "🔔 Notifications offset has been updated! You will now receive notification about collection **{offset} d.** before the collection at {time}. {toggle_notice}", + "set_offset_invalid": "Please, provide a valid integer number of days in range 0 to 7 (inclusive). {cancel_notice}", + "set_offset": "Alright. Please, send how many days in advance do you want to get a notification about the collection.", + "set_time_finished": "🔔 Notifications time has been updated! You will now receive notification about collection {offset} d. before the collection at **{time}**. {toggle_notice}", + "set_time_invalid": "Please, provide a valid time in HH:MM format. {cancel_notice}", + "set_time": "Alright. Please, send your desired time in HH:MM format.", + "setup_finished": "✅ Finished! Your location is now **{name}**. You will receive the notifications about garbage collection {offset} d. in advance at {time}.", + "setup_retry": "ℹ️ If you want try selecting the location again, use the /setup command.", + "setup": "⚙️ Let's begin configuration with the search for your location.\n\nPlease, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send will **NOT** be saved anywhere and is only used for location lookup in the database.", "start_code_invalid": "🚫 You have started the bot by the link containing a location, but it does not seem to be a valid one. Please, use the command /setup to manually configure the location.", "start_code": "ℹ️ You have started the bot by the link containing a location **{name}**.\n\nPlease, confirm whether you want to use it as your location.", "start_configure": "📍 Let's configure your location. Press the button on pop-up keyboard to start the process.", - "selection_invalid": "Please, select a valid option using the keyboard provided. {cancel_notice}", "start_selection_no": "Alright, you're on your own now. Please, use the command /setup to configure your location and start receiving reminders.", "start_selection_yes": "✅ Finished! Your location is now **{name}**. You will receive reminders about garbage collection {offset} d. in advance at {time}.\n\nPlease, visit /help if you want to know how to change notifications time or disable them.", "start": "👋 Welcome!\n\nThis small open-source bot is made to simplify your life a bit easier by sending you notifications about upcoming garbage collection in your location.\n\nBy using this bot you accept [Privacy Policy]({privacy_policy}), otherwise please block and remove this bot before further interaction.\n\nNow the official part is over so you can dive into the bot.", - "checkout_deleted": "🗑️ Your data has been deleted. If you want to start using this bot again, please use /setup command. Otherwise delete/block the bot and do not interact with it anymore.", - "checkout": "Here's pretty much all the data bot has. Please, use these buttons to choose whether you want to delete your data from the bot.", - "checkout_deletion": "Alright. Please, confirm that you want to delete your data from the bot.\n\nFollowing data will be deleted:\n• Selected location\n• Preferred language of all messages\n• Time of the notifications\n• Offset of the notifications\n\nUse keyboard provided to confirm and continue or /cancel to abort this operation.", "toggle_disabled": "🔕 Notifications have been disabled.", - "toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.", "toggle_enabled_location": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time} at the **{name}**.", + "toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.", + "toggle": "Execute /toggle to enable notifications.", "upcoming_empty_location": "You have no location set. Use /setup to select your location.", "upcoming_empty": "No garbage collection entries found for the next 30 days at **{name}**", - "upcoming": "Upcoming garbage collection:\n\n{entries}", - "reminder": "**Garbage Collection**\n\nType: {type}\nDate: {date}\n\nDon't forget to prepare your bin for collection!", - "search_nearby_empty": "Could not find any locations nearby. Let's try using the name search.", - "location_select": "Select the location using the keyboard provided.", - "location_name": "Please, send me a location name. It should be the name used in your local authorities' garbage collection schedule. This usually is a name of the district or even the town itself.", - "location_name_invalid": "Please, send the name of the location as a text. {cancel_notice}", - "location_name_empty": "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule.\n\n{cancel_notice}" + "upcoming": "Upcoming garbage collection:\n\n{entries}" }, "buttons": { - "start_configure": "⚙️ Let's configure the bot", - "start_code_yes": "✅ Yes, I want to use it", - "start_code_no": "❌ No, I don't want to use it", - "delete_yes": "✅ Yes, I want to delete it", + "delete_confirm": "I agree and want to proceed", "delete_no": "❌ No, I don't want to delete it", - "delete_confirm": "I agree and want to proceed" + "delete_yes": "✅ Yes, I want to delete it", + "search_name": "🔎 Search by location name", + "search_nearby": "📍 Search nearby locations", + "start_code_no": "❌ No, I don't want to use it", + "start_code_yes": "✅ Yes, I want to use it", + "start_configure": "⚙️ Let's configure the bot" }, "callbacks": { "locale_set": "Your language now is: {locale}" }, "force_replies": { "import": "JSON with garbage entries", - "location_name": "Location name" + "location_name": "Location name", + "set_offset": "Number of days", + "set_time": "Time as HH:MM" } } \ No newline at end of file diff --git a/plugins/callbacks/callback.py b/plugins/callbacks/callback.py deleted file mode 100644 index 56c73f6..0000000 --- a/plugins/callbacks/callback.py +++ /dev/null @@ -1,11 +0,0 @@ -from pyrogram import filters -from pyrogram.types import CallbackQuery - -from classes.pyroclient import PyroClient - - -@PyroClient.on_callback_query(filters.regex("nothing")) # type: ignore -async def callback_nothing(app: PyroClient, callback: CallbackQuery): - await callback.answer( - text=app._("nothing", "callbacks", locale=callback.from_user.language_code) - ) diff --git a/plugins/commands/remove_commands.py b/plugins/commands/remove_commands.py index 008b8a0..e15ebd7 100644 --- a/plugins/commands/remove_commands.py +++ b/plugins/commands/remove_commands.py @@ -8,5 +8,7 @@ from classes.pyroclient import PyroClient ~filters.scheduled & filters.private & filters.command(["remove_commands"], prefixes=["/"]) # type: ignore ) async def command_remove_commands(app: PyroClient, message: Message): - await message.reply_text("Okay.") + user = await app.find_user(message.from_user) + + await message.reply_text(app._("commands_removed", "messages", locale=user.locale)) await app.remove_commands(command_sets=await app.collect_commands()) diff --git a/plugins/commands/set_offset.py b/plugins/commands/set_offset.py index d807758..042c32c 100644 --- a/plugins/commands/set_offset.py +++ b/plugins/commands/set_offset.py @@ -17,8 +17,10 @@ async def command_set_offset(app: PyroClient, message: Message): user = await app.find_user(message.from_user) await message.reply_text( - "Alright. Please, send how many days in advance do you want to get a notification about the collection.", - reply_markup=ForceReply(placeholder="Number of days"), + app._("set_offset", "messages", locale=user.locale), + reply_markup=ForceReply( + placeholder=app._("set_offset", "force_replies", locale=user.locale) + ), ) while True: @@ -39,7 +41,9 @@ async def command_set_offset(app: PyroClient, message: Message): ) except (ValueError, TypeError): await answer.reply_text( - "Please, provide a valid integer number of days in range 0 to 7 (inclusive). Use /cancel if you want to cancel this operation." + app._("set_offset_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue @@ -51,13 +55,17 @@ async def command_set_offset(app: PyroClient, message: Message): logger.info("User %s has set offset to %s", user.id, offset) - notice = "" if user.enabled else "Execute /toggle to enable notifications." - garbage_time = datetime( 1970, 1, 1, hour=user.time_hour, minute=user.time_minute ).strftime(app._("time", "formats")) await answer.reply_text( - f"Notifications time has been updated! You will now receive notification about collection **{offset} d.** before the collection at {garbage_time}. {notice}", + app._("set_offset_finished", "messages", locale=user.locale).format( + offset=offset, + time=garbage_time, + toggle_notice="" + if user.enabled + else app._("toggle", "messages", locale=user.locale), + ), reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/set_time.py b/plugins/commands/set_time.py index 84c22c9..b4c6e49 100644 --- a/plugins/commands/set_time.py +++ b/plugins/commands/set_time.py @@ -17,8 +17,10 @@ async def command_set_time(app: PyroClient, message: Message): user = await app.find_user(message.from_user) await message.reply_text( - "Alright. Please, send your desired time in HH:MM format.", - reply_markup=ForceReply(placeholder="Time as HH:MM"), + app._("set_time", "messages", locale=user.locale), + reply_markup=ForceReply( + placeholder=app._("set_time", "force_replies", locale=user.locale) + ), ) while True: @@ -35,7 +37,9 @@ async def command_set_time(app: PyroClient, message: Message): datetime.strptime(answer.text, "%H:%M") except ValueError: await answer.reply_text( - "Please, provide a valid time in HH:MM format. Use /cancel if you want to cancel this operation." + app._("set_time_invalid", "messages", locale=user.locale).format( + cancel_notice=app._("cancel", "messages", locale=user.locale) + ) ) continue @@ -51,11 +55,15 @@ async def command_set_time(app: PyroClient, message: Message): user_time.strftime("%H:%M"), ) - notice = "" if user.enabled else "Execute /toggle to enable notifications." - garbage_time = user_time.strftime(app._("time", "formats")) await answer.reply_text( - f"Notifications time has been updated! You will now receive notification about collection {user.offset} d. before the collection at **{garbage_time}**. {notice}", + app._("set_time_finished", "messages", locale=user.locale).format( + offset=user.offset, + time=garbage_time, + toggle_notice="" + if user.enabled + else app._("toggle", "messages", locale=user.locale), + ), reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/setup.py b/plugins/commands/setup.py index 6deec52..3b1d2ec 100644 --- a/plugins/commands/setup.py +++ b/plugins/commands/setup.py @@ -22,12 +22,14 @@ async def command_setup(app: PyroClient, message: Message): keyboard_type = ReplyKeyboard(resize_keyboard=True, row_width=1) keyboard_type.add( - ReplyButton("Search nearby locations", request_location=True), - ReplyButton("Search by location name"), + ReplyButton( + app._("search_nearby", "buttons", locale=user.locale), request_location=True + ), + ReplyButton(app._("search_name", "buttons", locale=user.locale)), ) await message.reply_text( - "Let's begin configuration with the search for your location.\n\nPlease, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send will **NOT** be saved anywhere and is only used for location lookup in the database.", + app._("setup", "messages", locale=user.locale), reply_markup=keyboard_type, ) @@ -41,9 +43,9 @@ async def command_setup(app: PyroClient, message: Message): ) return - if answer_type.location is None and answer_type.text not in [ - "Search by location name", - ]: + if answer_type.location is None and answer_type.text not in app.in_all_locales( + "search_name", "buttons" + ): await answer_type.reply_text( app._("selection_invalid", "messages", locale=user.locale).format( cancel_notice=app._("cancel", "messages", locale=user.locale) @@ -61,16 +63,22 @@ async def command_setup(app: PyroClient, message: Message): if location is None: await answer_type.reply_text( - "If you want try selecting the location again, use the /setup command.", + app._("setup_retry", "messages", locale=user.locale), reply_markup=ReplyKeyboardRemove(), ) return await user.update_location(location.id) - user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute) + user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime( + app._("time", "formats", locale=user.locale) + ) await message.reply_text( - f"✅ Finished! Your location is now **{location.name}**. You will receive the notifications about garbage collection {user.offset} d. in advance at {user_time.strftime(app._('time', 'formats', locale=user.locale))}.", + app._("setup_finished", "messages", locale=user.locale).format( + name=location.name, + offset=user.offset, + time=user_time, + ), reply_markup=ReplyKeyboardRemove(), )