diff --git a/.gitignore b/.gitignore index 491c0a0..2de64f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ .idea/ venv/ -spamer/ **/__pycache__/ -users_json_files/ -bot/bot_main/for_photo_creation/photo_size.txt -test.py -bot_db.sql +temp_data/ database_dump .env +photo_size.txt +test.py diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..ff35bf4 --- /dev/null +++ b/bot/__init__.py @@ -0,0 +1,31 @@ +from .commands import ( + start_command, + help_command, + keywords_command, + time_command, + date_calculator_command, + add_text_to_photo_command, + weather_command, + currency_converter_command, + task_scheduler_command, + password_generator_command, +) +from .keywords import ( + keywords_for_interaction +) +from .bot_starter import MyBot + +__all__ = ( + "start_command", + "help_command", + "keywords_command", + "time_command", + "date_calculator_command", + "add_text_to_photo_command", + "weather_command", + "currency_converter_command", + "task_scheduler_command", + "password_generator_command", + "keywords_for_interaction", + "MyBot", +) diff --git a/bot/bot_main/bot_classes/ConnectionDB.py b/bot/bot_main/bot_classes/ConnectionDB.py deleted file mode 100644 index 14c3f68..0000000 --- a/bot/bot_main/bot_classes/ConnectionDB.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -import mysql -import mysql.connector - - -from dotenv import load_dotenv, find_dotenv - - -class ConnectionDB: - def __init__(self): - load_dotenv(find_dotenv()) - self.con = mysql.connector.connect( - host=os.getenv('HOST'), - user=os.getenv('DB_USER'), - passwd=os.getenv('PASSWORD'), - database=os.getenv('DATABASE'), - ) diff --git a/bot/bot_main/bot_classes/ConverterForm.py b/bot/bot_main/bot_classes/ConverterForm.py deleted file mode 100644 index d6b2a48..0000000 --- a/bot/bot_main/bot_classes/ConverterForm.py +++ /dev/null @@ -1,7 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class ConverterForm(StatesGroup): - dollar = State() - euro = State() - hryvnia = State() diff --git a/bot/bot_main/bot_classes/DaysToBirthday.py b/bot/bot_main/bot_classes/DaysToBirthday.py deleted file mode 100644 index 14037d0..0000000 --- a/bot/bot_main/bot_classes/DaysToBirthday.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class DaysToBirthday(StatesGroup): - day_month = State() diff --git a/bot/bot_main/bot_classes/DuplicateDescriptionError.py b/bot/bot_main/bot_classes/DuplicateDescriptionError.py deleted file mode 100644 index 77dbac2..0000000 --- a/bot/bot_main/bot_classes/DuplicateDescriptionError.py +++ /dev/null @@ -1,3 +0,0 @@ -class DuplicateDescriptionError(Exception): - def __init__(self, message): - self.message = message diff --git a/bot/bot_main/bot_classes/PasswordGeneratorStates.py b/bot/bot_main/bot_classes/PasswordGeneratorStates.py deleted file mode 100644 index d792d84..0000000 --- a/bot/bot_main/bot_classes/PasswordGeneratorStates.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class PasswordGeneratorStates(StatesGroup): - update_description = State() - update_password = State() - delete = State() - - set_description = State() diff --git a/bot/bot_main/bot_classes/PhotoInscription.py b/bot/bot_main/bot_classes/PhotoInscription.py deleted file mode 100644 index e78c186..0000000 --- a/bot/bot_main/bot_classes/PhotoInscription.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class PhotoInscription(StatesGroup): - user_incription_config = State() diff --git a/bot/bot_main/bot_classes/SearchTerm.py b/bot/bot_main/bot_classes/SearchTerm.py deleted file mode 100644 index 22cc787..0000000 --- a/bot/bot_main/bot_classes/SearchTerm.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class SearchTerm(StatesGroup): - search_term = State() diff --git a/bot/bot_main/bot_classes/StatisticsTable.py b/bot/bot_main/bot_classes/StatisticsTable.py deleted file mode 100644 index ab3e470..0000000 --- a/bot/bot_main/bot_classes/StatisticsTable.py +++ /dev/null @@ -1,28 +0,0 @@ -from bot.bot_main.bot_classes.ConnectionDB import ConnectionDB - - -class StatisticsTable(ConnectionDB): - def __init__(self): - super().__init__() - self.cur = self.con.cursor() - self.create_table() - - - def create_table(self): - self.cur.execute( - ''' - CREATE TABLE IF NOT EXISTS statistics ( - id INT PRIMARY KEY AUTO_INCREMENT, - usage_date DATE NOT NULL, - user_id INT NOT NULL, - task_sched_delete_num INT DEFAULT 0, - task_sched_update_num INT DEFAULT 0, - task_sched_insert_num INT DEFAULT 0, - pass_gen_delete_num INT DEFAULT 0, - pass_gen_update_num INT DEFAULT 0, - pass_gen_insert_num INT DEFAULT 0, - CONSTRAINT unique_date_id UNIQUE (usage_date, user_id), - FOREIGN KEY (user_id) REFERENCES users_info(user_id) ON DELETE CASCADE ON UPDATE CASCADE - ) - ''' - ) \ No newline at end of file diff --git a/bot/bot_main/bot_classes/StickerTable.py b/bot/bot_main/bot_classes/StickerTable.py deleted file mode 100644 index 36f60ac..0000000 --- a/bot/bot_main/bot_classes/StickerTable.py +++ /dev/null @@ -1,43 +0,0 @@ -from bot.bot_main.bot_classes.ConnectionDB import ConnectionDB - - -class StickerTable(ConnectionDB): - def __init__(self): - super().__init__() - self.cur = self.con.cursor() - self.create_table_stickers() - - - def create_table_stickers(self): - self.cur.execute( - ''' - CREATE TABLE IF NOT EXISTS stickers( - id INT PRIMARY KEY AUTO_INCREMENT, - sticker VARCHAR(80), - CONSTRAINT unique_sticker UNIQUE(sticker) - ) - ''' - ) - - def get_sticker(self): - self.cur.execute( - ''' - SELECT sticker FROM stickers - ORDER BY id; - ''' - ) - - result = self.cur.fetchall() - - return result - - def insert_into_sticker_table(self, sticker_id): - self.cur.execute( - ''' - INSERT IGNORE INTO stickers(sticker) - VALUES (%s) - ''', - (sticker_id,) - ) - - self.con.commit() \ No newline at end of file diff --git a/bot/bot_main/bot_classes/TaskScheduler.py b/bot/bot_main/bot_classes/TaskScheduler.py deleted file mode 100644 index 6444216..0000000 --- a/bot/bot_main/bot_classes/TaskScheduler.py +++ /dev/null @@ -1,7 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class TaskScheduler(StatesGroup): - insert = State() - update = State() - delete = State() diff --git a/bot/bot_main/bot_classes/UserSticker.py b/bot/bot_main/bot_classes/UserSticker.py deleted file mode 100644 index b6a3aae..0000000 --- a/bot/bot_main/bot_classes/UserSticker.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.dispatcher.filters.state import StatesGroup, State - - -class UserSticker(StatesGroup): - user_sticker = State() diff --git a/bot/bot_main/bot_classes/UserToken.py b/bot/bot_main/bot_classes/UserToken.py deleted file mode 100644 index 64de878..0000000 --- a/bot/bot_main/bot_classes/UserToken.py +++ /dev/null @@ -1,86 +0,0 @@ -from bot.bot_main.bot_classes.ConnectionDB import ConnectionDB - - -class UserToken(ConnectionDB): - def __init__(self): - super().__init__() - self.cur = self.con.cursor() - self.create_table() - - - def create_table(self): - self.cur.execute( - ''' - CREATE TABLE IF NOT EXISTS generated_tokens ( - id INT PRIMARY KEY AUTO_INCREMENT, - user_id INT NOT NULL, - user_token VARCHAR(150), - CONSTRAINT unique_token UNIQUE (user_id), - FOREIGN KEY (user_id) REFERENCES users_info(user_id) ON DELETE CASCADE ON UPDATE CASCADE - ) - ''' - ) - - - def select_all_tokens(self): - self.cur.execute( - ''' - SELECT user_token FROM generated_tokens - ''' - ) - - all_tokens_tuple_lst = self.cur.fetchall() - all_tokens_lst = [token_tuple[0] for token_tuple in all_tokens_tuple_lst] - - return all_tokens_lst - - - def select_token(self, get_user_id): - self.cur.execute( - ''' - SELECT user_token FROM generated_tokens - WHERE user_id = %s - ''', - (get_user_id,) - ) - - get_token = self.cur.fetchone() - - if get_token: - return get_token[0] - - - def add_token(self, get_user_id, get_new_token): - self.cur.execute( - ''' - INSERT INTO generated_tokens(user_id, user_token) - VALUES (%s, %s) - ON DUPLICATE KEY UPDATE user_token = %s - ''', - (get_user_id, get_new_token, get_new_token) - ) - - self.con.commit() - - - # def update_token(self, get_new_token, get_user_id): - # self.cur.execute( - # ''' - # UPDATE generated_tokens - # SET user_token = %s - # WHERE user_id = %s - # ''', - # (get_new_token, get_user_id,) - # ) - - - def remove_token(self, get_user_id): - self.cur.execute( - ''' - DELETE FROM generated_tokens - WHERE user_id = %s - ''', - (get_user_id,) - ) - - self.con.commit() diff --git a/bot/bot_main/bot_classes/UsersDataStore.py b/bot/bot_main/bot_classes/UsersDataStore.py deleted file mode 100644 index 059d3ed..0000000 --- a/bot/bot_main/bot_classes/UsersDataStore.py +++ /dev/null @@ -1,49 +0,0 @@ -from bot.bot_main.bot_classes.ConnectionDB import ConnectionDB - - -class UsersDataStore(ConnectionDB): - def __init__(self): - super().__init__() - self.cur = self.con.cursor() - self.update_start_date_after_month() - - - def create_table(self): - self.cur.execute( - ''' - CREATE TABLE IF NOT EXISTS users_info( - id INT PRIMARY KEY AUTO_INCREMENT, - user_id INT NOT NULL, - username VARCHAR(200) NOT NULL, - full_name VARCHAR(200) NOT NULL, - chat_id VARCHAR(30) NOT NULL, - start_msg_date DATE DEFAULT(CURRENT_DATE), - CONSTRAINT unique_data UNIQUE(user_id) - ) - ''' - ) - - - def update_start_date_after_month(self): - self.cur.execute( - ''' - UPDATE users_info, statistics - SET start_msg_date = CURDATE() - WHERE DATEDIFF(usage_date, start_msg_date) > 30 - ''' - ) - - self.con.commit() - - - def connect_to_db(self, user_id, username, full_name, chat_id): - self.cur.execute( - ''' - INSERT IGNORE INTO users_info (user_id, username, full_name, chat_id) - VALUES (%s, %s, %s, %s) - ON DUPLICATE KEY UPDATE username = %s, full_name = %s, chat_id = %s - ''', - (user_id, username, full_name, chat_id, username, full_name, chat_id) - ) - - self.con.commit() diff --git a/bot/bot_main/commands_and_keywords/add_text_to_photo_command.py b/bot/bot_main/commands_and_keywords/add_text_to_photo_command.py deleted file mode 100644 index 38ac163..0000000 --- a/bot/bot_main/commands_and_keywords/add_text_to_photo_command.py +++ /dev/null @@ -1,53 +0,0 @@ -from aiogram import types -from aiogram.dispatcher import FSMContext -from aiogram.types import ContentType, InputFile - -from bot.bot_main.bot_classes.PhotoInscription import PhotoInscription -from bot.bot_main.for_photo_creation.photo_size import get_data_from_txt -from bot.bot_main.for_photo_creation.validate_args_number import validate_args -from bot.bot_main.main_objects_initialization import dp, bot -from bot.other_functions.delete_with_delay import delete_messages - - -@dp.message_handler(state='*', commands=['photo']) -async def get_photo_config(message: types.Message): - await PhotoInscription.user_incription_config.set() - bot_message = await message.reply( - 'Send me an image with description where have to ' - 'be a text which i will add to your image, text must be ' - 'in the following format: example//100//200//233//500//20\n\n' - 'example - this is a text that will be added to image)\n' - '100 - X coordinate (only integer allowed)\n' - '200 - Y coordinate (only integer allowed\n' - '233 - image width (only integer allowed)\n' - '500 - image height (only integer allowed)\n' - '20 - font size (only integer allowed)', - parse_mode='HTML' - ) - - await delete_messages(message, bot_message) - -@dp.message_handler(state=PhotoInscription.user_incription_config, content_types=ContentType.PHOTO) -async def create_photo(message: types.Message, state: FSMContext): - photo = message.photo[-1] - await photo.download(destination_file='imgs/test.jpg') - - description = message.caption - - if not description: - await message.reply('Sorry, that photo doesn’t have a caption. Try again.') - else: - user_input_lst = description.split('//', 5) - formatted_lst = [args.strip() for args in user_input_lst] - - create_photo_using_args = await validate_args(formatted_lst, message) - - if create_photo_using_args: - result_photo = InputFile('imgs/result.jpg') - txt_data = get_data_from_txt() - await bot.send_photo( - message.chat.id, - photo=result_photo, caption=f'{txt_data}', parse_mode='HTML' - ) - - await state.finish() diff --git a/bot/bot_main/commands_and_keywords/currency_converter_command.py b/bot/bot_main/commands_and_keywords/currency_converter_command.py deleted file mode 100644 index c17d259..0000000 --- a/bot/bot_main/commands_and_keywords/currency_converter_command.py +++ /dev/null @@ -1,138 +0,0 @@ -from aiogram import types -from aiogram.dispatcher import FSMContext - -from bot.bot_main.bot_classes.ConverterForm import ConverterForm -from bot.bot_main.main_objects_initialization import dp -from bot.keyboards.currency_converter.converter_keyboard import converter_keyboard -from bot.keyboards.currency_converter.currency_keyboard import currency_keyboard -from bot.keyboards.currency_converter.return_keyboard import return_keyboard -from bot.other_functions import currency_cost as cc -from bot.other_functions.close_keyboard import close_keyboard - - -@dp.message_handler(state='*', commands=['currency']) -async def currency(message: types.Message): - await message.reply('Choose menu option💵:', reply_markup=currency_keyboard) - - -@dp.callback_query_handler(text=['currency_rate']) -async def exchange_rate(call: types.CallbackQuery): - await call.message.edit_text( - f'BUY / SALE\n' - f'' - f'1 UAH = {cc.find_dollars_buy_in_hryvnias():.4f} / {cc.find_dollars_sale_in_hryvnias():.4f} USD\n' - f'1 UAH = {cc.find_euros_buy_in_hryvnias():.4f} / {cc.find_euros_sale_in_hryvnias():.4f} EUR\n' - f'1 EUR = {cc.find_dollars_buy_in_euros():.4f} / {cc.find_dollars_sale_in_euros():.4f} USD\n' - f'1 EUR = {cc.find_hryvnias_buy_in_euros():.4f} / {cc.find_hryvnias_sale_in_euros():.4f} UAH\n' - f'1 USD = {cc.find_euros_buy_in_dollars():.4f} / {cc.find_euros_sale_in_dollars():.4f} EUR\n' - f'1 USD = {cc.find_hryvnias_buy_in_dollars():.4f} / {cc.find_hryvnias_sale_in_dollars():.4f} UAH' - f'', parse_mode='HTML', reply_markup=return_keyboard - ) - await call.answer() - - -@dp.callback_query_handler(text=['currency_converter']) -async def converter(call: types.CallbackQuery): - await call.message.edit_text('Currency converter:', reply_markup=converter_keyboard) - await call.answer() - - -@dp.callback_query_handler(text=['convert_dollar']) -async def convert_dollar(call: types.CallbackQuery): - await ConverterForm.dollar.set() - await call.answer('Enter the amount of $ to convert into euros and hryvnias...', True) - - -@dp.message_handler(state=ConverterForm.dollar) -async def process_dollar(message: types.Message, state: FSMContext): - input_data = message.text - - try: - input_data = input_data.replace(',','.') - answer = float(input_data) - - await message.reply( - f'Cost of buying {input_data} dollars in hryvnias: ' - f'{answer / cc.find_dollars_buy_in_hryvnias():.4f}\n' - f'Selling price of {input_data} dollars in hryvnias: ' - f'{answer / cc.find_dollars_sale_in_hryvnias():.4f}\n' - f'Cost of buying {input_data} dollars in euros: ' - f'{answer / cc.find_dollars_buy_in_euros():.4f}\n' - f'Selling price of {input_data} dollars in euros: ' - f'{answer / cc.find_dollars_sale_in_euros():.4f}\n', - ) - except ValueError: - await message.reply('Invalid input. Must be a number. Try again.') - - await state.finish() - - -@dp.callback_query_handler(text=['convert_euro']) -async def convert_euro(call: types.CallbackQuery): - await ConverterForm.euro.set() - await call.answer('Enter the amount of € to convert to dollars and hryvnias...', True) - - -@dp.message_handler(state=ConverterForm.euro) -async def process_euro(message: types.Message, state: FSMContext): - input_data = message.text - - try: - input_data = input_data.replace(',', '.') - answer = float(input_data) - - await message.reply( - f'Cost of buying {input_data} euros in hryvnias: ' - f'{answer / cc.find_euros_buy_in_hryvnias():.4f}\n' - f'Selling price of {input_data} euros in hryvnias: ' - f'{answer / cc.find_euros_sale_in_hryvnias():.4f}\n' - f'Cost of buying {input_data} euros in dollars: ' - f'{answer / cc.find_euros_buy_in_dollars():.4f}\n' - f'Selling price of {input_data} euros in dollars: ' - f'{answer / cc.find_euros_sale_in_dollars():.4f}\n', - ) - except ValueError: - await message.reply('Invalid input. Must be a number. Try again.') - - await state.finish() - - -@dp.callback_query_handler(text=['convert_hryvnia']) -async def convert_hryvnia(call: types.CallbackQuery): - await ConverterForm.hryvnia.set() - await call.answer('Enter the amount of ₴ to convert to dollars and euros...', True) - - -@dp.message_handler(state=ConverterForm.hryvnia) -async def process_hryvnia(message: types.Message, state: FSMContext): - input_data = message.text - - try: - input_data = input_data.replace(',', '.') - answer = float(input_data) - - await message.reply( - f'Cost of buying {input_data} hryvnias in dollars:' - f'{answer / cc.find_hryvnias_buy_in_dollars():.4f}\n' - f'Selling price of {input_data} hryvnias in dollars: ' - f'{answer / cc.find_hryvnias_sale_in_dollars():.4f}\n' - f'Cost of buying {input_data} hryvnias in euros: ' - f'{answer / cc.find_hryvnias_buy_in_euros():.4f}\n' - f'Selling price of {input_data} hryvnias in euros: ' - f'{answer / cc.find_hryvnias_sale_in_euros():.4f}\n', - ) - except ValueError: - await message.reply('Invalid input. Must be a number. Try again.') - - await state.finish() - - -@dp.callback_query_handler(text=['back_to_menu']) -async def back_to_main_menu(call: types.CallbackQuery): - await call.message.edit_text('Choose menu option💵:', reply_markup=currency_keyboard) - await call.answer() - - -@dp.callback_query_handler(text=['close_converter']) -async def close_converter(call: types.CallbackQuery): - await close_keyboard(call) diff --git a/bot/bot_main/commands_and_keywords/keywords_command.py b/bot/bot_main/commands_and_keywords/keywords_command.py deleted file mode 100644 index 235c417..0000000 --- a/bot/bot_main/commands_and_keywords/keywords_command.py +++ /dev/null @@ -1,39 +0,0 @@ -from aiogram import types - -from bot.bot_main.main_objects_initialization import dp - - -@dp.message_handler(state='*', commands=['keywords']) -async def get_keywords(message: types.Message): - await message.reply( - 'bitcoinget bitcoin price\n\n' - - 'dtr stickeradd your sticker to bot database\n\n' - - 'dtr examplesend you few examples how to use /photo\n\n' - - 'msgddeletes the bot message you replied to and your message\n\n' - - 'random mem any_textsend me an image with caption where must be words "random mem" ' - 'and after this words you can type anything. All what you will type after keyword will be printed ' - 'on your image and sent to you back. Font color will be randomly generated.\n\n' - - 'mem any_textsend me an image with caption where must be word "mem" ' - 'and after this word you can type anything. All what you will type after keyword will be printed ' - 'on your image and sent to you. Font color for the image will be automatically set by the system ' - 'depending on the colors that the image contains.\n\n' - - 'What date will it be after N days?send you a message with a date that will ' - 'be after N days from the current date in format dd.mm.yyyy\n\n' - - 'What date will be after N days if we start from "your_date"?send you a message with a date ' - 'that will be after N days from "your_date" ' - '(must be in the same format as in the result) in format dd.mm.yyyy\n\n' - - 'dtr delete tasksdeletes the table and all its contents created using the command ' - '/taskscheduler and creates a new empty task table\n\n' - - 'dtr delete passwordsdeletes the table and all its contents created using the command ' - '/password and creates a new empty password table\n\n', - parse_mode='HTML' - ) diff --git a/bot/bot_main/commands_and_keywords/keywords_for_interaction.py b/bot/bot_main/commands_and_keywords/keywords_for_interaction.py deleted file mode 100644 index 1d6d3db..0000000 --- a/bot/bot_main/commands_and_keywords/keywords_for_interaction.py +++ /dev/null @@ -1,206 +0,0 @@ -import re -from datetime import datetime, timedelta - -from aiogram import types -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters import IsReplyFilter -from aiogram.types import ContentType, InputFile - -from bot.bot_main.bot_classes.UserSticker import UserSticker -from bot.bot_main.for_photo_creation.remake_user_photo import create_new_photo_auto_config -from bot.other_functions import currency_cost as cc -from bot.bot_main.main_objects_initialization import dp, sticker_table, bot, unique_table, store_users_data -from bot.other_functions.check_date_words import check_day -from bot.other_functions.get_days_and_date_num import exctract_from_user_input_days_and_date, \ - exctract_from_user_input_days_num -# from bot.other_functions.non_admin_message_filter import delete_non_admin_message, get_admin_ids -from bot.other_functions.remove_start_keyword import remove_mem_from_start, remove_rand_mem_from_start - - -@dp.message_handler(regexp='^dtr delete tasks$|^дтр видали завдання$') -async def tasks_delete(message: types.Message): - user_id = message.from_id - unique_table.drop_remake_task_table(user_id) - await message.reply(text='All tasks successfully deleted!!!') - - -@dp.message_handler(regexp='^dtr delete passwords$|^дтр видали паролі$') -async def passwords_delete(message: types.Message): - user_id = message.from_id - unique_table.drop_remake_password_table(user_id) - await message.reply(text='All passwords successfully deleted!!!') - - -@dp.message_handler(regexp='^location$|^місцезнаходження$') -async def bot_location(message: types.Message): - await bot.send_location(message.chat.id, latitude=49.924394, longitude=27.746868) - - -@dp.message_handler(regexp='^bitcoin$|^біткоін$|^біток$') -async def bitcoin_price(message: types.Message): - await message.reply( - f'Bitcoin price right now: {cc.bitcoin_price()} $\n', parse_mode='HTML' - ) - - -@dp.message_handler(regexp='^dtr sticker$|^дтр стікер$') -async def handle_message(message: types.Message): - await UserSticker.user_sticker.set() - await message.reply( - 'Send me a sticker and I will save it to database.' - 'Then you can use stickers by typing command /sticker.' - ) - - -@dp.message_handler(regexp='^dtr example$|^дтр приклад$') -async def help_with_photo(message: types.Message): - await message.reply( - 'Below you can see examples how to use command /photo\n\n' - 'example - only one argument. Text "example" ' - 'will be printed in the top left corner, font size = 20.\n\n' - - 'example//40 - two arguments. Text "example" ' - 'will be printed in the top left corner, font size = 40.\n\n' - - '100//200 - two arguments. Resize photo: width = 100, height = 200\n\n' - - 'example//100//200 - three arguments. Text "example" ' - 'will be printed in position where coordinate X = 100 ' - 'and coordinate Y = 200, font size = 20.\n\n' - - 'example//100//200//40 - four arguments. Text "example" ' - 'will be printed in position where coordinate X = 100 ' - 'and coordinate Y = 200, font size = 40.\n\n' - - 'example//100//200//1000//2000//50 - six arguments. Text "example" ' - 'will be printed in position where coordinate X = 100 ' - 'and coordinate Y = 200. New width will be equal to 1000 and height - 2000, font size = 50.\n', - parse_mode='HTML' - ) - -@dp.message_handler(state=UserSticker.user_sticker, content_types=ContentType.STICKER) -async def create_photo(message: types.Message, state: FSMContext): - sended_user_sticker = message.sticker.file_id - sticker_table.insert_into_sticker_table(sended_user_sticker) - await message.reply(f'Your sticker was added successfully!') - await state.finish() - - -@dp.message_handler(regexp='^random mem|^рандом мем', content_types=ContentType.PHOTO) -async def send_auto_config_photo_with_rand_text_clr(message: types.Message): - photo = message.photo[-1] - await photo.download(destination_file='imgs/test_auto_conf.jpg') - get_user_photo_caption = message.caption - formatted_text = remove_rand_mem_from_start(get_user_photo_caption) - - create_new_photo_auto_config(False, formatted_text) - - result_photo = InputFile('imgs/result_auto_conf.jpg') - await bot.send_photo(message.chat.id, photo=result_photo) - - -@dp.message_handler(regexp='^mem|^мем', content_types=ContentType.PHOTO) -async def send_auto_config_photo_with_text(message: types.Message): - photo = message.photo[-1] - await photo.download(destination_file='imgs/test_auto_conf.jpg') - get_user_photo_caption = message.caption - formatted_text = remove_mem_from_start(get_user_photo_caption) - - create_new_photo_auto_config(True, formatted_text) - - result_photo = InputFile('imgs/result_auto_conf.jpg') - await bot.send_photo(message.chat.id, photo=result_photo) - - -@dp.message_handler(IsReplyFilter(True), regexp='^msgd$|пвдв$') -async def delete_two_messages(message: types.Message): - if message.reply_to_message.from_user.id == bot.id: - replied_msg_id = message.reply_to_message.message_id - await bot.delete_message(chat_id=message.chat.id, message_id=replied_msg_id) - await message.delete() - else: - return - - -@dp.message_handler( - regexp=re.compile( - '^(Яка дата|Який день) буде через [1-9]+[0-9]* (день|днів|дня)[?]*$|' - '^What (date|day) will be after [1-9]+[0-9]* (day|days)[?]*$', - re.IGNORECASE - ) -) -async def find_date_after_days_from_current_date(message: types.Message): - user_input = message.text - extracted_days = exctract_from_user_input_days_num(user_input) - - today = datetime.now() - answer = today + timedelta(days=extracted_days) - format_answer = answer.strftime('%d.%m.%Y') - - await message.reply( - f'After {extracted_days} {check_day(extracted_days)} date will be {format_answer}!' - ) - - -@dp.message_handler( - regexp=re.compile( - '^(Яка дата|Який день) буде через [1-9]+[0-9]* (день|днів|дня),* якщо починати з ' - '([1-9]|0[1-9]|[1-2][0-9]|3[0-1]).([1-9]|0[1-9]|1[0-2]).([1-9]+.*[0-9]+)[?]*$|' - '^What (date|day) will be after [1-9]+[0-9]* (day|days) if we start from ' - '([1-9]|0[1-9]|[1-2][0-9]|3[0-1]).([1-9]|0[1-9]|1[0-2]).([1-9]+.*[0-9]+)[?]*$', - re.IGNORECASE - ) -) -async def find_date_after_days(message: types.Message): - user_input = message.text - extracted_data = exctract_from_user_input_days_and_date(user_input) - days_num = extracted_data[0] - month_day = extracted_data[1][0] - month = extracted_data[1][1] - year = extracted_data[1][2] - - try: - user_date = datetime(day=month_day, month=month, year=year) - format_user_date = user_date.strftime('%d.%m.%Y') - answer = user_date + timedelta(days=days_num) - format_answer = answer.strftime('%d.%m.%Y') - - await message.reply( - f'If start from {format_user_date} after ' - f'{days_num} {check_day(days_num)}, date will be {format_answer}!' - ) - except ValueError: - await message.reply( - f'Some data is entered incorrectly!!!\n\n' - f'I think the problem is the {month_day} (first number in your date) because ' - f'the month you entered does not contain such day of the month.', - parse_mode='HTML' - ) - - -# @dp.message_handler(content_types=ContentType.VOICE) -# async def delete_voice_except_admins(message: types.Message): -# await delete_non_admin_message(message) - - -# @dp.message_handler(content_types=ContentType.VIDEO_NOTE) -# async def delete_video_note_except_admins(message: types.Message): -# await delete_non_admin_message(message) - - -@dp.message_handler(content_types=ContentType.ANY) -async def check_bot_usage(message: types.Message): - chat_id = message.chat.id - bot_id = message.bot.id - user_id = message.from_id - username = message.from_user.username - full_name = message.from_user.full_name - message_id = message.message_id - - if username is None: - username = ' ' - - if full_name is None: - full_name = ' ' - - store_users_data.connect_to_db(user_id, username, full_name, chat_id) diff --git a/bot/bot_main/commands_and_keywords/main_commands.py b/bot/bot_main/commands_and_keywords/main_commands.py deleted file mode 100644 index 82ce6d1..0000000 --- a/bot/bot_main/commands_and_keywords/main_commands.py +++ /dev/null @@ -1,14 +0,0 @@ -from aiogram import types - -from bot.bot_main.main_objects_initialization import dp -from config import EN_COMMAND_DESC_LIST - - -@dp.message_handler(state='*', commands=['start']) -async def start_command(message: types.Message): - await message.reply('I am activated. Type your commands or keywords.') - - -@dp.message_handler(state='*', commands=['help']) -async def help_command(message: types.Message): - await message.reply(text=EN_COMMAND_DESC_LIST) diff --git a/bot/bot_main/commands_and_keywords/password_generator_command.py b/bot/bot_main/commands_and_keywords/password_generator_command.py deleted file mode 100644 index a73856b..0000000 --- a/bot/bot_main/commands_and_keywords/password_generator_command.py +++ /dev/null @@ -1,539 +0,0 @@ -import asyncio - -import aiogram.utils.exceptions -import mysql.connector -from aiogram import types -from aiogram.dispatcher import FSMContext -from aiogram.types import CallbackQuery - -from bot.bot_main.bot_classes.DuplicateDescriptionError import DuplicateDescriptionError -from bot.bot_main.bot_classes.PasswordGeneratorStates import PasswordGeneratorStates -from bot.bot_main.for_password_generation.generate_password import main_generation -# from bot.bot_main.for_password_generation import password_content_callbacks -# from bot.bot_main.for_password_generation import password_length_callbacks -from bot.bot_main.for_password_generation.password_content_callbacks import default_content_keyboard -from bot.bot_main.for_password_generation.password_length_callbacks import default_length_keyboard -from bot.bot_main.main_objects_initialization import dp, bot, unique_table, store_users_data -from bot.keyboards.password_generator.back_keyboard import back_to_telegram_generator_keyboard -from bot.keyboards.password_generator.download_app_keyboard import download_app_keyboard -from bot.keyboards.password_generator.generation_keyboard import third_generator_keyboard -from bot.keyboards.password_generator.radio_keyboard import first_generator_keyboard -from bot.keyboards.password_generator.set_length_keyboard import second_generator_keyboard -from bot.keyboards.password_generator.main_keyboard import password_start_keyboard -from bot.keyboards.password_generator.return_to_update_keyboard import password_generator_return_to_update -from bot.keyboards.password_generator.telegram_keyboard import password_telegram_keyboard -from bot.keyboards.password_generator.update_keyboard import password_generator_update_keyboard -from bot.other_functions import check_for_comma_and_dot, check_for_id_in_table -from bot.other_functions.check_for_repetetive_characters import check_if_repeatable_characters_is_present -from bot.other_functions.check_length_keyboard import keyboard_length_choice -from bot.other_functions.check_radio_keyboard import keyboard_content_choice -from bot.other_functions.close_keyboard import close_keyboard -from bot.other_functions.encryption_decryption import encrypt, decrypt -from bot.other_functions.work_with_json import send_json, remove_json -from bot.other_functions.message_delete_exception import message_delete_control - -DELETE_TIMEOUT = 60 - -@dp.message_handler(state='*', commands=['password']) -async def password(message: types.Message): - chat_id = message.chat.id - user_id = message.from_id - username = message.from_user.username - full_name = message.from_user.full_name - store_users_data.connect_to_db(user_id, username, full_name, chat_id) - - await message.reply( - 'Welcome to password generator 🔒\n\n' - '' - 'As you can see below, there are two buttons. By clicking on the first button, ' - 'you will go to the password generator embedded in me. After clicking the second button, ' - 'you will be presented with options to download the application to your local machine.' - '', - reply_markup=password_start_keyboard, - parse_mode='HTML' - ) - - -@dp.callback_query_handler(text=['telegram_password']) -async def telegram_password(call: CallbackQuery): - await call.message.edit_text( - 'Choose menu option:\n\n', - reply_markup=password_telegram_keyboard, - parse_mode='HTML' - ) - - -@dp.callback_query_handler(text=['show_password']) -async def option_show_passwords(call: types.CallbackQuery): - select_result = unique_table.select_pass_gen_table(f'pass_gen_table_{call.from_user.id}') - - if not select_result: - await call.message.edit_text('Table has no records', reply_markup=back_to_telegram_generator_keyboard) - else: - all_passwords = ''.join( - f'Password №{password_number}\n' - f'ID: {password_data[0]}\n' - f'Description: {password_data[1]}\n' - f'Password: {decrypt(password_data[2])}\n' - f'Length: {password_data[3]}\n' - f'Has repetetive?: {password_data[4]}\n\n' - for password_number, password_data in enumerate(select_result, 1) - ) - - try: - await call.message.edit_text( - text=all_passwords, - reply_markup=back_to_telegram_generator_keyboard, - parse_mode='HTML' - ) - - await call.answer() - except aiogram.utils.exceptions.BadRequest: - await call.message.edit_text( - text='Your passwords data is too big. I will send you a JSON file.', - reply_markup=back_to_telegram_generator_keyboard, - parse_mode='HTML' - ) - - sent_message = await send_json(call) - - await call.answer() - - await asyncio.sleep(DELETE_TIMEOUT) - await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) - - -@dp.callback_query_handler(text=['create_password']) -async def create_password(call: CallbackQuery): - message_text = 'Choose what your password can contain:' - - await call.message.edit_text(text=message_text, parse_mode='HTML', reply_markup=first_generator_keyboard) - await call.answer() - - -@dp.callback_query_handler(text=['change_desc_pass']) -async def change_desc_pass(call: CallbackQuery): - await call.message.edit_text( - 'What do you want to change?:\n\n', - reply_markup=password_generator_update_keyboard, - parse_mode='HTML' - ) - - -@dp.callback_query_handler(text=['change_description']) -async def change_password(call: CallbackQuery): - await PasswordGeneratorStates.update_description.set() - await call.answer('Enter description information in the format: "ID value", "description text"', True) - await call.message.delete() - - -@dp.message_handler(state=PasswordGeneratorStates.update_description) -async def process_description_update(message: types.Message, state: FSMContext): - try: - user_data = check_for_comma_and_dot.check_user_data(message.text) - user_chosen_id = check_for_comma_and_dot.check_user_id(message.text) - - if int(user_chosen_id) not in check_for_id_in_table.check_for_password_id(message): - raise ValueError - - - unique_table.update_password_desc( - f'pass_gen_table_{message.from_user.id}', user_data, int(user_chosen_id) - ) - - await message.answer('Data has been changed!', reply_markup=password_generator_return_to_update) - except ValueError: - await message.answer('Entered data was incorrect!', reply_markup=password_generator_return_to_update) - except mysql.connector.errors.IntegrityError: - await message.answer( - 'Password with such description already exists!', reply_markup=password_generator_return_to_update - ) - - await message_delete_control(message) - - await state.finish() - -@dp.callback_query_handler(text=['change_password']) -async def change_password(call: CallbackQuery): - await PasswordGeneratorStates.update_password.set() - await call.answer('Enter password information in the format: "ID value", "password text"', True) - await call.message.delete() - - -@dp.message_handler(state=PasswordGeneratorStates.update_password) -async def process_password_update(message: types.Message, state: FSMContext): - try: - user_data = check_for_comma_and_dot.check_user_data(message.text) - user_data_length = len(user_data) - repetetive_characters_in_password = check_if_repeatable_characters_is_present(user_data) - user_chosen_id = check_for_comma_and_dot.check_user_id(message.text) - - if int(user_chosen_id) not in check_for_id_in_table.check_for_password_id(message): - raise ValueError - - encryped_password = encrypt(user_data) - unique_table.update_password( - f'pass_gen_table_{message.from_user.id}', - encryped_password, - user_data_length, - repetetive_characters_in_password, - int(user_chosen_id) - ) - - await message.answer('Data has been changed!', reply_markup=password_generator_return_to_update) - except ValueError: - await message.answer('Entered data was incorrect!', reply_markup=password_generator_return_to_update) - except mysql.connector.errors.IntegrityError: - await message.answer( - 'Password with such description already exists!', reply_markup=password_generator_return_to_update - ) - - await message_delete_control(message) - - await state.finish() - -@dp.callback_query_handler(text=['delete_password']) -async def delete_password(call: CallbackQuery): - await PasswordGeneratorStates.delete.set() - await call.answer('Enter password ID and all data about that password will be removed.', True) - await call.message.delete() - - -@dp.message_handler(state=PasswordGeneratorStates.delete) -async def process_password_update(message: types.Message, state: FSMContext): - try: - if int(message.text) not in check_for_id_in_table.check_for_password_id(message): - raise ValueError - - unique_table.delete_from_password_table(f'pass_gen_table_{message.from_user.id}', message.text) - await message.answer('Data deleted successfully!', reply_markup=back_to_telegram_generator_keyboard) - except ValueError: - await message.answer('Password ID was entered incorrectly!', reply_markup=back_to_telegram_generator_keyboard) - - await message_delete_control(message) - - await state.finish() - - -@dp.callback_query_handler(text=['password_app']) -async def password_app(call: CallbackQuery): - await call.message.edit_text( - 'Choose your system: ', parse_mode='HTML', reply_markup=download_app_keyboard - ) - await call.answer() - - -@dp.callback_query_handler(text=['windows_installer']) -async def windows_installer(call: CallbackQuery): - with open('password_generator_app/Password_Generator_Windows_Setup.zip', 'rb') as zip_archive: - zip_content = zip_archive.read() - - sent_message = await call.bot.send_document( - chat_id=call.message.chat.id, - document=('Password_Generator_Windows_Setup.zip', zip_content), - caption='Password for archive: 123454321', - parse_mode = 'HTML' - ) - - await call.answer() - - await asyncio.sleep(DELETE_TIMEOUT) - await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) - - -@dp.callback_query_handler(text=['linux_macos']) -async def linux_macos(call: CallbackQuery): - with open('password_generator_app/Password_Generator_Linux_MacOS.zip', 'rb') as zip_archive: - zip_content = zip_archive.read() - - sent_message = await call.bot.send_document( - chat_id=call.message.chat.id, - document=('Password_Generator_Linux_MacOS.zip', zip_content), - caption='Password for archive: 123454321', - parse_mode='HTML' - ) - - await call.answer() - - await asyncio.sleep(DELETE_TIMEOUT) - await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) - - -@dp.callback_query_handler(text=['json_password']) -async def json_password(call: CallbackQuery): - await call.message.edit_text( - 'You can see your JSON file below.', - parse_mode='HTML', reply_markup=back_to_telegram_generator_keyboard - ) - - sent_message = await send_json(call) - remove_json(call) - - await call.answer() - - await asyncio.sleep(DELETE_TIMEOUT) - await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) - - -@dp.callback_query_handler(text=['generate_password']) -async def generate_password(call: types.CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - data = await storage.get_data() - necessary_keys = ['bot_message_id', 'password_contains', 'password_length'] - user_alphabet = data.get('password_contains') - user_length = data.get('password_length') - - if all(key in data for key in necessary_keys): - alphabet = keyboard_content_choice(user_alphabet) - password_length = keyboard_length_choice(user_length) - generated_password = main_generation(alphabet, password_length) - data.update({'generated_pasword': generated_password}) - - await call.answer(cache_time=2) - - await call.message.edit_text( - text=f'Your password:\n{generated_password}', - parse_mode='HTML' - ) - await call.message.edit_reply_markup( - reply_markup=third_generator_keyboard - ) - await storage.set_data(data) - else: - await call.answer( - 'You need to press one of the button what your password could ' - 'contain and choose password difficulty first!', True - ) - - -@dp.callback_query_handler(text=['store_in_db']) -async def option_set_description(call: types.CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - data = await storage.get_data() - necessary_keys = ['bot_message_id', 'password_contains', 'password_length', 'generated_pasword'] - - if all(key in data for key in necessary_keys): - await PasswordGeneratorStates.set_description.set() - await call.answer('Enter description text...', True) - else: - await call.answer( - 'You need to press one of the button what your password could ' - 'contain, choose password difficulty and generate password first!', True - ) - - -@dp.message_handler(state=PasswordGeneratorStates.set_description) -async def process_description(message: types.Message, state: FSMContext): - user_desc = message.text - storage = dp.current_state(chat=message.chat.id, user=message.from_user.id) - data = await storage.get_data() - data.update({'description': f'{user_desc}'}) - await storage.set_data(data) - message_id = data['bot_message_id'] - - if 'generated_pasword' in data: - try: - if len(user_desc) > 384: - raise ValueError - - table_name = f'pass_gen_table_{message.from_id}' - user_description = data['description'] - user_password = data['generated_pasword'] - password_length = keyboard_length_choice(data['password_length']) - has_repetetive = check_if_repeatable_characters_is_present(user_password) - - await message.delete() - - if user_description in unique_table.select_description(table_name): - raise DuplicateDescriptionError('Duplicate description in user table.') - else: - encryped_password = encrypt(user_password) - unique_table.insert_password_data( - table_name, - user_description, - encryped_password, - password_length, - has_repetetive - ) - - await message.from_user.bot.edit_message_text( - text=f'Password saved successfuly!!!\n\n' - f'Your description:\n{data["description"]}\n' - f'Your password:\n{data["generated_pasword"]}', - chat_id=message.chat.id, - message_id=message_id, - parse_mode='HTML' - ) - await message.from_user.bot.edit_message_reply_markup( - chat_id=message.chat.id, - message_id=message_id, - reply_markup=third_generator_keyboard - ) - except ValueError: - await message.from_user.bot.edit_message_text( - text='Invalid input!\nToo many characters entered.', - chat_id=message.chat.id, - message_id=message_id, - ) - await message.from_user.bot.edit_message_reply_markup( - chat_id=message.chat.id, - message_id=message_id, - reply_markup=third_generator_keyboard - ) - except DuplicateDescriptionError: - await message.from_user.bot.edit_message_text( - text='Invalid input!\nDescription already in the table!!!', - chat_id=message.chat.id, - message_id=message_id, - ) - await message.from_user.bot.edit_message_reply_markup( - chat_id=message.chat.id, - message_id=message_id, - reply_markup=third_generator_keyboard - ) - - await state.finish() - await storage.set_data(data) - - -@dp.callback_query_handler(text=['next_to_second']) -async def next_to_second(call: CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - data = await storage.get_data() - necessary_keys = ['bot_message_id', 'password_contains'] - - if 'password_length' not in data: - if all(key in data for key in necessary_keys): - await call.message.edit_text( - 'Choose the password difficulty:\n\n', - reply_markup=second_generator_keyboard, - parse_mode='HTML' - ) - else: - await call.message.edit_text(text='Choose what your password can contain:', parse_mode='HTML') - await call.message.edit_reply_markup(reply_markup=first_generator_keyboard) - await call.answer('You should select any of these before continue!', True) - else: - await call.message.edit_text( - 'Choose the password difficulty:\n\n', - parse_mode='HTML' - ) - await call.message.edit_reply_markup(reply_markup=second_generator_keyboard) - await call.answer() - - -@dp.callback_query_handler(text=['next_to_third']) -async def next_to_third(call: CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - data = await storage.get_data() - necessary_keys = ['bot_message_id', 'password_contains', 'password_length'] - - if 'generated_pasword' in data: - if data['description']: - await call.message.edit_text( - text = f'Password saved successfuly!!!\n\n' - f'Your description:\n{data["description"]}\n' - f'Your password:\n{data["generated_pasword"]}', - reply_markup = third_generator_keyboard, - parse_mode = 'HTML' - ) - else: - await call.message.edit_text( - text=f'Your password:\n{data["generated_pasword"]}', - reply_markup=third_generator_keyboard, - parse_mode='HTML' - ) - else: - if all(key in data for key in necessary_keys): - await call.message.edit_text( - 'Generate your password and write it to the database! \n\n', - reply_markup=third_generator_keyboard, - parse_mode='HTML' - ) - else: - await call.message.edit_text(text='Choose the password difficulty:', parse_mode='HTML') - await call.message.edit_reply_markup(reply_markup=second_generator_keyboard) - await call.answer('You need to choose what will contain your password and choose its difficulty!', True) - - -@dp.callback_query_handler(text=['main_generator_menu']) -async def main_generator_menu(call: CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - await storage.finish() - - default_content_keyboard() - default_length_keyboard() - - await call.message.edit_text( - 'Choose menu option: \n\n', - reply_markup=password_telegram_keyboard, - parse_mode='HTML' - ) - - -@dp.callback_query_handler(text=['back_to_first']) -async def back_to_first(call: CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - data = await storage.get_data() - - message_text = 'Choose what your password can contain:' - - await call.message.edit_text(text=message_text, parse_mode='HTML', reply_markup=first_generator_keyboard) - await call.answer() - - -@dp.callback_query_handler(text=['back_to_second']) -async def back_to_second(call: CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - data = await storage.get_data() - user_choice = data.get('password_length') - - message_text = 'Choose the password difficulty:\n\n' - - await call.message.edit_text(text=message_text, parse_mode='HTML') - await call.message.edit_reply_markup(reply_markup=second_generator_keyboard) - await call.answer() - - -@dp.callback_query_handler(text=['return_to_update_menu']) -async def return_to_update_menu(call: CallbackQuery): - await call.message.edit_text( - 'What do you want to change?:', - parse_mode='HTML', reply_markup=password_generator_update_keyboard - ) - await call.answer() - - -@dp.callback_query_handler(text=['back_to_telegram_generator']) -async def back_to_telegram_generator(call: CallbackQuery): - storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) - await storage.finish() - - default_content_keyboard() - default_length_keyboard() - - await call.message.edit_text( - 'Choose menu option: ', - parse_mode='HTML', reply_markup=password_telegram_keyboard - ) - await call.answer() - - -@dp.callback_query_handler(text=['back_to_main_menu']) -async def back_to_main_menu(call: CallbackQuery): - await call.message.edit_text( - 'Welcome to password generator 🔒\n\n' - '' - 'As you can see below, there are two buttons. By clicking on the first button, ' - 'you will go to the password generator embedded in me. After clicking the second button, ' - 'you will be presented with options to download the application to your local machine.' - '', - parse_mode='HTML', - reply_markup=password_start_keyboard - ) - await call.answer() - - -@dp.callback_query_handler(text=['close_app']) -async def close_app(call: CallbackQuery): - await close_keyboard(call) diff --git a/bot/bot_main/commands_and_keywords/random_numbers_generation_command.py b/bot/bot_main/commands_and_keywords/random_numbers_generation_command.py deleted file mode 100644 index e4d3850..0000000 --- a/bot/bot_main/commands_and_keywords/random_numbers_generation_command.py +++ /dev/null @@ -1,31 +0,0 @@ -from random import randint - -from aiogram import types - -from bot.bot_main.main_objects_initialization import dp -from bot.keyboards.random_number import random_numbers_keyboard -from bot.other_functions.close_keyboard import close_keyboard - - -@dp.message_handler(state='*', commands=['random']) -async def randomize(message: types.Message): - await message.reply('Select a number range: ', reply_markup=random_numbers_keyboard.random_number_keyboard) - - -@dp.callback_query_handler(text=['random_value_1', 'random_value_2', 'random_value_3', 'random_value_4']) -async def choose_random(call: types.CallbackQuery): - if call.data == 'random_value_1': - await call.answer(f'Generated random number: {randint(0, 10)} 🎲', True) - elif call.data == 'random_value_2': - await call.answer(f'Generated random number: {randint(0, 100)} 🎲', True) - elif call.data == 'random_value_3': - await call.answer(f'Generated random number: {randint(-100, 100)} 🎲', True) - elif call.data == 'random_value_4': - await call.answer(f'Generated random number: \n{randint(-1000000, 1000000)} 🎲', True) - - await call.answer() - - -@dp.callback_query_handler(text=['close_random']) -async def close_random(call: types.CallbackQuery): - await close_keyboard(call) diff --git a/bot/bot_main/commands_and_keywords/sticker_command.py b/bot/bot_main/commands_and_keywords/sticker_command.py deleted file mode 100644 index 8adeab4..0000000 --- a/bot/bot_main/commands_and_keywords/sticker_command.py +++ /dev/null @@ -1,12 +0,0 @@ -from aiogram import types - -from bot.bot_main import main_objects_initialization -from bot.other_functions.extract_random_data import get_random_data -from bot.bot_main.main_objects_initialization import dp, bot - - -@dp.message_handler(state='*', commands=['sticker']) -async def choose_sticker(message: types.Message): - data = main_objects_initialization.sticker_table.get_sticker() - await bot.send_sticker(message.chat.id, sticker=f'{get_random_data(data)}') - await message.delete() diff --git a/bot/bot_main/commands_and_keywords/task_scheduler_command.py b/bot/bot_main/commands_and_keywords/task_scheduler_command.py deleted file mode 100644 index 9ca9bc6..0000000 --- a/bot/bot_main/commands_and_keywords/task_scheduler_command.py +++ /dev/null @@ -1,124 +0,0 @@ -import mysql.connector - -from aiogram import types -from aiogram.dispatcher import FSMContext - -from bot.bot_main import main_objects_initialization -from bot.bot_main.bot_classes.TaskScheduler import TaskScheduler -from bot.bot_main.main_objects_initialization import dp -from bot.keyboards.task_scheduler import back_to_planner_keyboard -from bot.keyboards.task_scheduler.scheduler_keyboard import scheduler_keyboard -from bot.other_functions import check_for_comma_and_dot, check_for_id_in_table -from bot.other_functions.close_keyboard import close_keyboard -from bot.other_functions.message_delete_exception import message_delete_control - - -@dp.message_handler(state='*', commands=['taskscheduler']) -async def scheduler_call(message: types.Message): - chat_id = message.chat.id - user_id = message.from_id - username = message.from_user.username - full_name = message.from_user.full_name - main_objects_initialization.store_users_data.connect_to_db(user_id, username, full_name, chat_id) - - await message.reply('Choose a task scheduler function:', reply_markup=scheduler_keyboard) - - -@dp.callback_query_handler(text=['select']) -async def option_select(call: types.CallbackQuery): - select_result = main_objects_initialization.unique_table.select_table('table_' + str(call.from_user.id)) - if not select_result: - await call.message.edit_text('Table has no records', reply_markup=back_to_planner_keyboard.return_keyboard) - else: - all_tasks = ''.join( - f'ID{task[0]} | Task №{task_number} | {task[1]}\n' - for task_number, task in enumerate(select_result, 1) - ) - - await call.message.edit_text(all_tasks, reply_markup=back_to_planner_keyboard.return_keyboard) - - -@dp.callback_query_handler(text=['insert']) -async def option_insert(call: types.CallbackQuery): - await TaskScheduler.insert.set() - await call.answer('Enter the task text...', True) - await call.message.delete() - -@dp.message_handler(state=TaskScheduler.insert) -async def process_insert(message: types.Message, state: FSMContext): - try: - if len(message.text) > 768: - raise ValueError - - main_objects_initialization.unique_table.insert_into_table(('table_' + str(message.from_user.id)), message.text) - await message.answer('Data added successfully!', reply_markup=back_to_planner_keyboard.return_keyboard) - except ValueError: - await message.answer('Too many characters entered!', reply_markup=back_to_planner_keyboard.return_keyboard) - - await message_delete_control(message) - - await state.finish() - - -@dp.callback_query_handler(text=['update']) -async def option_update(call: types.CallbackQuery): - await TaskScheduler.update.set() - await call.answer('Enter information in the format: "ID value", "task text"', True) - await call.message.delete() - - -@dp.message_handler(state=TaskScheduler.update) -async def process_update(message: types.Message, state: FSMContext): - try: - user_data = check_for_comma_and_dot.check_user_data(message.text) - user_id = check_for_comma_and_dot.check_user_id(message.text) - - if int(user_id) not in check_for_id_in_table.check_for_id(message): - raise ValueError - - main_objects_initialization.unique_table.update_table(f'table_{message.from_user.id}', user_data, int(user_id)) - await message.answer('Data has been changed!', reply_markup=back_to_planner_keyboard.return_keyboard) - except ValueError: - await message.answer('Data is entered incorrectly!', reply_markup=back_to_planner_keyboard.return_keyboard) - except mysql.connector.errors.IntegrityError: - await message.answer('You have already added this task', reply_markup=back_to_planner_keyboard.return_keyboard) - - await message_delete_control(message) - - await state.finish() - - -@dp.callback_query_handler(text=['delete']) -async def option_delete(call: types.CallbackQuery): - await TaskScheduler.delete.set() - await call.answer('Enter the ID of the task you want to delete...', True) - await call.message.delete() - - -@dp.message_handler(state=TaskScheduler.delete) -async def process_delete(message: types.Message, state: FSMContext): - try: - if int(message.text) not in check_for_id_in_table.check_for_id(message): - raise ValueError - - main_objects_initialization.unique_table.delete_from_table(('table_' + str(message.from_user.id)), message.text) - await message.answer('Data deleted successfully!', reply_markup=back_to_planner_keyboard.return_keyboard) - except ValueError: - await message.answer('Task ID was entered incorrectly!', reply_markup=back_to_planner_keyboard.return_keyboard) - - await message_delete_control(message) - - await state.finish() - - -@dp.callback_query_handler(text=['back_to_scheduler']) -async def back_to_main_menu(call: types.CallbackQuery): - await call.message.edit_text( - 'Choose a task scheduler function:', reply_markup=scheduler_keyboard - ) - await call.answer() - - -@dp.callback_query_handler(text=['close_scheduler']) -async def close_scheduler(call: types.CallbackQuery): - await close_keyboard(call) diff --git a/bot/bot_main/commands_and_keywords/time_commands.py b/bot/bot_main/commands_and_keywords/time_commands.py deleted file mode 100644 index 4ba5f42..0000000 --- a/bot/bot_main/commands_and_keywords/time_commands.py +++ /dev/null @@ -1,67 +0,0 @@ -import time -from datetime import date, datetime - -from aiogram import types -from aiogram.dispatcher import FSMContext - -from bot.bot_main.bot_classes.DaysToBirthday import DaysToBirthday -from bot.bot_main.main_objects_initialization import dp -from bot.other_functions.check_date_words import check_year, check_month, check_day - - -@dp.message_handler(state='*', commands=['time']) -async def search_time(message: types.Message): - utc_time = datetime.utcnow() - current_year = utc_time.timetuple().tm_year - current_month = utc_time.timetuple().tm_mon - current_day = utc_time.timetuple().tm_mday - - delta = date(current_year, current_month, current_day) - date(2022, 2, 23) - days_of_unity = date(current_year, current_month, current_day) - date(1919, 1, 21) - - await message.reply( - f'Current date:\t{utc_time.strftime("%d.%m.%Y UTC")} 📅\n' - f'Current time:\t{utc_time.strftime("%H:%M:%S UTC")} 🕔\n' - f'Day of the week:\t{(utc_time.strftime("%A")).upper()}\n' - f'Day of the year:\t{utc_time.timetuple().tm_yday} 🌞\n' - f'Number of days after russian full scale invasion to Ukraine:\t{delta.days} 🕊\n' - f'Day of Unity of Ukraine:\t{days_of_unity.days} 🤝' - ) - - if current_month == 1 and current_day == 22: - anniversary = current_year - 1919 - await message.answer(f'WOW, {anniversary}TH ANNIVERSARY OF THE UNITY OF UKRAINE') - - -@dp.message_handler(state='*', commands=['birthday']) -async def get_birthday(message: types.Message): - await DaysToBirthday.day_month.set() - await message.reply('Enter date of birth (or other date) in format dd.mm.yyyy...') - - -@dp.message_handler(state=DaysToBirthday.day_month) -async def process_birthday(message: types.Message, state: FSMContext): - input_data = message.text - input_data = input_data.split('.', 2) - current_year = time.localtime().tm_year - current_month = time.localtime().tm_mon - current_day = time.localtime().tm_mday - - try: - answer = abs( - date(current_year, current_month, current_day) - - date(int(input_data[2]), int(input_data[1]), int(input_data[0])) - ) - - years = int(answer.days / 365) - months = int((answer.days % 365) / 31) - days = answer.days - years * 365 - months * 31 - await message.reply(f'The number of days between the entered date and the current date: {answer.days}\n' - f'Formatted date: ' - f'{years} {check_year(years)}, {months} {check_month(months)}, {days} {check_day(days)}') - except ValueError: - await message.reply('Your input was incorrect! Try again.') - except IndexError: - await message.reply('Your input was incorrect! Try again.') - - await state.finish() diff --git a/bot/bot_main/commands_and_keywords/user_info_command.py b/bot/bot_main/commands_and_keywords/user_info_command.py deleted file mode 100644 index f747b7f..0000000 --- a/bot/bot_main/commands_and_keywords/user_info_command.py +++ /dev/null @@ -1,118 +0,0 @@ -import asyncio - -from aiogram import types - -from bot.bot_main.for_password_generation.generate_password import generate_token -from bot.bot_main.main_objects_initialization import dp, store_users_data, token_table, bot, unique_table -from bot.keyboards.token.token_keyboard import token_keyboard -from bot.other_functions.close_keyboard import close_keyboard -from bot.other_functions.encryption_decryption import encrypt, decrypt - -DELETE_TIMEOUT = 30 - -@dp.message_handler(state='*', commands=['token']) -async def user_data(message: types.Message): - get_username = message.from_user.username - user_id = message.from_user.id - full_name = message.from_user.full_name - chat_id = message.chat.id - - if chat_id == user_id: - if unique_table.check_tables_to_allow_token(user_id): - store_users_data.connect_to_db(user_id, get_username, full_name, chat_id) - - all_tokens = token_table.select_all_tokens() - user_token = token_table.select_token(user_id) - - get_username = check_for_username(get_username) - - if user_token in all_tokens: - await message.reply( - f'Username: {get_username}\n' - f'Telegram ID: {user_id}\n\n' - f'Desktop application token:\n{decrypt(user_token)}', - parse_mode='HTML', - reply_markup=token_keyboard - ) - else: - await message.reply( - f'Username: {get_username}\n' - f'Telegram ID: {user_id}\n\n', - parse_mode='HTML', - reply_markup=token_keyboard - ) - else: - bot_message = await message.reply( - 'First, you need to use the password generator (/password) ' - 'and create at least one password or click one of the buttons:\n' - '"Show all passwords", "Change description", "Change password", "Delete password"!\n' - 'Only then can you use this command.' - ) - await asyncio.sleep(DELETE_TIMEOUT) - await bot.delete_message(chat_id=chat_id, message_id=bot_message.message_id) - else: - bot_message = await message.reply('This command can be used only in private conversation with bot!') - await asyncio.sleep(DELETE_TIMEOUT) - await bot.delete_message(chat_id=chat_id, message_id=bot_message.message_id) - - -@dp.callback_query_handler(text='add_token') -async def add_token(call: types.CallbackQuery): - random_token = generate_token() - user_token = encrypt(random_token) - get_username = call.from_user.username - user_id = call.from_user.id - token_table.add_token(user_id, user_token) - - get_username = check_for_username(get_username) - - await call.message.edit_text( - f'Username: {get_username}\n' - f'Telegram ID: {user_id}\n\n' - f'Desktop application token:\n{random_token}', - parse_mode='HTML', - ) - - await call.message.edit_reply_markup(reply_markup=token_keyboard) - await call.answer() - - -@dp.callback_query_handler(text='delete_token') -async def delete_token(call: types.CallbackQuery): - get_username = call.from_user.username - user_id = call.from_user.id - - all_tokens = token_table.select_all_tokens() - user_token = token_table.select_token(user_id) - - if user_token in all_tokens: - token_table.remove_token(user_id) - else: - await call.answer() - return - - get_username = check_for_username(get_username) - - await call.message.edit_text( - f'Username: {get_username}\n' - f'Telegram ID: {user_id}', - parse_mode='HTML', - ) - - await call.message.edit_reply_markup(reply_markup=token_keyboard) - await call.answer() - - -@dp.callback_query_handler(text='close_token') -async def close_token(call: types.CallbackQuery): - await close_keyboard(call) - - -def check_for_username(username): - if username is not None: - username = f'{username}' - else: - username = f'You does not have a username🤷' - - return username - diff --git a/bot/bot_main/commands_and_keywords/weather_command.py b/bot/bot_main/commands_and_keywords/weather_command.py deleted file mode 100644 index fbb8b89..0000000 --- a/bot/bot_main/commands_and_keywords/weather_command.py +++ /dev/null @@ -1,39 +0,0 @@ -import requests -from aiogram import types -from aiogram.dispatcher import FSMContext - -from bot.bot_main.bot_classes.WeatherInfo import WeatherInfo -from bot.bot_main.main_objects_initialization import dp -from bot.parsing.parse_temprature import find_avarage_temp_between_two, parse_minmax_temp, \ - avarage_day_temp, parse_avarage_precipitation_probability - - -@dp.message_handler(state='*', commands=['weather']) -async def weather(message: types.Message): - await WeatherInfo.place.set() - await message.reply('Enter the name of the town...') - - -@dp.message_handler(state=WeatherInfo.place) -async def process_weather(message: types.Message, state: FSMContext): - input_place = message.text - - url = f'https://ua.sinoptik.ua/погода-{input_place}' - answer = requests.get(url) - - if answer.status_code == 200: - temp_list = find_avarage_temp_between_two(url) - await message.reply( - f'{parse_minmax_temp(url)}' - f'{avarage_day_temp(url)}' - f'Night temrature: {temp_list[0]}\n' - f'Morning temperature: {temp_list[1]}\n' - f'Day temperature: {temp_list[2]}\n' - f'Evening temperature: {temp_list[3]}\n\n' - f'Maximum chance of precipitation: {parse_avarage_precipitation_probability(url)}%', - parse_mode='Markdown' - ) - else: - await message.reply('Can not find such town name!') - - await state.finish() diff --git a/bot/bot_main/commands_and_keywords/youtube_search_command.py b/bot/bot_main/commands_and_keywords/youtube_search_command.py deleted file mode 100644 index 9d5104e..0000000 --- a/bot/bot_main/commands_and_keywords/youtube_search_command.py +++ /dev/null @@ -1,30 +0,0 @@ -from aiogram import types -from aiogram.dispatcher import FSMContext - -from bot.bot_main.bot_classes.SearchTerm import SearchTerm -from bot.bot_main.main_objects_initialization import dp -from bot.parsing import parse_link - - -@dp.message_handler(state='*', commands=['eugene']) -async def parse_links(message: types.Message): - await SearchTerm.search_term.set() - await message.reply('Enter a key words for video search in youtube...') - - -@dp.message_handler(state=SearchTerm.search_term) -async def process_links(message: types.Message, state: FSMContext): - input_data = message.text - - answer = parse_link.links_split(input_data) - - result = '' - for i in range(0, 5): - result += f'{answer[i]}\n' - - if "{'result': []}" not in result: - await message.reply(f'{result}') - else: - await message.answer('The link could not be found for this request.') - - await state.finish() diff --git a/bot/bot_main/for_password_generation/generate_password.py b/bot/bot_main/for_password_generation/generate_password.py deleted file mode 100644 index 7bdfb9c..0000000 --- a/bot/bot_main/for_password_generation/generate_password.py +++ /dev/null @@ -1,19 +0,0 @@ -from random import choices -from string import digits, ascii_letters, punctuation - - -def exclude_invalid_symblols_for_markup() -> str: - excluded_symbols = '<>&"' - allowed_symbols = ''.join([char for char in punctuation if char not in excluded_symbols]) - - return allowed_symbols - - -def main_generation(password_alphabet: str, password_length: int) -> str: - return ''.join(choices(password_alphabet, k=password_length)) - - -def generate_token() -> str: - token_alphabet = ascii_letters + digits + exclude_invalid_symblols_for_markup() - - return ''.join(choices(token_alphabet, k=50)) diff --git a/bot/bot_main/for_password_generation/password_content_callbacks.py b/bot/bot_main/for_password_generation/password_content_callbacks.py deleted file mode 100644 index 3bfe1eb..0000000 --- a/bot/bot_main/for_password_generation/password_content_callbacks.py +++ /dev/null @@ -1,143 +0,0 @@ -from aiogram import types - -from bot.bot_main.main_objects_initialization import dp -from bot.keyboards.password_generator.radio_keyboard import first_generator_keyboard, all_button, letters_button, \ - digits_button, let_dig_button, let_sig_button, dig_sig_button -from bot.other_functions.change_state_mem_storage import update_state - - -def default_content_keyboard(): - all_button.text = 'All characters' - letters_button.text = 'Only letters' - digits_button.text = 'Only digits' - let_dig_button.text = 'Letters & digits' - let_sig_button.text = 'Letters & signs' - dig_sig_button.text = 'Digits & signs' - - -@dp.callback_query_handler(text=['all_characters']) -async def option_all_characters(call: types.CallbackQuery): - result = await update_state(call) - - await call.answer( - 'Now your password can contain all posible characters!', - True - ) - - if all_button.text == 'All characters ✅': - pass - else: - all_button.text = 'All characters ✅' - letters_button.text = 'Only letters' - digits_button.text = 'Only digits' - let_dig_button.text = 'Letters & digits' - let_sig_button.text = 'Letters & signs' - dig_sig_button.text = 'Digits & signs' - - await call.message.edit_reply_markup(first_generator_keyboard) - - -@dp.callback_query_handler(text=['only_letters']) -async def option_only_letters(call: types.CallbackQuery): - result = await update_state(call) - await call.answer( - 'Now your password can contain only small and big english letters!', - True - ) - - if letters_button.text == 'Only letters ✅': - pass - else: - all_button.text = 'All characters' - letters_button.text = 'Only letters ✅' - digits_button.text = 'Only digits' - let_dig_button.text = 'Letters & digits' - let_sig_button.text = 'Letters & signs' - dig_sig_button.text = 'Digits & signs' - - await call.message.edit_reply_markup(first_generator_keyboard) - - -@dp.callback_query_handler(text=['only_digits']) -async def option_only_digits(call: types.CallbackQuery): - result = await update_state(call) - await call.answer( - 'Now your password can contain only digits!', - True - ) - - if digits_button.text == 'Only digits ✅': - pass - else: - all_button.text = 'All characters' - letters_button.text = 'Only letters' - digits_button.text = 'Only digits ✅' - let_dig_button.text = 'Letters & digits' - let_sig_button.text = 'Letters & signs' - dig_sig_button.text = 'Digits & signs' - - await call.message.edit_reply_markup(first_generator_keyboard) - - -@dp.callback_query_handler(text=['letters_digits']) -async def option_letters_digits(call: types.CallbackQuery): - result = await update_state(call) - await call.answer( - 'Now your password can contain english letters and digits!', - True - ) - - if let_dig_button.text == 'Letters & digits ✅': - pass - else: - all_button.text = 'All characters' - letters_button.text = 'Only letters' - digits_button.text = 'Only digits' - let_dig_button.text = 'Letters & digits ✅' - let_sig_button.text = 'Letters & signs' - dig_sig_button.text = 'Digits & signs' - - await call.message.edit_reply_markup(first_generator_keyboard) - - -@dp.callback_query_handler(text=['letters_signs']) -async def option_letters_signs(call: types.CallbackQuery): - result = await update_state(call) - await call.answer( - 'Now your password can contain all letters and signs!', - True - ) - - if let_sig_button.text == 'Letters & signs ✅': - pass - else: - all_button.text = 'All characters' - letters_button.text = 'Only letters' - digits_button.text = 'Only digits' - let_dig_button.text = 'Letters & digits' - let_sig_button.text = 'Letters & signs ✅' - dig_sig_button.text = 'Digits & signs' - - await call.message.edit_reply_markup(first_generator_keyboard) - - -@dp.callback_query_handler(text=['digits_signs']) -async def option_digits_signs(call: types.CallbackQuery): - result = await update_state(call) - - await call.answer( - 'Now your password can contain digits and signs!', - True - ) - - if dig_sig_button.text == 'Digits & signs ✅': - pass - else: - all_button.text = 'All characters' - letters_button.text = 'Only letters' - digits_button.text = 'Only digits' - let_dig_button.text = 'Letters & digits' - let_sig_button.text = 'Letters & signs' - dig_sig_button.text = 'Digits & signs ✅' - - await call.message.edit_reply_markup(first_generator_keyboard) diff --git a/bot/bot_main/for_password_generation/password_length_callbacks.py b/bot/bot_main/for_password_generation/password_length_callbacks.py deleted file mode 100644 index fd4f0ad..0000000 --- a/bot/bot_main/for_password_generation/password_length_callbacks.py +++ /dev/null @@ -1,165 +0,0 @@ -from aiogram import types - -from bot.bot_main.main_objects_initialization import dp -from bot.keyboards.password_generator.set_length_keyboard import very_easy_button, easy_button, normal_button, \ - hard_button, very_hard_button, unbreakable_button, second_generator_keyboard -from bot.other_functions.change_state_mem_storage import update_with_length_state - - -def default_length_keyboard(): - very_easy_button.text = 'Very easy' - easy_button.text = 'Easy' - normal_button.text = 'Normal' - hard_button.text = 'Hard' - very_hard_button.text = 'Very hard' - unbreakable_button.text = 'Unbreakable' - - -@dp.callback_query_handler(text=['very_easy']) -async def option_very_easy(call: types.CallbackQuery): - result = await update_with_length_state(call) - - if result is None: - pass - else: - await call.answer( - 'Length of your password will be equal to 10 characters!', - True - ) - - if very_easy_button.text == 'Very easy ✅': - pass - else: - very_easy_button.text = 'Very easy ✅' - easy_button.text = 'Easy' - normal_button.text = 'Normal' - hard_button.text = 'Hard' - very_hard_button.text = 'Very hard' - unbreakable_button.text = 'Unbreakable' - - await call.message.edit_reply_markup(second_generator_keyboard) - - -@dp.callback_query_handler(text=['easy']) -async def option_easy(call: types.CallbackQuery): - result = await update_with_length_state(call) - - if result is None: - pass - else: - await call.answer( - 'Length of your password will be equal to 20 characters!', - True - ) - - if easy_button.text == 'Easy ✅': - pass - else: - very_easy_button.text = 'Very easy' - easy_button.text = 'Easy ✅' - normal_button.text = 'Normal' - hard_button.text = 'Hard' - very_hard_button.text = 'Very hard' - unbreakable_button.text = 'Unbreakable' - - await call.message.edit_reply_markup(second_generator_keyboard) - - -@dp.callback_query_handler(text=['normal']) -async def option_normal(call: types.CallbackQuery): - result = await update_with_length_state(call) - - if result is None: - pass - else: - await call.answer( - 'Length of your password will be equal to 30 characters!', - True - ) - - if normal_button.text == 'Normal ✅': - pass - else: - very_easy_button.text = 'Very easy' - easy_button.text = 'Easy' - normal_button.text = 'Normal ✅' - hard_button.text = 'Hard' - very_hard_button.text = 'Very hard' - unbreakable_button.text = 'Unbreakable' - - await call.message.edit_reply_markup(second_generator_keyboard) - - -@dp.callback_query_handler(text=['hard']) -async def option_hard(call: types.CallbackQuery): - result = await update_with_length_state(call) - - if result is None: - pass - else: - await call.answer( - 'Length of your password will be equal to 40 characters!', - True - ) - - if hard_button.text == 'Hard ✅': - pass - else: - very_easy_button.text = 'Very easy' - easy_button.text = 'Easy' - normal_button.text = 'Normal' - hard_button.text = 'Hard ✅' - very_hard_button.text = 'Very hard' - unbreakable_button.text = 'Unbreakable' - - await call.message.edit_reply_markup(second_generator_keyboard) - - -@dp.callback_query_handler(text=['very_hard']) -async def option_very_hard(call: types.CallbackQuery): - result = await update_with_length_state(call) - - if result is None: - pass - else: - await call.answer( - 'Length of your password will be equal to 50 characters!', - True - ) - - if very_hard_button.text == 'Very hard ✅': - pass - else: - very_easy_button.text = 'Very easy' - easy_button.text = 'Easy' - normal_button.text = 'Normal' - hard_button.text = 'Hard' - very_hard_button.text = 'Very hard ✅' - unbreakable_button.text = 'Unbreakable' - - await call.message.edit_reply_markup(second_generator_keyboard) - - -@dp.callback_query_handler(text=['unbreakable']) -async def option_unbreakable(call: types.CallbackQuery): - result = await update_with_length_state(call) - - if result is None: - pass - else: - await call.answer( - 'Length of your password will be equal to 100 characters!', - True - ) - - if unbreakable_button.text == 'Unbreakable ✅': - pass - else: - very_easy_button.text = 'Very easy' - easy_button.text = 'Easy' - normal_button.text = 'Normal' - hard_button.text = 'Hard' - very_hard_button.text = 'Very hard' - unbreakable_button.text = 'Unbreakable ✅' - - await call.message.edit_reply_markup(second_generator_keyboard) diff --git a/bot/bot_main/for_photo_creation/photo_size.py b/bot/bot_main/for_photo_creation/photo_size.py deleted file mode 100644 index f771f39..0000000 --- a/bot/bot_main/for_photo_creation/photo_size.py +++ /dev/null @@ -1,23 +0,0 @@ -def get_ratio(width, height) -> str: - if width > height: - return f'{round(width / height, 2)} : 1' - elif width < height: - return f'1 : {round(height / width, 2)}' - else: - return '1 : 1' - -def write_to_txt(width, height): - sides_ratio = get_ratio(width, height) - with open('bot/bot_main/for_photo_creation/photo_size.txt', 'w+') as size_txt: - size_txt.write( - f'Width: {width}\n' - f'Height: {height}\n' - f'Approximate sides ratio: {sides_ratio}\n' - ) - - -def get_data_from_txt() -> str: - with open('bot/bot_main/for_photo_creation/photo_size.txt', 'r') as size_txt: - result = size_txt.read() - - return result diff --git a/bot/bot_main/main_objects_initialization.py b/bot/bot_main/main_objects_initialization.py deleted file mode 100644 index 44e4ac8..0000000 --- a/bot/bot_main/main_objects_initialization.py +++ /dev/null @@ -1,19 +0,0 @@ -from aiogram import Bot, Dispatcher -from aiogram.contrib.fsm_storage.memory import MemoryStorage - -from bot.bot_main.bot_classes.StatisticsTable import StatisticsTable -from bot.bot_main.bot_classes.UniqueTablesForUsers import UniqueTablesForUsers -from bot.bot_main.bot_classes.UserToken import UserToken -from bot.bot_main.bot_classes.UsersDataStore import UsersDataStore -from bot.bot_main.bot_classes.StickerTable import StickerTable -from config import API_TOKEN - -bot = Bot(token=API_TOKEN) -storage = MemoryStorage() -dp = Dispatcher(bot, storage=storage) - -store_users_data = UsersDataStore() -statistics_table = StatisticsTable() -unique_table = UniqueTablesForUsers() -sticker_table = StickerTable() -token_table = UserToken() diff --git a/bot/bot_starter.py b/bot/bot_starter.py new file mode 100644 index 0000000..b305c33 --- /dev/null +++ b/bot/bot_starter.py @@ -0,0 +1,7 @@ +from aiogram.utils import executor +from .config import dp + + +class MyBot: + def start(self) -> None: + return executor.start_polling(dp, skip_updates=True) diff --git a/bot/commands/__init__.py b/bot/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/commands/add_text_to_photo_command.py b/bot/commands/add_text_to_photo_command.py new file mode 100644 index 0000000..076bd1e --- /dev/null +++ b/bot/commands/add_text_to_photo_command.py @@ -0,0 +1,59 @@ +from aiogram import types +from aiogram.dispatcher import FSMContext +from aiogram.types import ContentType, InputFile + +from .commands_utils.states import PhotoInscription +from .commands_utils.for_photo_creation.photo_size import get_data_from_txt +from .commands_utils.for_photo_creation.validate_args_number import validate_args +from ..config import dp, bot +from ..enums import Command +from bot.commands.commands_utils.delete_with_delay import delete_messages + + +@dp.message_handler(state="*", commands=Command.IMAGE_EDITOR) +async def get_photo_config(message: types.Message): + await PhotoInscription.user_inscription_config.set() + bot_message = await message.reply( + "Send me an image with description where have to " + "be a text which i will add to your image, text must be " + "in the following format: example//100//200//233//500//20\n\n" + "example - this is a text that will be added to image)\n" + "100 - X coordinate (only integer allowed)\n" + "200 - Y coordinate (only integer allowed\n" + "233 - image width (only integer allowed)\n" + "500 - image height (only integer allowed)\n" + "20 - font size (only integer allowed)", + parse_mode="HTML", + ) + + await delete_messages(message, bot_message) + + +@dp.message_handler( + state=PhotoInscription.user_inscription_config, content_types=ContentType.PHOTO +) +async def create_photo(message: types.Message, state: FSMContext): + photo = message.photo[-1] + await photo.download(destination_file="imgs/test.jpg") + + description = message.caption + + if not description: + await message.reply("Sorry, that photo doesn’t have a caption. Try again.") + else: + user_input_lst = description.split("//", 5) + formatted_lst = [args.strip() for args in user_input_lst] + + create_photo_using_args = await validate_args(formatted_lst, message) + + if create_photo_using_args: + result_photo = InputFile("imgs/result.jpg") + txt_data = get_data_from_txt() + await bot.send_photo( + message.chat.id, + photo=result_photo, + caption=f"{txt_data}", + parse_mode="HTML", + ) + + await state.finish() diff --git a/bot/commands/commands_utils/__init__.py b/bot/commands/commands_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/other_functions/change_state_mem_storage.py b/bot/commands/commands_utils/change_state_mem_storage.py similarity index 60% rename from bot/other_functions/change_state_mem_storage.py rename to bot/commands/commands_utils/change_state_mem_storage.py index 0a4e052..b726dc9 100644 --- a/bot/other_functions/change_state_mem_storage.py +++ b/bot/commands/commands_utils/change_state_mem_storage.py @@ -1,4 +1,4 @@ -from bot.bot_main.main_objects_initialization import dp +from bot.config import dp async def update_state(call): @@ -7,8 +7,8 @@ async def update_state(call): storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) data = await storage.get_data() - data.update({'bot_message_id': call.message.message_id}) - data.update({'password_contains': choice}) + data.update({"bot_message_id": call.message.message_id}) + data.update({"password_contains": choice}) await storage.set_data(data) data = await storage.get_data() @@ -21,10 +21,10 @@ async def update_with_length_state(call): storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) data = await storage.get_data() - necessary_keys = ['bot_message_id', 'password_contains'] + necessary_keys = ["bot_message_id", "password_contains"] if all(key in data for key in necessary_keys): - data.update({'password_length': choice}) + data.update({"password_length": choice}) await storage.set_data(data) data = await storage.get_data() @@ -32,7 +32,9 @@ async def update_with_length_state(call): return data else: await call.answer( - 'You should press one of the button what your password could contain ' - 'first. And only then you can choose difficulty!', True, cache_time=2 + "You should press one of the button what your password could contain " + "first. And only then you can choose difficulty!", + True, + cache_time=2, ) return diff --git a/bot/commands/commands_utils/check_for_comma_and_dot.py b/bot/commands/commands_utils/check_for_comma_and_dot.py new file mode 100644 index 0000000..ff5ccc6 --- /dev/null +++ b/bot/commands/commands_utils/check_for_comma_and_dot.py @@ -0,0 +1,26 @@ +def check_user_id(message): + user_id = "" + if ", " in message: + user_id = message[: message.find(",")] + elif "," in message: + user_id = message[: message.find(",")] + elif ". " in message: + user_id = message[: message.find(".")] + elif "." in message: + user_id = message[: message.find(".")] + + return user_id + + +def check_user_data(message): + user_data = "" + if ", " in message: + user_data = message[message.find(",") + 2:] + elif "," in message: + user_data = message[message.find(",") + 1:] + elif ". " in message: + user_data = message[message.find(".") + 2:] + elif "." in message: + user_data = message[message.find(".") + 1:] + + return user_data diff --git a/bot/other_functions/check_for_id_in_table.py b/bot/commands/commands_utils/check_for_id_in_table.py similarity index 57% rename from bot/other_functions/check_for_id_in_table.py rename to bot/commands/commands_utils/check_for_id_in_table.py index 4e1a22f..ae729a8 100644 --- a/bot/other_functions/check_for_id_in_table.py +++ b/bot/commands/commands_utils/check_for_id_in_table.py @@ -1,8 +1,8 @@ -from bot.bot_main.main_objects_initialization import unique_table +from bot.config import unique_table def check_for_id(message): - table_data = unique_table.select_table(f'table_{message.from_user.id}') + table_data = unique_table.select_table(f"table_{message.from_user.id}") temp_lst = [] for count, value in enumerate(table_data): @@ -10,12 +10,12 @@ def check_for_id(message): return temp_lst + def check_for_password_id(message): - id_data = unique_table.select_pass_gen_id(f'pass_gen_table_{message.from_user.id}') + id_data = unique_table.select_pass_gen_id(f"pass_gen_table_{message.from_user.id}") temp_lst = [] for id_value in id_data: temp_lst.append(int(id_value[0])) return temp_lst - diff --git a/bot/other_functions/check_for_repetetive_characters.py b/bot/commands/commands_utils/check_for_repetitive_characters.py similarity index 100% rename from bot/other_functions/check_for_repetetive_characters.py rename to bot/commands/commands_utils/check_for_repetitive_characters.py diff --git a/bot/commands/commands_utils/check_length_keyboard.py b/bot/commands/commands_utils/check_length_keyboard.py new file mode 100644 index 0000000..20ae600 --- /dev/null +++ b/bot/commands/commands_utils/check_length_keyboard.py @@ -0,0 +1,13 @@ +def keyboard_length_choice(user_choice: str) -> int: + password_complexities = { + "very_easy": 10, + "easy": 20, + "normal": 30, + "hard": 40, + "very_hard": 50, + "unbreakable": 100, + } + + for complexity_key in password_complexities: + if user_choice == complexity_key: + return password_complexities[complexity_key] diff --git a/bot/commands/commands_utils/check_radio_keyboard.py b/bot/commands/commands_utils/check_radio_keyboard.py new file mode 100644 index 0000000..168c9bf --- /dev/null +++ b/bot/commands/commands_utils/check_radio_keyboard.py @@ -0,0 +1,22 @@ +from string import digits, ascii_letters + +from bot.commands.commands_utils.generate_password import ( + exclude_invalid_symbols_for_markup, +) + + +def keyboard_content_choice(user_choice: str) -> str: + allowed_punctuation = exclude_invalid_symbols_for_markup() + + password_content_options = { + "all_characters": digits + ascii_letters + allowed_punctuation, + "only_letters": ascii_letters, + "only_digits": digits, + "letters_digits": digits + ascii_letters, + "letters_signs": ascii_letters + allowed_punctuation, + "digits_signs": digits + allowed_punctuation, + } + + for content_option_key in password_content_options: + if user_choice == content_option_key: + return password_content_options[content_option_key] diff --git a/bot/commands/commands_utils/currency_converter_reply_builder.py b/bot/commands/commands_utils/currency_converter_reply_builder.py new file mode 100644 index 0000000..025f8df --- /dev/null +++ b/bot/commands/commands_utils/currency_converter_reply_builder.py @@ -0,0 +1,20 @@ +from typing import Type, Literal, List + +Currency: Type[str] = Literal["dollars", "hryvnias", "euros", "bitcoins"] + + +def currency_reply( + currency_amount: float, + input_data: str, + main_currency: Currency, + exchange_currency: List[Currency], + buy_price: List[float], + sell_price: List[float], +) -> str: + return "".join( + f"Cost of buying {input_data} {main_currency} in {exchange_currency[index]}: " + f"{currency_amount / buy_price[index]:.4f}\n" + f"Selling price of {input_data} {main_currency} in {exchange_currency[index]}: " + f"{currency_amount / sell_price[index]:.4f}\n\n" + for index in range(len(buy_price)) + ) diff --git a/bot/commands/commands_utils/currency_cost.py b/bot/commands/commands_utils/currency_cost.py new file mode 100644 index 0000000..d2f42ad --- /dev/null +++ b/bot/commands/commands_utils/currency_cost.py @@ -0,0 +1,256 @@ +import json +from abc import ABC, abstractmethod +from typing import Any, Union, Dict, List, Tuple +from urllib.request import urlopen + +import requests + +# from bot.config import CURRENCY_URL, BITCOIN_URL +from bot.enums.currencies import Currency + + +# from .currency_converter_reply_builder import Currency + + +class Converter: + CURRENCY_URL = "https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=11" + BITCOIN_URL = "https://api.coindesk.com/v1/bpi/currentprice.json" + + def _fetch_url_data(self, url: str) -> Any: + response = urlopen(url) + currency_rate_data = json.loads(response.read()) + return currency_rate_data + + def fetch_currency_data(self) -> Dict[str, Dict[str, float]]: + data = self._fetch_url_data(self.CURRENCY_URL) + return { + "buy": { + "uah_eur": float(data[0]["buy"]), + "uah_usd": float(data[1]["buy"]), + }, + "sale": { + "uah_eur": float(data[0]["sale"]), + "uah_usd": float(data[1]["sale"]), + }, + } + + def fetch_bitcoin_data(self) -> Dict[str, float]: + data = self._fetch_url_data(self.BITCOIN_URL) + return { + "btc_usd": float(data["bpi"]["USD"]["rate_float"]), + } + + def united_currency_data(self) -> Dict[str, Dict[str, float]]: + main_data = self.fetch_currency_data() + crypto_data = self.fetch_bitcoin_data() + main_data["buy"]["uah_btc"] = crypto_data["btc_usd"] * main_data["buy"]["uah_usd"] + main_data["sale"]["uah_btc"] = crypto_data["btc_usd"] * main_data["sale"]["uah_usd"] + + return main_data + + def convert( + self, base_currency: Currency + ) -> Dict[str, Union[Currency, List[Currency], List[float]]]: + currency_list = [*Currency] + currency_list.remove(base_currency) + + currency_data = { + "main_currency": base_currency, + "exchange_currency": currency_list, + } + + convert_data = self.united_currency_data() + print(convert_data["buy"].values()) + print(convert_data["sale"].values()) + + if base_currency == Currency.HRYVNIAS: + currency_data["buy_price"] = list(convert_data["buy"].values()) + currency_data["sale_price"] = list(convert_data["sale"].values()) + elif base_currency == Currency.DOLLARS: + currency_data["buy_price"] = [1, 1, 1] + currency_data["sale_price"] = [1, 1, 1] + elif base_currency == Currency.EUROS: + currency_data["buy_price"] = [1, 1, 1] + currency_data["sale_price"] = [1, 1, 1] + elif base_currency == Currency.BITCOINS: + currency_data["buy_price"] = [1, 1, 1] + currency_data["sale_price"] = [1, 1, 1] + + return currency_data + + +# print(Converter().unite_currency_data()) +# class Converter: +# def __init__(self): +# self.currency_rate_data = self._get_currency_rate_data(CURRENCY_URL) +# self.crypto_rate_data = self._get_currency_rate_data(BITCOIN_URL) +# self._parse_currency_url() +# self.buy_prices_usd = {} +# self.sale_prices_usd = {} +# +# def _get_currency_rate_data(self, url: str) -> Any: +# response = urlopen(url) +# currency_rate_data = json.loads(response.read()) +# return currency_rate_data +# +# def _parse_currency_url(self) -> None: +# self.buy_prices_usd +# +# def _parse_crypto_url(self) -> None: +# ... +# +# def convert( +# self, converting_currency: CurrencyType +# ) -> Dict[str, Union[CurrencyType, List[CurrencyType], List[float]]]: +# input_data = self.check_currency_type(converting_currency) +# +# return { +# "main_currency": input_data["converting_currency"], +# "exchange_currency": input_data["currency_list"], +# "buy_price": [1, 1, 1], +# "sell_price": [1, 1, 1], +# } +# +# def check_currency_type( +# self, converting_currency: CurrencyType +# ) -> Dict[str, Union[CurrencyType, List[CurrencyType]]]: +# currency_list = [*Currency] +# currency_list.remove(converting_currency) +# +# return { +# "converting_currency": converting_currency, +# "currency_list": currency_list, +# } +# +# +# Converter().convert(Currency.HRYVNIAS) + +# class Converter(ABC): +# def __init__(self, url: str): +# self.response = urlopen(url) +# self.currency_rate_data = json.loads(self.response.read()) +# +# @abstractmethod +# def convert(self, currency: Currency) -> Dict[str, Union[Currency, List[Currency], List[float]]]: +# raise NotImplementedError +# +# @abstractmethod +# def check_currency_type(self) -> None: +# raise NotImplementedError +# +# +# class DefaultConverter(Converter): +# def convert(self, currency: Currency) -> float: +# data = { +# "hryvnias": { +# "sale_usd": 12, +# "sale_eur": 12, +# "buy_usd": 12, +# "buy_eur": 11, +# }, +# "dollars": { +# "sale_uah": 12, +# "sale_eur": 12, +# "buy_uah": 1 / float(self.currency_rate_data[1]["buy"]), +# "buy_eur": 11, +# }, +# "euros": { +# "sale_uah": 12, +# "sale_usd": 12, +# "buy_uah": 1 / float(self.currency_rate_data[0]["buy"]), +# "buy_usd": 11, +# } +# } + + +# class BitcoinConverter(Converter): +# def convert(self, currency: Crypto) -> float: +# data = { +# "bitcoins": { +# "uah": 12, +# "usd": 12, +# "eur": 12, +# }, +# } + + +# def get_currency_rate_data(url: str) -> Any: +# response = urlopen(url) +# currency_rate_data = json.loads(response.read()) +# return currency_rate_data +# +# +# def find_dollars_buy_in_hryvnias(currency_rate_data: Any) -> float: +# return 1 / float(currency_rate_data[1]["buy"]) +# +# +# def find_euros_buy_in_hryvnias(currency_rate_data: Any) -> float: +# return 1 / float(currency_rate_data[0]["buy"]) +# +# +# def find_dollars_buy_in_euros(currency_rate_data: Any) -> float: +# return float(currency_rate_data[0]["buy"]) / float(currency_rate_data[1]["buy"]) +# +# +# def find_hryvnias_buy_in_euros(currency_rate_data: Any) -> float: +# return float(currency_rate_data[0]["buy"]) +# +# +# def find_euros_buy_in_dollars(currency_rate_data: Any) -> float: +# return float(currency_rate_data[1]["buy"]) / float(currency_rate_data[0]["buy"]) +# +# +# def find_hryvnias_buy_in_dollars(currency_rate_data: Any) -> float: +# return float(currency_rate_data[1]["buy"]) +# +# +# def find_dollars_sale_in_hryvnias(currency_rate_data: Any) -> float: +# return 1 / float(currency_rate_data[1]["sale"]) +# +# +# def find_euros_sale_in_hryvnias(currency_rate_data: Any) -> float: +# return 1 / float(currency_rate_data[0]["sale"]) +# +# +# def find_dollars_sale_in_euros(currency_rate_data: Any) -> float: +# return float(currency_rate_data[0]["sale"]) / float(currency_rate_data[1]["sale"]) +# +# +# def find_hryvnias_sale_in_euros(currency_rate_data: Any) -> float: +# return float(currency_rate_data[0]["sale"]) +# +# +# def find_euros_sale_in_dollars(currency_rate_data: Any) -> float: +# return float(currency_rate_data[1]["sale"]) / float(currency_rate_data[0]["sale"]) +# +# +# def find_hryvnias_sale_in_dollars(currency_rate_data: Any) -> float: +# return float(currency_rate_data[1]["sale"]) +# +# +# def bitcoin_price_in_dollars(currency_rate_data: Any) -> float: +# return float(currency_rate_data["bpi"]["USD"]["rate_float"]) +# +# +# def bitcoin_price_in_euros(currency_rate_data: Any) -> float: +# return float(currency_rate_data["bpi"]["EUR"]["rate_float"]) +# +# +# def uah_to_usd(currency_rate_data: Any) -> str: +# return "".join( +# f"1 UAH = {find_dollars_buy_in_hryvnias(currency_rate_data):.4f} / " +# f"{find_dollars_sale_in_hryvnias(currency_rate_data):.4f} USD\n" +# ) +# +# +# def uah_to_euro(currency_rate_data: Any) -> str: +# return "".join( +# f"1 UAH = {find_euros_buy_in_hryvnias(currency_rate_data):.4f} / " +# f"{find_euros_sale_in_hryvnias(currency_rate_data):.4f} EUR\n" +# ) + +# def uah_to_btc(currency_rate_data: Any) -> str: +# return "".join( +# f"1 UAH = {(find_dollars_buy_in_hryvnias(currency_rate_data) / bitcoin_price_in_dollars(currency_rate_data)):.4f} / " +# f"{find_euros_sale_in_hryvnias(currency_rate_data):.4f} BTC\n" +# ) diff --git a/bot/other_functions/delete_with_delay.py b/bot/commands/commands_utils/delete_with_delay.py similarity index 85% rename from bot/other_functions/delete_with_delay.py rename to bot/commands/commands_utils/delete_with_delay.py index 6821d8c..fae8b2f 100644 --- a/bot/other_functions/delete_with_delay.py +++ b/bot/commands/commands_utils/delete_with_delay.py @@ -1,9 +1,10 @@ import asyncio -from bot.bot_main.main_objects_initialization import bot +from bot.config import bot DELETE_TIMEOUT = 60 + async def delete_messages(message, bot_message): user_message = message.message_id await asyncio.sleep(DELETE_TIMEOUT) diff --git a/bot/commands/commands_utils/encryption_decryption.py b/bot/commands/commands_utils/encryption_decryption.py new file mode 100644 index 0000000..f0c5d06 --- /dev/null +++ b/bot/commands/commands_utils/encryption_decryption.py @@ -0,0 +1,14 @@ +import base64 + + +def encrypt(data) -> str: + encode = base64.b85encode(data.encode("UTF-16")) + sub_start_symbols = (encode.decode()).split("|N", 1)[1] # -> into database + return sub_start_symbols + + +def decrypt(data) -> str: + print(data) + add_start_symbols = "|N" + data + decode = base64.b85decode(add_start_symbols).decode("UTF-16") # -> from database + return decode diff --git a/bot/commands/commands_utils/for_photo_creation/__init__.py b/bot/commands/commands_utils/for_photo_creation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/commands/commands_utils/for_photo_creation/photo_size.py b/bot/commands/commands_utils/for_photo_creation/photo_size.py new file mode 100644 index 0000000..8d4a105 --- /dev/null +++ b/bot/commands/commands_utils/for_photo_creation/photo_size.py @@ -0,0 +1,33 @@ +import os + +TXT_FILE = "photo_size.txt" +TXT_SIZE_PATH = os.path.join("bot", "commands", "commands_utils", "for_photo_creation", TXT_FILE) + +def get_ratio(width, height) -> str: + if width > height: + return f"{round(width / height, 2)} : 1" + elif width < height: + return f"1 : {round(height / width, 2)}" + else: + return "1 : 1" + + +def write_to_txt(width, height): + sides_ratio = get_ratio(width, height) + + with open(TXT_SIZE_PATH, "w+") as size_txt: + size_txt.write( + f"Width: {width}\n" + f"Height: {height}\n" + f"Approximate sides ratio: {sides_ratio}\n" + ) + + +def get_data_from_txt() -> str: + with open(TXT_SIZE_PATH, "r") as size_txt: + result = size_txt.read() + + return result + + +# write_to_txt(100, 100) \ No newline at end of file diff --git a/bot/bot_main/for_photo_creation/remake_user_photo.py b/bot/commands/commands_utils/for_photo_creation/remake_user_photo.py similarity index 59% rename from bot/bot_main/for_photo_creation/remake_user_photo.py rename to bot/commands/commands_utils/for_photo_creation/remake_user_photo.py index bf09681..617a83c 100644 --- a/bot/bot_main/for_photo_creation/remake_user_photo.py +++ b/bot/commands/commands_utils/for_photo_creation/remake_user_photo.py @@ -1,17 +1,14 @@ import random import textwrap -import warnings from PIL import Image, ImageFont, ImageDraw +from .photo_size import write_to_txt -from bot.bot_main.for_photo_creation.photo_size import write_to_txt - -def create_new_photo(user_data=' ', pos_x=0, pos_y=0, img_width=0, img_height=0, img_font=20): - - warnings.filterwarnings('ignore', category=DeprecationWarning) - - used_image = Image.open('imgs/test.jpg') +def create_new_photo( + user_data=" ", pos_x=0, pos_y=0, img_width=0, img_height=0, img_font=20 +): + used_image = Image.open("imgs/test.jpg") if img_width == 0 and img_height == 0: width, height = used_image.size @@ -19,28 +16,28 @@ def create_new_photo(user_data=' ', pos_x=0, pos_y=0, img_width=0, img_height=0, else: used_image = change_size_statement(img_width, img_height, used_image) - IMAGE_WIDTH, IMAGE_HEIGHT = used_image.size - write_to_txt(IMAGE_WIDTH, IMAGE_HEIGHT) + image_width, image_height = used_image.size + write_to_txt(image_width, image_height) default_font_size = 20 font_size = change_font_size(img_font, default_font_size) - if ((pos_x or pos_y) < 0) or (pos_x > IMAGE_WIDTH or pos_y > pos_y): + if ((pos_x or pos_y) < 0) or (pos_x > image_width or pos_y > pos_y): return True draw = ImageDraw.Draw(used_image) - font = ImageFont.truetype('fonts/AdverGothicC.ttf', font_size) + font = ImageFont.truetype("fonts/AdverGothicC.ttf", font_size) - dominant_color = get_image_general_color(used_image, IMAGE_WIDTH, IMAGE_HEIGHT) + dominant_color = get_image_general_color(used_image, image_width, image_height) font_color = visible_color(dominant_color) lines = [] - line = '' - line_height = font.getsize('A')[1] + line = "" + line_height = font.getbbox("A")[1] - for word in user_data.split(' '): - if draw.textsize(line + ' ' + word, font=font)[0] <= IMAGE_WIDTH - pos_x: - line += ' ' + word + for word in user_data.split(" "): + if draw.textlength(text=line + " " + word, font=font) <= image_width - pos_x: + line += " " + word else: lines.append(line.strip()) line = word @@ -52,33 +49,31 @@ def create_new_photo(user_data=' ', pos_x=0, pos_y=0, img_width=0, img_height=0, draw.text((pos_x, pos_y), line, fill=font_color, font=font) pos_y += line_height + (font_size * 0.2) - used_image.save('imgs/result.jpg') + used_image.save("imgs/result.jpg") -def create_new_photo_auto_config(clr_choice: bool, user_data=' '): - warnings.filterwarnings('ignore', category=DeprecationWarning) - - used_image = Image.open('imgs/test_auto_conf.jpg') - IMAGE_WIDTH, IMAGE_HEIGHT = used_image.size +def create_new_photo_auto_config(clr_choice: bool, user_data=" "): + used_image = Image.open("imgs/test_auto_conf.jpg") + image_width, image_height = used_image.size draw = ImageDraw.Draw(used_image) # 20 big 'A' with font size = 50 is width 722. One 'A' == 36 - if IMAGE_WIDTH <= 500: - font_size = int(IMAGE_WIDTH * 0.1) + if image_width <= 500: + font_size = int(image_width * 0.1) wrap_width = int(font_size * 0.8) - elif IMAGE_WIDTH <= 1000: - font_size = int(IMAGE_WIDTH * 0.05) + elif image_width <= 1000: + font_size = int(image_width * 0.05) wrap_width = int(font_size * 0.8) else: - font_size = int(IMAGE_WIDTH * 0.03) + font_size = int(image_width * 0.03) wrap_width = int(font_size * 0.8) text_wrap = textwrap.wrap(user_data, width=wrap_width) - font = ImageFont.truetype('fonts/AdverGothicC.ttf', font_size) + font = ImageFont.truetype("fonts/AdverGothicC.ttf", font_size) - current_height, padding = IMAGE_HEIGHT - font_size, 10 + current_height, padding = image_height - font_size, 10 - generalized_color = get_image_general_color(used_image, IMAGE_WIDTH, IMAGE_HEIGHT) + generalized_color = get_image_general_color(used_image, image_width, image_height) if clr_choice: font_color = visible_color(generalized_color) else: @@ -87,14 +82,14 @@ def create_new_photo_auto_config(clr_choice: bool, user_data=' '): for new_line in text_wrap[::-1]: text_width, text_height = draw.textsize(new_line, font=font) draw.text( - ((IMAGE_WIDTH - text_width) / 2, current_height), + ((image_width - text_width) / 2, current_height), new_line, font=font, - fill=font_color + fill=font_color, ) current_height -= text_height + padding - used_image.save('imgs/result_auto_conf.jpg') + used_image.save("imgs/result_auto_conf.jpg") def change_size_statement(new_width: int, new_height: int, image: Image) -> Image: @@ -123,16 +118,21 @@ def get_image_general_color(image: Image, width, height) -> tuple: sum_blue = 0 for color_counter, color in enumerate(colors, 1): - color_coeff = 1 + (color[0] / full_pixels_num) - sum_red += int(color[1][0] * color_coeff) - sum_green += int(color[1][1] * color_coeff) - sum_blue += int(color[1][2] * color_coeff) + color_coefficient = 1 + (color[0] / full_pixels_num) + sum_red += int(color[1][0] * color_coefficient) + sum_green += int(color[1][1] * color_coefficient) + sum_blue += int(color[1][2] * color_coefficient) color_number = len(colors) * 2 - result_color = sum_red // color_number, sum_green // color_number, sum_blue // color_number + result_color = ( + sum_red // color_number, + sum_green // color_number, + sum_blue // color_number, + ) return result_color + def visible_color(background: tuple) -> tuple: red_clr = 255 - background[0] green_clr = 255 - background[1] @@ -143,7 +143,9 @@ def visible_color(background: tuple) -> tuple: def change_color(background: tuple) -> tuple: - brightness = (background[0] * 299 + background[1] * 587 + background[2] * 114) // 1000 + brightness = ( + background[0] * 299 + background[1] * 587 + background[2] * 114 + ) // 1000 if brightness < 128: rand_col_1 = random.randint(128, 255) @@ -155,4 +157,3 @@ def change_color(background: tuple) -> tuple: rand_col_3 = random.randint(0, 127) return rand_col_1, rand_col_2, rand_col_3 - diff --git a/bot/bot_main/for_photo_creation/validate_args_number.py b/bot/commands/commands_utils/for_photo_creation/validate_args_number.py similarity index 69% rename from bot/bot_main/for_photo_creation/validate_args_number.py rename to bot/commands/commands_utils/for_photo_creation/validate_args_number.py index a74f321..b86c07f 100644 --- a/bot/bot_main/for_photo_creation/validate_args_number.py +++ b/bot/commands/commands_utils/for_photo_creation/validate_args_number.py @@ -1,5 +1,5 @@ -from bot.bot_main.for_photo_creation.photo_size import get_data_from_txt -from bot.bot_main.for_photo_creation.remake_user_photo import create_new_photo +from .photo_size import get_data_from_txt +from .remake_user_photo import create_new_photo async def validate_args(formatted_lst, message): @@ -20,7 +20,7 @@ async def validate_args(formatted_lst, message): pos_y=int(formatted_lst[2]), img_font=int(formatted_lst[3]), ) - elif len(formatted_lst) == 2 and check_for_int_type(formatted_lst[0], formatted_lst[1]): + elif len(formatted_lst) == 2 and check_for_digit(formatted_lst[0]) and check_for_digit(formatted_lst[1]): get_error_state = create_new_photo( img_width=int(formatted_lst[0]), img_height=int(formatted_lst[1]), @@ -43,28 +43,25 @@ async def validate_args(formatted_lst, message): if get_error_state: await message.reply( - f'Coordinate X or Y are out of image bounds! Try again.\n{get_data_from_txt()}', parse_mode='HTML' + f"Coordinate X or Y are out of image bounds! Try again.\n{get_data_from_txt()}", + parse_mode="HTML", ) return False except ValueError: await message.reply( - f'One or more arguments has incorrect value! Try again.\n{get_data_from_txt()}', parse_mode='HTML' + f"One or more arguments has incorrect value! Try again.\n{get_data_from_txt()}", + parse_mode="HTML", ) return False except IndexError: await message.reply( - f'Your input is incorrect! Try again.\n{get_data_from_txt()}', parse_mode='HTML' + f"Your input is incorrect! Try again.\n{get_data_from_txt()}", + parse_mode="HTML", ) return False return True -def check_for_int_type(str1: str, str2: str) -> bool: - for char in str1, str2: - if char.isdigit(): - continue - else: - return False - - return True +def check_for_digit(str_arg: str) -> bool: + return all(char.isdigit() for char in str_arg) diff --git a/bot/commands/commands_utils/generate_password.py b/bot/commands/commands_utils/generate_password.py new file mode 100644 index 0000000..a3ab546 --- /dev/null +++ b/bot/commands/commands_utils/generate_password.py @@ -0,0 +1,15 @@ +from random import choices +from string import punctuation + + +def exclude_invalid_symbols_for_markup() -> str: + excluded_symbols = '<>&"' + allowed_symbols = "".join( + [char for char in punctuation if char not in excluded_symbols] + ) + + return allowed_symbols + + +def main_generation(password_alphabet: str, password_length: int) -> str: + return "".join(choices(password_alphabet, k=password_length)) diff --git a/bot/commands/commands_utils/get_days_and_date_num.py b/bot/commands/commands_utils/get_days_and_date_num.py new file mode 100644 index 0000000..20e66f1 --- /dev/null +++ b/bot/commands/commands_utils/get_days_and_date_num.py @@ -0,0 +1,25 @@ +import re +from typing import Tuple, List + + +def extract_from_user_input_days_num(user_input: str) -> int: + days_num_pattern = re.compile("[1-9]+[0-9]*") + + days_num = re.findall(days_num_pattern, user_input) + return int(days_num[0]) + + +def extract_from_user_input_days_and_date(user_input: str) -> Tuple[int, List[int]]: + days_num = extract_from_user_input_days_num(user_input) + date_pattern = re.compile( + "([1-9]|0[1-9]|[1-2][0-9]|3[0-1]).([1-9]|0[1-9]|1[0-2]).([1-9]+.*[0-9]+)" + ) + + result_date = re.findall(date_pattern, user_input) + + int_list = [] + for date_element in result_date[0]: + int_list.append(int(date_element)) + result_date = int_list + + return days_num, result_date diff --git a/bot/commands/commands_utils/get_id_and_convert.py b/bot/commands/commands_utils/get_id_and_convert.py new file mode 100644 index 0000000..f8d079f --- /dev/null +++ b/bot/commands/commands_utils/get_id_and_convert.py @@ -0,0 +1,2 @@ +def get_id_from_str(table_name): + return int("".join(decimal for decimal in table_name if decimal.isdecimal())) diff --git a/bot/other_functions/message_delete_exception.py b/bot/commands/commands_utils/message_delete_exception.py similarity index 100% rename from bot/other_functions/message_delete_exception.py rename to bot/commands/commands_utils/message_delete_exception.py diff --git a/bot/other_functions/non_admin_message_filter.py b/bot/commands/commands_utils/non_admin_message_filter.py similarity index 81% rename from bot/other_functions/non_admin_message_filter.py rename to bot/commands/commands_utils/non_admin_message_filter.py index 7570296..5dff998 100644 --- a/bot/other_functions/non_admin_message_filter.py +++ b/bot/commands/commands_utils/non_admin_message_filter.py @@ -1,17 +1,17 @@ -from bot.bot_main.main_objects_initialization import bot +from bot.config import bot def get_admin_ids(administrators_list) -> list: administrators_id_list = [] for admin in range(len(administrators_list)): - administrators_id_list.append(administrators_list[admin]['user']['id']) + administrators_id_list.append(administrators_list[admin]["user"]["id"]) return administrators_id_list async def delete_non_admin_message(message): - if message.chat.type == 'supergroup': + if message.chat.type == "supergroup": administrators_list = await bot.get_chat_administrators(message.chat.id) administrators_id_list = get_admin_ids(administrators_list) diff --git a/bot/commands/commands_utils/parse_temprature.py b/bot/commands/commands_utils/parse_temprature.py new file mode 100644 index 0000000..9464726 --- /dev/null +++ b/bot/commands/commands_utils/parse_temprature.py @@ -0,0 +1,105 @@ +import re +import requests +from bs4 import BeautifulSoup as Bs +from typing import List, Tuple, Any + + +def parse_temp_at_time(url: str) -> str: + r = requests.get(url) + html = Bs(r.content, "html.parser") + time = html.select(".imgBlock p") + temp_at_time = html.select(".today-temp") + + return time[0].text + ": " + temp_at_time[0].text + + +def parse_minmax_temp(url: str) -> str: + r = requests.get(url) + html = Bs(r.content, "html.parser") + min_temp = html.select(".min span") + max_temp = html.select(".max span") + + return ( + "Min temperature: " + + min_temp[0].text + + "C\n" + + "Max temperature: " + + max_temp[0].text + + "C\n" + ) + + +def parse_today_temp(url: str) -> list: + r = requests.get(url) + html = Bs(r.content, "html.parser") + + today_temp = html.select(".temperature td") + + new_list = [] + for td in today_temp: + new_list.append(td.text) + + return new_list + + +def list_of_tuples(url: str) -> List[Tuple[Any, Any]]: + initial_list = parse_today_temp(url) + new_list = [ + (initial_list[i - 1], initial_list[i]) for i in range(1, len(initial_list), 2) + ] + return new_list + + +def parse_average_precipitation_probability(url: str) -> int: + r = requests.get(url) + html = Bs(r.content, "html.parser") + + precipitation_probability = html.select("tr:nth-child(8) td") + count = 0 + temp = 0 + precipitation_probability_list = [] + for td in precipitation_probability: + if td.text == "-": + precipitation_probability_list.append(0) + temp = temp + else: + precipitation_probability_list.append(int(td.text)) + temp += int(td.text) + count += 1 + + return max(precipitation_probability_list) + + +def check_for_negative_temp(temperature: float) -> str: + if temperature > 0: + return "+" + str(temperature) + "°C" + else: + return str(temperature) + "°C" + + +def find_average_temp_between_two(url: str) -> List[str]: + temp_lst = list_of_tuples(url) + result_list = [] + + for i in temp_lst: + sum_of_two = 0 + + for j in range(2): + result = int(re.search(r"-*\d+", i[j]).group()) + sum_of_two += result + + result_list.append(check_for_negative_temp(sum_of_two / 2)) + + return result_list + + +def average_day_temp(url: str) -> str: + average_temp = 0 + count = 0 + for i in parse_today_temp(url): + average_temp += int(re.search(r"-*\d+", i).group()) + count += 1 + + return ( + "Average temperature: " + check_for_negative_temp(average_temp / count) + "\n\n" + ) diff --git a/bot/commands/commands_utils/password_content_callbacks.py b/bot/commands/commands_utils/password_content_callbacks.py new file mode 100644 index 0000000..8c66f81 --- /dev/null +++ b/bot/commands/commands_utils/password_content_callbacks.py @@ -0,0 +1,134 @@ +from aiogram import types + +from bot.config import dp +from bot.keyboards.password_generator.radio_keyboard import ( + first_generator_keyboard, + all_button, + letters_button, + digits_button, + let_dig_button, + let_sig_button, + dig_sig_button, +) +from bot.commands.commands_utils.change_state_mem_storage import update_state + + +def default_content_keyboard(): + all_button.text = "All characters" + letters_button.text = "Only letters" + digits_button.text = "Only digits" + let_dig_button.text = "Letters & digits" + let_sig_button.text = "Letters & signs" + dig_sig_button.text = "Digits & signs" + + +@dp.callback_query_handler(text=["all_characters"]) +async def option_all_characters(call: types.CallbackQuery): + result = await update_state(call) + + await call.answer("Now your password can contain all possible characters!", True) + + if all_button.text == "All characters ✅": + pass + else: + all_button.text = "All characters ✅" + letters_button.text = "Only letters" + digits_button.text = "Only digits" + let_dig_button.text = "Letters & digits" + let_sig_button.text = "Letters & signs" + dig_sig_button.text = "Digits & signs" + + await call.message.edit_reply_markup(first_generator_keyboard) + + +@dp.callback_query_handler(text=["only_letters"]) +async def option_only_letters(call: types.CallbackQuery): + result = await update_state(call) + await call.answer( + "Now your password can contain only small and big english letters!", True + ) + + if letters_button.text == "Only letters ✅": + pass + else: + all_button.text = "All characters" + letters_button.text = "Only letters ✅" + digits_button.text = "Only digits" + let_dig_button.text = "Letters & digits" + let_sig_button.text = "Letters & signs" + dig_sig_button.text = "Digits & signs" + + await call.message.edit_reply_markup(first_generator_keyboard) + + +@dp.callback_query_handler(text=["only_digits"]) +async def option_only_digits(call: types.CallbackQuery): + result = await update_state(call) + await call.answer("Now your password can contain only digits!", True) + + if digits_button.text == "Only digits ✅": + pass + else: + all_button.text = "All characters" + letters_button.text = "Only letters" + digits_button.text = "Only digits ✅" + let_dig_button.text = "Letters & digits" + let_sig_button.text = "Letters & signs" + dig_sig_button.text = "Digits & signs" + + await call.message.edit_reply_markup(first_generator_keyboard) + + +@dp.callback_query_handler(text=["letters_digits"]) +async def option_letters_digits(call: types.CallbackQuery): + result = await update_state(call) + await call.answer("Now your password can contain english letters and digits!", True) + + if let_dig_button.text == "Letters & digits ✅": + pass + else: + all_button.text = "All characters" + letters_button.text = "Only letters" + digits_button.text = "Only digits" + let_dig_button.text = "Letters & digits ✅" + let_sig_button.text = "Letters & signs" + dig_sig_button.text = "Digits & signs" + + await call.message.edit_reply_markup(first_generator_keyboard) + + +@dp.callback_query_handler(text=["letters_signs"]) +async def option_letters_signs(call: types.CallbackQuery): + result = await update_state(call) + await call.answer("Now your password can contain all letters and signs!", True) + + if let_sig_button.text == "Letters & signs ✅": + pass + else: + all_button.text = "All characters" + letters_button.text = "Only letters" + digits_button.text = "Only digits" + let_dig_button.text = "Letters & digits" + let_sig_button.text = "Letters & signs ✅" + dig_sig_button.text = "Digits & signs" + + await call.message.edit_reply_markup(first_generator_keyboard) + + +@dp.callback_query_handler(text=["digits_signs"]) +async def option_digits_signs(call: types.CallbackQuery): + result = await update_state(call) + + await call.answer("Now your password can contain digits and signs!", True) + + if dig_sig_button.text == "Digits & signs ✅": + pass + else: + all_button.text = "All characters" + letters_button.text = "Only letters" + digits_button.text = "Only digits" + let_dig_button.text = "Letters & digits" + let_sig_button.text = "Letters & signs" + dig_sig_button.text = "Digits & signs ✅" + + await call.message.edit_reply_markup(first_generator_keyboard) diff --git a/bot/commands/commands_utils/password_length_callbacks_handler.py b/bot/commands/commands_utils/password_length_callbacks_handler.py new file mode 100644 index 0000000..419b814 --- /dev/null +++ b/bot/commands/commands_utils/password_length_callbacks_handler.py @@ -0,0 +1,85 @@ +from typing import Coroutine, Any, Dict + +from aiogram import types +from aiogram.types import InlineKeyboardButton + +from bot.config import dp +from bot.keyboards.password_generator.set_length_keyboard import ( + very_easy_button, + easy_button, + normal_button, + hard_button, + very_hard_button, + unbreakable_button, + second_generator_keyboard, +) +from bot.commands.commands_utils.change_state_mem_storage import update_with_length_state + + +class LengthController: + BUTTON_OPTIONS: Dict[InlineKeyboardButton, str] = { + very_easy_button: "Very easy", + easy_button: "Easy", + normal_button: "Normal", + hard_button: "Hard", + very_hard_button: "Very hard", + unbreakable_button: "Unbreakable", + } + OPTION_SELECTED_SYMBOL: str = " ✅" + EMPTY_STRING: str = "" + + async def select_option( + self, + call: types.CallbackQuery, + result: Coroutine[Any, Any, dict | None], + button: InlineKeyboardButton, + ) -> None: + if result is not None and self.OPTION_SELECTED_SYMBOL not in button.text: + for button_option in self.BUTTON_OPTIONS: + normal_option = self.try_get_option(button_option) + self.check_for_selection(button_option, button, normal_option) + await call.message.edit_reply_markup(second_generator_keyboard) + + def check_for_selection( + self, + current_button: InlineKeyboardButton, + selected_button: InlineKeyboardButton, + option_data: str, + ) -> None: + if current_button != selected_button: + current_button.text = option_data + else: + current_button.text = option_data + self.OPTION_SELECTED_SYMBOL + + def back_to_default_keyboard(self) -> None: + for button_option in self.BUTTON_OPTIONS: + button_option.text = self.try_get_option(button_option) + + def try_get_option(self, chosen_option: InlineKeyboardButton) -> str: + try: + return self.BUTTON_OPTIONS[chosen_option] + except KeyError: + return chosen_option.text.replace( + self.OPTION_SELECTED_SYMBOL, self.EMPTY_STRING + ) + + +@dp.callback_query_handler( + lambda call: call.data + in ["very_easy", "easy", "normal", "hard", "very_hard", "unbreakable"] +) +async def handle_difficulty_option(call: types.CallbackQuery): + button_mapping = { + "very_easy": very_easy_button, + "easy": easy_button, + "normal": normal_button, + "hard": hard_button, + "very_hard": very_hard_button, + "unbreakable": unbreakable_button, + } + + option = call.data + result = await update_with_length_state(call) + length_controller = LengthController() + button = button_mapping.get(option) + await length_controller.select_option(call, result, button) diff --git a/bot/commands/commands_utils/process_json.py b/bot/commands/commands_utils/process_json.py new file mode 100644 index 0000000..3120e3b --- /dev/null +++ b/bot/commands/commands_utils/process_json.py @@ -0,0 +1,77 @@ +import json +import os +from typing import List, Tuple, Any + +from aiogram import types + +from .encryption_decryption import decrypt + +NUMBER_OF_JSON_IDENTS = 12 +JSON_DIR = "temp_data" + + +def make_json_dir() -> None: + if not os.path.exists(JSON_DIR): + os.makedirs(JSON_DIR) + + +def remove_json(call: types.CallbackQuery) -> None: + path_to_json = os.path.join(JSON_DIR, f"json_{call.from_user.id}.json") + + if os.path.exists(path_to_json): + os.remove(path_to_json) + + +def write_to_json(user_id: int, table_rows: List[Tuple[Any]]) -> None: + data = [ + { + "Password №": password_number, + "ID": password_data[0], + "Password description": password_data[1], + "Password": decrypt(password_data[2]), + "Length": password_data[3], + "Has repetitive?": password_data[4], + } + for password_number, password_data in enumerate(table_rows, 1) + ] + + file_path = os.path.join(JSON_DIR, f"json_{user_id}.json") + make_json_dir() + + with open(file_path, "w") as outfile: + json.dump(data, outfile, indent=NUMBER_OF_JSON_IDENTS, ensure_ascii=False) + + +def read_json(user_id: int) -> bytes: + path_to_json = os.path.join(JSON_DIR, f"json_{user_id}.json") + with open(path_to_json, "rb") as json_file: + json_content = json_file.read() + + return json_content + + +async def table_is_empty_message(call: types.CallbackQuery) -> types.Message: + sent_message = await call.message.edit_text( + text="Table has no records. So, JSON is empty.", + parse_mode="HTML", + ) + await call.message.edit_reply_markup(reply_markup=call.message.reply_markup) + return sent_message + + +async def json_message(call: types.CallbackQuery, json_content: bytes) -> types.Message: + sent_message = await call.bot.send_document( + chat_id=call.message.chat.id, + document=("Passwords.json", json_content), + caption=f"Json file for @{call.from_user.username}", + parse_mode="HTML", + ) + return sent_message + + +async def send_json(call: types.CallbackQuery, user_id: int, data_list: List[Tuple[Any]]) -> types.Message: + make_json_dir() + write_to_json(user_id, data_list) + json_data = read_json(user_id) + + return await json_message(call, json_data) diff --git a/bot/commands/commands_utils/states.py b/bot/commands/commands_utils/states.py new file mode 100644 index 0000000..fcbc750 --- /dev/null +++ b/bot/commands/commands_utils/states.py @@ -0,0 +1,36 @@ +from aiogram.dispatcher.filters.state import StatesGroup, State + + +class ConverterForm(StatesGroup): + dollar = State() + euro = State() + hryvnia = State() + + +class DaysToBirthday(StatesGroup): + day_month = State() + + +class PasswordGeneratorStates(StatesGroup): + update_description = State() + update_password = State() + delete = State() + set_description = State() + + +class PhotoInscription(StatesGroup): + user_inscription_config = State() + + +class SearchTerm(StatesGroup): + search_term = State() + + +class TaskScheduler(StatesGroup): + insert = State() + update = State() + delete = State() + + +class WeatherInfo(StatesGroup): + place = State() diff --git a/bot/commands/currency_converter_command.py b/bot/commands/currency_converter_command.py new file mode 100644 index 0000000..c5426a4 --- /dev/null +++ b/bot/commands/currency_converter_command.py @@ -0,0 +1,141 @@ +from typing import Dict, Union, Tuple, List + +from aiogram import types +from aiogram.dispatcher import FSMContext + +from .commands_utils.currency_converter_reply_builder import currency_reply +from .commands_utils.currency_cost import Converter +# from .commands_utils.currency_cost import get_currency_rate_data +from .commands_utils.states import ConverterForm +from .commands_utils import currency_cost as cc +from ..config import dp, CURRENCY_URL +from ..enums import Command +from ..enums.currencies import Currency +# from ..enums.currencies import CurrencyType +from ..enums.keyboard_callbacks import ConverterCallbackStorage +from ..keyboards.currency_converter_keyboard import main_converter_menu, currency_rate_menu, convert_menu +from ..keyboards.keyboard_close import close_keyboard + + +@dp.message_handler(state="*", commands=Command.CURRENCY_CONVERTER) +async def currency_command_handler(message: types.Message): + await message.reply("Choose menu option💵:", reply_markup=main_converter_menu) + + +@dp.callback_query_handler(text=ConverterCallbackStorage.CURRENCY_RATE) +async def process_exchange_rate(call: types.CallbackQuery): + rate_data = cc.get_currency_rate_data(CURRENCY_URL) + # bitcoin = get_currency_rate_data(BITCOIN_URL) + + await call.message.edit_text( + f"BUY / SALE\n" + f"" + f"1 UAH = {cc.find_dollars_buy_in_hryvnias(rate_data):.4f} / {cc.find_dollars_sale_in_hryvnias(rate_data):.4f} USD\n" + f"1 UAH = {cc.find_euros_buy_in_hryvnias(rate_data):.4f} / {cc.find_euros_sale_in_hryvnias(rate_data):.4f} EUR\n" + f"1 EUR = {cc.find_dollars_buy_in_euros(rate_data):.4f} / {cc.find_dollars_sale_in_euros(rate_data):.4f} USD\n" + f"1 EUR = {cc.find_hryvnias_buy_in_euros(rate_data):.4f} / {cc.find_hryvnias_sale_in_euros(rate_data):.4f} UAH\n" + f"1 USD = {cc.find_euros_buy_in_dollars(rate_data):.4f} / {cc.find_euros_sale_in_dollars(rate_data):.4f} EUR\n" + f"1 USD = {cc.find_hryvnias_buy_in_dollars(rate_data):.4f} / {cc.find_hryvnias_sale_in_dollars(rate_data):.4f} UAH\n" + # f"1 BTC = {cc.bitcoin_price_in_dollars(bitcoin):.4f} USD\n" + # f"1 BTC = {cc.bitcoin_price_in_euros(bitcoin):.4f} EUR\n" + f"", + parse_mode="HTML", + reply_markup=currency_rate_menu, + ) + await call.answer() + + +@dp.callback_query_handler(text=ConverterCallbackStorage.CURRENCY_CONVERTER) +async def process_converter(call: types.CallbackQuery): + await call.message.edit_text("Currency converter:", reply_markup=convert_menu) + await call.answer() + + +@dp.callback_query_handler(text=ConverterCallbackStorage.CONVERT_DOLLAR) +async def convert_dollar(call: types.CallbackQuery): + await ConverterForm.dollar.set() + await call.answer( + "Enter the amount of $ to convert into euros and hryvnias...", True + ) + + +@dp.message_handler(state=ConverterForm.dollar) +async def process_dollar(message: types.Message, state: FSMContext): + ... + # rate_data = get_currency_rate_data(CURRENCY_URL) + # + # data: Dict[str, Union[Currency, Tuple[Currency, Currency], Tuple[float, float]]] = { + # "main_currency": "dollars", + # "exchange_currency": ("hryvnias", "euros"), + # "buy_price": (cc.find_dollars_buy_in_hryvnias(rate_data), cc.find_dollars_buy_in_euros(rate_data)), + # "sell_price": (cc.find_dollars_sale_in_hryvnias(rate_data), cc.find_dollars_sale_in_euros(rate_data)), + # } + # await process_currency(message, state, reply_data=data) + + +@dp.callback_query_handler(text=ConverterCallbackStorage.CONVERT_EURO) +async def convert_euro(call: types.CallbackQuery): + await ConverterForm.euro.set() + await call.answer( + "Enter the amount of € to convert to dollars and hryvnias...", True + ) + + +@dp.message_handler(state=ConverterForm.euro) +async def process_euro(message: types.Message, state: FSMContext): + ... + # rate_data = get_currency_rate_data(CURRENCY_URL) + # + # data: Dict[str, Union[Currency, Tuple[Currency, Currency], Tuple[float, float]]] = { + # "main_currency": "euros", + # "exchange_currency": ("hryvnias", "dollars"), + # "buy_price": (cc.find_euros_buy_in_hryvnias(rate_data), cc.find_euros_buy_in_dollars(rate_data)), + # "sell_price": (cc.find_euros_sale_in_hryvnias(rate_data), cc.find_euros_sale_in_dollars(rate_data)), + # } + # await process_currency(message, state, reply_data=data) + + +@dp.callback_query_handler(text=ConverterCallbackStorage.CONVERT_HRYVNIA) +async def convert_hryvnia(call: types.CallbackQuery): + await ConverterForm.hryvnia.set() + await call.answer("Enter the amount of ₴ to convert to dollars and euros...", True) + + +@dp.message_handler(state=ConverterForm.hryvnia) +async def process_hryvnia(message: types.Message, state: FSMContext): + await process_currency(message, state, base_currency=Currency.HRYVNIAS) + + +async def process_currency( + message: types.Message, + state: FSMContext, + base_currency: Currency +) -> None: + input_data = message.text + + try: + input_data = input_data.replace(",", ".") + currency_amount = float(input_data) + reply_dict = Converter().convert(base_currency) + reply_data = currency_reply( + currency_amount, + input_data, + *reply_dict.values(), + ) + + await message.reply(reply_data) + except ValueError: + await message.reply("Invalid input. Must be a number. Try again.") + + await state.finish() + + +@dp.callback_query_handler(text=ConverterCallbackStorage.MAIN_CONVERTER_MENU_BACK) +async def back_to_main_converter_menu(call: types.CallbackQuery): + await call.message.edit_text("Choose menu option💵:", reply_markup=main_converter_menu) + await call.answer() + + +@dp.callback_query_handler(text=ConverterCallbackStorage.CLOSE_CONVERTER) +async def close_converter(call: types.CallbackQuery): + await close_keyboard(call) diff --git a/bot/commands/date_calculator_command.py b/bot/commands/date_calculator_command.py new file mode 100644 index 0000000..13f2f2c --- /dev/null +++ b/bot/commands/date_calculator_command.py @@ -0,0 +1,46 @@ +import time +from datetime import date + +from aiogram import types +from aiogram.dispatcher import FSMContext + +from .commands_utils.states import DaysToBirthday +from ..config import dp +from ..enums import Command +from bot.keywords.keyword_utils.check_date_words import check_year, check_month, check_day + + +@dp.message_handler(state="*", commands=Command.DATE_CALCULATOR) +async def date_calculator_command_handler(message: types.Message): + await DaysToBirthday.day_month.set() + await message.reply("Enter date of birth (or other date) in format dd.mm.yyyy...") + + +@dp.message_handler(state=DaysToBirthday.day_month) +async def process_date_calculator_command(message: types.Message, state: FSMContext): + input_data = message.text + input_data = input_data.split(".", 2) + current_year = time.localtime().tm_year + current_month = time.localtime().tm_mon + current_day = time.localtime().tm_mday + + try: + answer = abs( + date(current_year, current_month, current_day) + - date(int(input_data[2]), int(input_data[1]), int(input_data[0])) + ) + + years = int(answer.days / 365) + months = int((answer.days % 365) / 31) + days = answer.days - years * 365 - months * 31 + await message.reply( + f"The number of days between the entered date and the current date: {answer.days}\n" + f"Formatted date: " + f"{years} {check_year(years)}, {months} {check_month(months)}, {days} {check_day(days)}" + ) + except ValueError: + await message.reply("Your input was incorrect! Try again.") + except IndexError: + await message.reply("Your input was incorrect! Try again.") + + await state.finish() diff --git a/bot/commands/help_command.py b/bot/commands/help_command.py new file mode 100644 index 0000000..f99b70b --- /dev/null +++ b/bot/commands/help_command.py @@ -0,0 +1,10 @@ +from aiogram import types + +from ..config import dp +from ..config import COMMANDS_DESCRIPTION +from ..enums import Command + + +@dp.message_handler(state="*", commands=Command.HELP) +async def help_command_handler(message: types.Message): + await message.reply(text=COMMANDS_DESCRIPTION, parse_mode="HTML") diff --git a/bot/commands/keywords_command.py b/bot/commands/keywords_command.py new file mode 100644 index 0000000..1984eb3 --- /dev/null +++ b/bot/commands/keywords_command.py @@ -0,0 +1,31 @@ +from aiogram import types + +from ..config import dp +from ..enums import Command + + +@dp.message_handler(state="*", commands=Command.KEYWORD_LIST) +async def get_keywords(message: types.Message): + await message.reply( + "bitcoinget bitcoin price\n\n" + "dtr stickeradd your sticker to bot database\n\n" + "dtr examplesend you few examples how to use /photo\n\n" + "msgddeletes the bot message you replied to and your message\n\n" + 'random mem any_textsend me an image with caption where must be words "random mem" ' + "and after this words you can type anything. All what you will type after keyword will be printed " + "on your image and sent to you back. Font color will be randomly generated.\n\n" + 'mem any_textsend me an image with caption where must be word "mem" ' + "and after this word you can type anything. All what you will type after keyword will be printed " + "on your image and sent to you. Font color for the image will be automatically set by the system " + "depending on the colors that the image contains.\n\n" + "What date will it be after N days?send you a message with a date that will " + "be after N days from the current date in format dd.mm.yyyy\n\n" + 'What date will be after N days if we start from "your_date"?send you a message with a date ' + 'that will be after N days from "your_date" ' + "(must be in the same format as in the result) in format dd.mm.yyyy\n\n" + "dtr delete tasksdeletes the table and all its contents created using the command " + "/taskscheduler and creates a new empty task table\n\n" + "dtr delete passwordsdeletes the table and all its contents created using the command " + "/password and creates a new empty password table\n\n", + parse_mode="HTML", + ) diff --git a/bot/commands/password_generator_command.py b/bot/commands/password_generator_command.py new file mode 100644 index 0000000..b4af3d4 --- /dev/null +++ b/bot/commands/password_generator_command.py @@ -0,0 +1,623 @@ +import asyncio +import mysql.connector +import aiogram.utils.exceptions +from aiogram import types +from aiogram.dispatcher import FSMContext +from aiogram.types import CallbackQuery + +from .commands_utils.states import PasswordGeneratorStates +from .commands_utils.generate_password import main_generation + +from .commands_utils.password_content_callbacks import ( + default_content_keyboard, +) +from .commands_utils.password_length_callbacks_handler import ( + LengthController, +) +from .commands_utils import check_for_comma_and_dot, check_for_id_in_table +from .commands_utils.check_for_repetitive_characters import ( + check_if_repeatable_characters_is_present, +) +from .commands_utils.check_length_keyboard import keyboard_length_choice +from .commands_utils.check_radio_keyboard import keyboard_content_choice +from .commands_utils.encryption_decryption import encrypt, decrypt +from .commands_utils.process_json import send_json, remove_json, table_is_empty_message +from .commands_utils.message_delete_exception import message_delete_control +from ..config import ( + dp, + bot, + unique_table, +) +from ..enums import Command +from ..keyboards.keyboard_close import close_keyboard +from ..keyboards.password_generator.back_keyboard import ( + back_to_telegram_generator_keyboard, +) +from ..keyboards.password_generator.download_app_keyboard import download_app_keyboard +from ..keyboards.password_generator.generation_keyboard import ( + third_generator_keyboard, +) +from ..keyboards.password_generator.radio_keyboard import first_generator_keyboard +from ..keyboards.password_generator.set_length_keyboard import ( + second_generator_keyboard, +) +from ..keyboards.password_generator.main_keyboard import password_start_keyboard +from ..keyboards.password_generator.return_to_update_keyboard import ( + password_generator_return_to_update, +) +from ..keyboards.password_generator.telegram_keyboard import ( + password_telegram_keyboard, +) +from ..keyboards.password_generator.update_keyboard import ( + password_generator_update_keyboard, +) + +DELETE_TIMEOUT = 60 + + +@dp.message_handler(state="*", commands=Command.PASSWORD_GENERATOR) +async def password(message: types.Message): + await message.reply( + "Welcome to password generator 🔒\n\n" + "" + "As you can see below, there are two buttons. By clicking on the first button, " + "you will go to the password generator embedded in me. After clicking the second button, " + "you will be presented with options to download the application to your local machine." + "", + reply_markup=password_start_keyboard, + parse_mode="HTML", + ) + + +@dp.callback_query_handler(text=["telegram_password"]) +async def telegram_password(call: CallbackQuery): + await call.message.edit_text( + "Choose menu option:\n\n", + reply_markup=password_telegram_keyboard, + parse_mode="HTML", + ) + + +@dp.callback_query_handler(text=["show_password"]) +async def option_show_passwords(call: types.CallbackQuery): + select_result = unique_table.select_pass_gen_table( + f"pass_gen_table_{call.from_user.id}" + ) + + if not select_result: + await call.message.edit_text( + "Table has no records", reply_markup=back_to_telegram_generator_keyboard + ) + else: + all_passwords = "".join( + f"Password №{password_number}\n" + f"ID: {password_data[0]}\n" + f"Description: {password_data[1]}\n" + f"Password: {decrypt(password_data[2])}\n" + f"Length: {password_data[3]}\n" + f"Has repetitive?: {password_data[4]}\n\n" + for password_number, password_data in enumerate(select_result, 1) + ) + + try: + await call.message.edit_text( + text=all_passwords, + reply_markup=back_to_telegram_generator_keyboard, + parse_mode="HTML", + ) + + await call.answer() + except aiogram.utils.exceptions.BadRequest: + await call.message.edit_text( + text="Your passwords data is too big. I will send you a JSON file.", + reply_markup=back_to_telegram_generator_keyboard, + parse_mode="HTML", + ) + + sent_message = await send_json(call) + + await call.answer() + + await asyncio.sleep(DELETE_TIMEOUT) + await bot.delete_message( + call.message.chat.id, message_id=sent_message.message_id + ) + + +@dp.callback_query_handler(text=["create_password"]) +async def create_password(call: CallbackQuery): + message_text = "Choose what your password can contain:" + + await call.message.edit_text( + text=message_text, parse_mode="HTML", reply_markup=first_generator_keyboard + ) + await call.answer() + + +@dp.callback_query_handler(text=["change_desc_pass"]) +async def change_desc_pass(call: CallbackQuery): + await call.message.edit_text( + "What do you want to change?:\n\n", + reply_markup=password_generator_update_keyboard, + parse_mode="HTML", + ) + + +@dp.callback_query_handler(text=["change_description"]) +async def change_password(call: CallbackQuery): + await PasswordGeneratorStates.update_description.set() + await call.answer( + 'Enter description information in the format: "ID value", "description text"', + True, + ) + await call.message.delete() + + +@dp.message_handler(state=PasswordGeneratorStates.update_description) +async def process_description_update(message: types.Message, state: FSMContext): + try: + user_data = check_for_comma_and_dot.check_user_data(message.text) + user_chosen_id = check_for_comma_and_dot.check_user_id(message.text) + + if int(user_chosen_id) not in check_for_id_in_table.check_for_password_id( + message + ): + raise ValueError + + unique_table.update_password_desc( + f"pass_gen_table_{message.from_user.id}", user_data, int(user_chosen_id) + ) + + await message.answer( + "Data has been changed!", reply_markup=password_generator_return_to_update + ) + except ValueError: + await message.answer( + "Entered data was incorrect!", + reply_markup=password_generator_return_to_update, + ) + except mysql.connector.errors.IntegrityError: + await message.answer( + "Password with such description already exists!", + reply_markup=password_generator_return_to_update, + ) + + await message_delete_control(message) + + await state.finish() + + +@dp.callback_query_handler(text=["change_password"]) +async def change_password(call: CallbackQuery): + await PasswordGeneratorStates.update_password.set() + await call.answer( + 'Enter password information in the format: "ID value", "password text"', True + ) + await call.message.delete() + + +@dp.message_handler(state=PasswordGeneratorStates.update_password) +async def process_password_update(message: types.Message, state: FSMContext): + try: + user_data = check_for_comma_and_dot.check_user_data(message.text) + user_data_length = len(user_data) + repetitive_characters_in_password = check_if_repeatable_characters_is_present( + user_data + ) + user_chosen_id = check_for_comma_and_dot.check_user_id(message.text) + + if int(user_chosen_id) not in check_for_id_in_table.check_for_password_id( + message + ): + raise ValueError + + encrypted_password = encrypt(user_data) + unique_table.update_password( + f"pass_gen_table_{message.from_user.id}", + encrypted_password, + user_data_length, + repetitive_characters_in_password, + int(user_chosen_id), + ) + + await message.answer( + "Data has been changed!", reply_markup=password_generator_return_to_update + ) + except ValueError: + await message.answer( + "Entered data was incorrect!", + reply_markup=password_generator_return_to_update, + ) + except mysql.connector.errors.IntegrityError: + await message.answer( + "Password with such description already exists!", + reply_markup=password_generator_return_to_update, + ) + + await message_delete_control(message) + + await state.finish() + + +@dp.callback_query_handler(text=["delete_password"]) +async def delete_password(call: CallbackQuery): + await PasswordGeneratorStates.delete.set() + await call.answer( + "Enter password ID and all data about that password will be removed.", True + ) + await call.message.delete() + + +@dp.message_handler(state=PasswordGeneratorStates.delete) +async def process_password_update(message: types.Message, state: FSMContext): + try: + if int(message.text) not in check_for_id_in_table.check_for_password_id( + message + ): + raise ValueError + + unique_table.delete_from_password_table( + f"pass_gen_table_{message.from_user.id}", message.text + ) + await message.answer( + "Data deleted successfully!", + reply_markup=back_to_telegram_generator_keyboard, + ) + except ValueError: + await message.answer( + "Password ID was entered incorrectly!", + reply_markup=back_to_telegram_generator_keyboard, + ) + + await message_delete_control(message) + + await state.finish() + + +@dp.callback_query_handler(text=["password_app"]) +async def password_app(call: CallbackQuery): + await call.message.edit_text( + "Choose your system: ", + parse_mode="HTML", + reply_markup=download_app_keyboard, + ) + await call.answer() + + +@dp.callback_query_handler(text=["windows_installer"]) +async def windows_installer(call: CallbackQuery): + with open( + "password_generator_app/Password_Generator_Windows_Setup.zip", "rb" + ) as zip_archive: + zip_content = zip_archive.read() + + sent_message = await call.bot.send_document( + chat_id=call.message.chat.id, + document=("Password_Generator_Windows_Setup.zip", zip_content), + caption="Password for archive: 123454321", + parse_mode="HTML", + ) + + await call.answer() + + await asyncio.sleep(DELETE_TIMEOUT) + await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) + + +@dp.callback_query_handler(text=["linux_macos"]) +async def linux_macos(call: CallbackQuery): + with open( + "password_generator_app/Password_Generator_v2_Linux.zip", "rb" + ) as zip_archive: + zip_content = zip_archive.read() + + sent_message = await call.bot.send_document( + chat_id=call.message.chat.id, + document=("Password_Generator_v2_Linux.zip", zip_content), + caption="Password for archive: 123454321", + parse_mode="HTML", + ) + + await call.answer() + + await asyncio.sleep(DELETE_TIMEOUT) + await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) + + +@dp.callback_query_handler(text=["json_password"]) +async def json_password(call: CallbackQuery): + user_id = call.from_user.id + password_data_list = unique_table.select_pass_gen_table(f"pass_gen_table_{user_id}") + + if not password_data_list: + return await table_is_empty_message(call) + + await call.message.edit_text( + "You can see your JSON file below.", + parse_mode="HTML", + reply_markup=back_to_telegram_generator_keyboard, + ) + + sent_message = await send_json(call, user_id, password_data_list) + remove_json(call) + + await call.answer(cache_time=2) + + await asyncio.sleep(DELETE_TIMEOUT) + await bot.delete_message(call.message.chat.id, message_id=sent_message.message_id) + + +@dp.callback_query_handler(text=["generate_password"]) +async def generate_password(call: types.CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + data = await storage.get_data() + necessary_keys = ["bot_message_id", "password_contains", "password_length"] + user_alphabet = data.get("password_contains") + user_length = data.get("password_length") + + if all(key in data for key in necessary_keys): + alphabet = keyboard_content_choice(user_alphabet) + password_length = keyboard_length_choice(user_length) + generated_password = main_generation(alphabet, password_length) + data.update({"generated_password": generated_password}) + + await call.answer(cache_time=2) + + await call.message.edit_text( + text=f"Your password:\n{generated_password}", parse_mode="HTML" + ) + await call.message.edit_reply_markup(reply_markup=third_generator_keyboard) + await storage.set_data(data) + else: + await call.answer( + "You need to press one of the button what your password could " + "contain and choose password difficulty first!", + True, + ) + + +@dp.callback_query_handler(text=["store_in_db"]) +async def option_set_description(call: types.CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + data = await storage.get_data() + necessary_keys = [ + "bot_message_id", + "password_contains", + "password_length", + "generated_password", + ] + + if all(key in data for key in necessary_keys): + await PasswordGeneratorStates.set_description.set() + await call.answer("Enter description text...", True) + else: + await call.answer( + "You need to press one of the button what your password could " + "contain, choose password difficulty and generate password first!", + True, + ) + + +@dp.message_handler(state=PasswordGeneratorStates.set_description) +async def process_description(message: types.Message, state: FSMContext): + user_desc = message.text + storage = dp.current_state(chat=message.chat.id, user=message.from_user.id) + data = await storage.get_data() + data.update({"description": f"{user_desc}"}) + await storage.set_data(data) + message_id = data["bot_message_id"] + + if "generated_password" in data: + try: + if len(user_desc) > 384: + raise ValueError + + table_name = f"pass_gen_table_{message.from_id}" + user_description = data["description"] + user_password = data["generated_password"] + password_length = keyboard_length_choice(data["password_length"]) + has_repetitive = check_if_repeatable_characters_is_present(user_password) + + await message.delete() + + encrypted_password = encrypt(user_password) + unique_table.insert_password_data( + table_name, + user_description, + encrypted_password, + password_length, + has_repetitive, + ) + + await message.from_user.bot.edit_message_text( + text=f"Password saved successfully!!!\n\n" + f'Your description:\n{data["description"]}\n' + f'Your password:\n{data["generated_password"]}', + chat_id=message.chat.id, + message_id=message_id, + parse_mode="HTML", + ) + await message.from_user.bot.edit_message_reply_markup( + chat_id=message.chat.id, + message_id=message_id, + reply_markup=third_generator_keyboard, + ) + except ValueError: + await message.from_user.bot.edit_message_text( + text="Invalid input!\nToo many characters entered.", + chat_id=message.chat.id, + message_id=message_id, + ) + await message.from_user.bot.edit_message_reply_markup( + chat_id=message.chat.id, + message_id=message_id, + reply_markup=third_generator_keyboard, + ) + except mysql.connector.errors.IntegrityError: + await message.from_user.bot.edit_message_text( + text="Invalid input!\nDescription already in the table!!!", + chat_id=message.chat.id, + message_id=message_id, + ) + await message.from_user.bot.edit_message_reply_markup( + chat_id=message.chat.id, + message_id=message_id, + reply_markup=third_generator_keyboard, + ) + + await state.finish() + await storage.set_data(data) + + +@dp.callback_query_handler(text=["next_to_second"]) +async def next_to_second(call: CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + data = await storage.get_data() + necessary_keys = ["bot_message_id", "password_contains"] + + if "password_length" not in data: + if all(key in data for key in necessary_keys): + await call.message.edit_text( + "Choose the password difficulty:\n\n", + reply_markup=second_generator_keyboard, + parse_mode="HTML", + ) + else: + await call.message.edit_text( + text="Choose what your password can contain:", + parse_mode="HTML", + ) + await call.message.edit_reply_markup(reply_markup=first_generator_keyboard) + await call.answer("You should select any of these before continue!", True) + else: + await call.message.edit_text( + "Choose the password difficulty:\n\n", parse_mode="HTML" + ) + await call.message.edit_reply_markup(reply_markup=second_generator_keyboard) + await call.answer() + + +@dp.callback_query_handler(text=["next_to_third"]) +async def next_to_third(call: CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + data = await storage.get_data() + necessary_keys = ["bot_message_id", "password_contains", "password_length"] + + if "generated_password" in data: + if "description" in data: + await call.message.edit_text( + text=f"Password saved successfully!!!\n\n" + f'Your description:\n{data["description"]}\n' + f'Your password:\n{data["generated_password"]}', + reply_markup=third_generator_keyboard, + parse_mode="HTML", + ) + else: + await call.message.edit_text( + text=f'Your password:\n{data["generated_password"]}', + reply_markup=third_generator_keyboard, + parse_mode="HTML", + ) + else: + if all(key in data for key in necessary_keys): + await call.message.edit_text( + "Generate your password and write it to the database! \n\n", + reply_markup=third_generator_keyboard, + parse_mode="HTML", + ) + else: + await call.message.edit_text( + text="Choose the password difficulty:", parse_mode="HTML" + ) + await call.message.edit_reply_markup(reply_markup=second_generator_keyboard) + await call.answer( + "You need to choose what will contain your password and choose its difficulty!", + True, + ) + + +@dp.callback_query_handler(text=["main_generator_menu"]) +async def main_generator_menu(call: CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + await storage.finish() + + default_content_keyboard() + LengthController().back_to_default_keyboard() + + await call.message.edit_text( + "Choose menu option: \n\n", + reply_markup=password_telegram_keyboard, + parse_mode="HTML", + ) + + +@dp.callback_query_handler(text=["back_to_first"]) +async def back_to_first(call: CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + await storage.get_data() + + message_text = "Choose what your password can contain:" + + await call.message.edit_text( + text=message_text, parse_mode="HTML", reply_markup=first_generator_keyboard + ) + await call.answer() + + +@dp.callback_query_handler(text=["back_to_second"]) +async def back_to_second(call: CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + data = await storage.get_data() + data.get("password_length") + + message_text = "Choose the password difficulty:\n\n" + + await call.message.edit_text(text=message_text, parse_mode="HTML") + await call.message.edit_reply_markup(reply_markup=second_generator_keyboard) + await call.answer() + + +@dp.callback_query_handler(text=["return_to_update_menu"]) +async def return_to_update_menu(call: CallbackQuery): + await call.message.edit_text( + "What do you want to change?:", + parse_mode="HTML", + reply_markup=password_generator_update_keyboard, + ) + await call.answer() + + +@dp.callback_query_handler(text=["back_to_telegram_generator"]) +async def back_to_telegram_generator(call: CallbackQuery): + storage = dp.current_state(chat=call.message.chat.id, user=call.from_user.id) + await storage.finish() + + default_content_keyboard() + LengthController().back_to_default_keyboard() + + await call.message.edit_text( + "Choose menu option: ", + parse_mode="HTML", + reply_markup=password_telegram_keyboard, + ) + await call.answer() + + +@dp.callback_query_handler(text=["back_to_main_menu"]) +async def back_to_main_menu(call: CallbackQuery): + await call.message.edit_text( + "Welcome to password generator 🔒\n\n" + "" + "As you can see below, there are two buttons. By clicking on the first button, " + "you will go to the password generator embedded in me. After clicking the second button, " + "you will be presented with options to download the application to your local machine." + "", + parse_mode="HTML", + reply_markup=password_start_keyboard, + ) + await call.answer() + + +@dp.callback_query_handler(text=["close_app"]) +async def close_app(call: CallbackQuery): + await close_keyboard(call) diff --git a/bot/commands/start_command.py b/bot/commands/start_command.py new file mode 100644 index 0000000..3110630 --- /dev/null +++ b/bot/commands/start_command.py @@ -0,0 +1,13 @@ +from aiogram import types + +from ..config import dp +from ..enums import Command + + +@dp.message_handler(state="*", commands=Command.START) +async def start_command_handler(message: types.Message): + await message.reply( + f"{message.from_user.full_name}, I am activated. " + f"Type commands or keywords. Use /help command to see the list of possible actions.", + parse_mode="HTML" + ) diff --git a/bot/commands/task_scheduler_command.py b/bot/commands/task_scheduler_command.py new file mode 100644 index 0000000..3160bee --- /dev/null +++ b/bot/commands/task_scheduler_command.py @@ -0,0 +1,163 @@ +import mysql.connector + +from aiogram import types +from aiogram.dispatcher import FSMContext + +from .commands_utils.message_delete_exception import message_delete_control +from .commands_utils.states import TaskScheduler +from .commands_utils import check_for_comma_and_dot, check_for_id_in_table +from ..config import dp, unique_table +from ..enums import Command +from ..keyboards.keyboard_close import close_keyboard +from ..keyboards.task_scheduler import back_to_planner_keyboard +from ..keyboards.task_scheduler.scheduler_keyboard import scheduler_keyboard + + +@dp.message_handler(state="*", commands=Command.TASK_SCHEDULER) +async def scheduler_call(message: types.Message): + chat_id = message.chat.id + user_id = message.from_id + username = message.from_user.username + full_name = message.from_user.full_name + # main_objects_initialization.store_users_data.connect_to_db( + # user_id, username, full_name, chat_id + # ) + + await message.reply( + "Choose a task scheduler function:", reply_markup=scheduler_keyboard + ) + + +@dp.callback_query_handler(text=["select"]) +async def option_select(call: types.CallbackQuery): + select_result = unique_table.select_table( + "table_" + str(call.from_user.id) + ) + if not select_result: + await call.message.edit_text( + "Table has no records", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + else: + all_tasks = "".join( + f"ID{task[0]} | Task №{task_number} | {task[1]}\n" + for task_number, task in enumerate(select_result, 1) + ) + + await call.message.edit_text( + all_tasks, reply_markup=back_to_planner_keyboard.return_keyboard + ) + + +@dp.callback_query_handler(text=["insert"]) +async def option_insert(call: types.CallbackQuery): + await TaskScheduler.insert.set() + await call.answer("Enter the task text...", True) + await call.message.delete() + + +@dp.message_handler(state=TaskScheduler.insert) +async def process_insert(message: types.Message, state: FSMContext): + try: + if len(message.text) > 768: + raise ValueError + + unique_table.insert_into_table( + ("table_" + str(message.from_user.id)), message.text + ) + await message.answer( + "Data added successfully!", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + except ValueError: + await message.answer( + "Too many characters entered!", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + + await message_delete_control(message) + + await state.finish() + + +@dp.callback_query_handler(text=["update"]) +async def option_update(call: types.CallbackQuery): + await TaskScheduler.update.set() + await call.answer('Enter information in the format: "ID value", "task text"', True) + await call.message.delete() + + +@dp.message_handler(state=TaskScheduler.update) +async def process_update(message: types.Message, state: FSMContext): + try: + user_data = check_for_comma_and_dot.check_user_data(message.text) + user_id = check_for_comma_and_dot.check_user_id(message.text) + + if int(user_id) not in check_for_id_in_table.check_for_id(message): + raise ValueError + + unique_table.update_table( + f"table_{message.from_user.id}", user_data, int(user_id) + ) + await message.answer( + "Data has been changed!", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + except ValueError: + await message.answer( + "Data is entered incorrectly!", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + except mysql.connector.errors.IntegrityError: + await message.answer( + "You have already added this task", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + + await message_delete_control(message) + + await state.finish() + + +@dp.callback_query_handler(text=["delete"]) +async def option_delete(call: types.CallbackQuery): + await TaskScheduler.delete.set() + await call.answer("Enter the ID of the task you want to delete...", True) + await call.message.delete() + + +@dp.message_handler(state=TaskScheduler.delete) +async def process_delete(message: types.Message, state: FSMContext): + try: + if int(message.text) not in check_for_id_in_table.check_for_id(message): + raise ValueError + + unique_table.delete_from_table( + ("table_" + str(message.from_user.id)), message.text + ) + await message.answer( + "Data deleted successfully!", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + except ValueError: + await message.answer( + "Task ID was entered incorrectly!", + reply_markup=back_to_planner_keyboard.return_keyboard, + ) + + await message_delete_control(message) + + await state.finish() + + +@dp.callback_query_handler(text=["back_to_scheduler"]) +async def back_to_main_menu(call: types.CallbackQuery): + await call.message.edit_text( + "Choose a task scheduler function:", reply_markup=scheduler_keyboard + ) + await call.answer() + + +@dp.callback_query_handler(text=["close_scheduler"]) +async def close_scheduler(call: types.CallbackQuery): + await close_keyboard(call) diff --git a/bot/commands/time_command.py b/bot/commands/time_command.py new file mode 100644 index 0000000..059af02 --- /dev/null +++ b/bot/commands/time_command.py @@ -0,0 +1,31 @@ +from datetime import date, datetime +from aiogram import types + +from ..config import dp +from ..enums import Command + + +@dp.message_handler(state="*", commands=Command.TIME) +async def time_command_handler(message: types.Message): + utc_time = datetime.utcnow() + current_year = utc_time.timetuple().tm_year + current_month = utc_time.timetuple().tm_mon + current_day = utc_time.timetuple().tm_mday + + delta = date(current_year, current_month, current_day) - date(2022, 2, 24) + days_of_unity = date(current_year, current_month, current_day) - date(1919, 1, 21) + + await message.reply( + f'Current date:\t{utc_time.strftime("%d.%m.%Y UTC")} 📅\n' + f'Current time:\t{utc_time.strftime("%H:%M:%S UTC")} 🕔\n' + f'Day of the week:\t{(utc_time.strftime("%A")).upper()}\n' + f"Day of the year:\t{utc_time.timetuple().tm_yday} 🌞\n" + f"Number of days after russian full scale invasion to Ukraine:\t{delta.days + 1} 🕊\n" + f"Day of Unity of Ukraine:\t{days_of_unity.days} 🤝" + ) + + if current_month == 1 and current_day == 22: + anniversary = current_year - 1919 + await message.answer( + f"WOW, {anniversary}TH ANNIVERSARY OF THE UNITY OF UKRAINE" + ) diff --git a/bot/commands/weather_command.py b/bot/commands/weather_command.py new file mode 100644 index 0000000..6a6b419 --- /dev/null +++ b/bot/commands/weather_command.py @@ -0,0 +1,43 @@ +import requests +from aiogram import types +from aiogram.dispatcher import FSMContext +from .commands_utils.states import WeatherInfo +from .commands_utils.parse_temprature import ( + find_average_temp_between_two, + parse_minmax_temp, + average_day_temp, + parse_average_precipitation_probability, +) +from ..config import dp +from ..enums import Command + + +@dp.message_handler(state="*", commands=Command.CURRENT_WEATHER) +async def weather_command_handler(message: types.Message): + await WeatherInfo.place.set() + await message.reply("Enter the name of the town...") + + +@dp.message_handler(state=WeatherInfo.place) +async def process_weather_command(message: types.Message, state: FSMContext): + input_place = message.text + + url = f"https://ua.sinoptik.ua/погода-{input_place}" + answer = requests.get(url) + + if answer.status_code == 200: + temp_list = find_average_temp_between_two(url) + await message.reply( + f"{parse_minmax_temp(url)}" + f"{average_day_temp(url)}" + f"Night temperature: {temp_list[0]}\n" + f"Morning temperature: {temp_list[1]}\n" + f"Day temperature: {temp_list[2]}\n" + f"Evening temperature: {temp_list[3]}\n\n" + f"Maximum chance of precipitation: {parse_average_precipitation_probability(url)}%", + parse_mode="Markdown", + ) + else: + await message.reply("Can not find such town name!") + + await state.finish() diff --git a/bot/config.py b/bot/config.py new file mode 100644 index 0000000..94c81dd --- /dev/null +++ b/bot/config.py @@ -0,0 +1,33 @@ +import os + +from aiogram import Bot, Dispatcher +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from dotenv import load_dotenv, find_dotenv + +from .db_utils.unique_tables_creator import UniqueTablesCreator + +load_dotenv(find_dotenv()) +BOT_TOKEN = os.getenv("API_TOKEN") + +COMMANDS_DESCRIPTION = """ +/start - check if bot is working +/time - current date, time and other time info +/date_calculator - calculate number of days from one date to another +/current_weather - show the weather in the entered settlement +/image_editor - adding text to photo, change photo size +/currency_converter - currency converter +/password_generator - random password generator +/task_scheduler - create a list of tasks +/keyword_list - list of keywords that the bot responds to +/help - command list +""" + +CURRENCY_URL = "https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=11" +BITCOIN_URL = "https://api.coindesk.com/v1/bpi/currentprice.json" + +# PROXY_URL = "http://proxy.server:3128" +# bot = Bot(token=BOT_TOKEN, proxy=PROXY_URL) +bot = Bot(token=BOT_TOKEN) +storage = MemoryStorage() +dp = Dispatcher(bot, storage=storage) +unique_table = UniqueTablesCreator() diff --git a/bot/db_utils/__init__.py b/bot/db_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/db_utils/db_connector.py b/bot/db_utils/db_connector.py new file mode 100644 index 0000000..da8eb35 --- /dev/null +++ b/bot/db_utils/db_connector.py @@ -0,0 +1,14 @@ +import os +import mysql +import mysql.connector + + +class DatabaseConnector: + def __init__(self): + self.con = mysql.connector.connect( + host=os.getenv("HOST"), + user=os.getenv("DB_USER"), + passwd=os.getenv("PASSWORD"), + database=os.getenv("DATABASE"), + ) + self.cur = self.con.cursor() diff --git a/bot/bot_main/bot_classes/UniqueTablesForUsers.py b/bot/db_utils/unique_tables_creator.py similarity index 67% rename from bot/bot_main/bot_classes/UniqueTablesForUsers.py rename to bot/db_utils/unique_tables_creator.py index 261a026..eaf3fc2 100644 --- a/bot/bot_main/bot_classes/UniqueTablesForUsers.py +++ b/bot/db_utils/unique_tables_creator.py @@ -1,35 +1,32 @@ -from bot.bot_main.bot_classes.ConnectionDB import ConnectionDB -from bot.other_functions.get_id_and_convert import get_id_from_str +from .db_connector import DatabaseConnector +from bot.commands.commands_utils.get_id_and_convert import get_id_from_str -class UniqueTablesForUsers(ConnectionDB): +class UniqueTablesCreator(DatabaseConnector): def __init__(self): super().__init__() - self.cur = self.con.cursor() - self.date_id_procedure() - self.month_avg_statistics_view() - + # self.date_id_procedure() + # self.month_avg_statistics_view() def date_id_procedure(self): self.cur.execute( - ''' + """ CREATE PROCEDURE IF NOT EXISTS add_date_and_id(IN get_user_id INT) BEGIN DECLARE date_now DATE; SET date_now = CURDATE(); - + INSERT IGNORE INTO statistics(usage_date, user_id) VALUES (date_now, get_user_id); END - ''' + """ ) self.con.commit() - def month_avg_statistics_view(self): self.cur.execute( - ''' + """ CREATE OR REPLACE VIEW statistics_view AS SELECT u.start_msg_date, (SELECT s2.usage_date @@ -48,181 +45,170 @@ def month_avg_statistics_view(self): JOIN users_info u ON s.user_id = u.user_id WHERE DATEDIFF(s.usage_date, u.start_msg_date) < 31 GROUP BY s.user_id; - ''' + """ ) self.con.commit() - -######################################################################################################################## -#################################################### TASK SCHEDULER #################################################### -######################################################################################################################## + ######################################################################################################################## + #################################################### TASK SCHEDULER #################################################### + ######################################################################################################################## def create_table(self, user_id): self.cur.execute( - ''' + """ CREATE TABLE IF NOT EXISTS `%s` ( id INT PRIMARY KEY AUTO_INCREMENT, user_data VARCHAR(768), CONSTRAINT unique_data UNIQUE(user_data) ) - ''', (user_id,) + """, + (user_id,), ) - def drop_remake_task_table(self, user_id): - table_name = f'table_{user_id}' - self.cur.execute('DROP TABLE IF EXISTS `%s`', (table_name,)) + table_name = f"table_{user_id}" + self.cur.execute("DROP TABLE IF EXISTS `%s`", (table_name,)) self.create_table(table_name) - def trigger_on_delete_task(self, trigger_name, table_name, user_id): # self.cur.execute('DROP TRIGGER IF EXISTS delete_from_task') self.cur.execute( - ''' + """ CREATE TRIGGER IF NOT EXISTS `%s` BEFORE DELETE ON `%s` FOR EACH ROW BEGIN DECLARE date_now DATE; DECLARE get_user_id INT; - + SET date_now = CURDATE(); SET get_user_id = %s; - + UPDATE statistics SET task_sched_delete_num = task_sched_delete_num + 1 WHERE usage_date = date_now AND user_id = get_user_id; END - ''', (trigger_name, table_name, user_id) + """, + (trigger_name, table_name, user_id), ) - - def trigger_change_task(self, trigger_name, table_name, user_id): + def trigger_change_task(self, trigger_name, table_name, user_id): # self.cur.execute('DROP TRIGGER IF EXISTS change_task') self.cur.execute( - ''' + """ CREATE TRIGGER IF NOT EXISTS `%s` BEFORE UPDATE ON `%s` FOR EACH ROW BEGIN DECLARE date_now DATE; DECLARE get_user_id INT; - + SET date_now = CURDATE(); SET get_user_id = %s; - + UPDATE statistics SET task_sched_update_num = task_sched_update_num + 1 WHERE usage_date = date_now AND user_id = get_user_id; END - ''', (trigger_name, table_name, user_id) + """, + (trigger_name, table_name, user_id), ) - def trigger_add_task(self, trigger_name, table_name, user_id): # self.cur.execute('DROP TRIGGER IF EXISTS add_task') self.cur.execute( - ''' + """ CREATE TRIGGER IF NOT EXISTS `%s` BEFORE INSERT ON `%s` FOR EACH ROW BEGIN DECLARE date_now DATE; DECLARE get_user_id INT; - + SET date_now = CURDATE(); SET get_user_id = %s; - + UPDATE statistics SET task_sched_insert_num = task_sched_insert_num + 1 WHERE usage_date = date_now AND user_id = get_user_id; END - ''', (trigger_name, table_name, user_id) + """, + (trigger_name, table_name, user_id), ) - def select_table(self, user_id): self.create_table(user_id) self.cur.execute( - ''' + """ SELECT id, user_data FROM `%s` ORDER BY id; - ''', (user_id,) + """, + (user_id,), ) result = self.cur.fetchall() return result - def delete_from_table(self, user_id, table_id): self.create_table(user_id) - converted_id = get_id_from_str(user_id) - trigger_name = f'delete_task_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_on_delete_task(trigger_name, user_id, converted_id) + # converted_id = get_id_from_str(user_id) + # trigger_name = f"delete_task_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_on_delete_task(trigger_name, user_id, converted_id) self.cur.execute( - ''' + """ DELETE FROM `%s` WHERE id = %s - ''', - (user_id, table_id) + """, + (user_id, table_id), ) self.con.commit() - def update_table(self, user_id, user_data, table_id): self.create_table(user_id) converted_id = get_id_from_str(user_id) - trigger_name = f'change_task_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_change_task(trigger_name, user_id, converted_id) + # trigger_name = f"change_task_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_change_task(trigger_name, user_id, converted_id) self.cur.execute( - ''' + """ UPDATE `%s` SET user_data = %s WHERE id = %s; - ''', - (user_id, user_data, table_id) + """, + (user_id, user_data, table_id), ) self.con.commit() - def insert_into_table(self, user_id, user_data): self.create_table(user_id) converted_id = get_id_from_str(user_id) - trigger_name = f'add_task_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_add_task(trigger_name, user_id, converted_id) + # trigger_name = f"add_task_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_add_task(trigger_name, user_id, converted_id) self.cur.execute( - 'INSERT IGNORE INTO `%s` (user_data) VALUES (%s)', - (user_id, user_data) + "INSERT IGNORE INTO `%s` (user_data) VALUES (%s)", (user_id, user_data) ) self.con.commit() -######################################################################################################################## -################################################## PASSWORD GENERATOR ################################################## -######################################################################################################################## + ######################################################################################################################## + ################################################## PASSWORD GENERATOR ################################################## + ######################################################################################################################## def create_pass_gen_table(self, user_id): self.cur.execute( - ''' + """ CREATE TABLE IF NOT EXISTS `%s` ( id INT PRIMARY KEY AUTO_INCREMENT, password_description VARCHAR(384) COLLATE utf8mb4_bin, @@ -231,86 +217,86 @@ def create_pass_gen_table(self, user_id): has_repetetive BOOLEAN, CONSTRAINT unique_data UNIQUE(password_description) ) - ''', (user_id,) + """, + (user_id,), ) - def drop_remake_password_table(self, user_id): - table_name = f'pass_gen_table_{user_id}' - self.cur.execute('DROP TABLE IF EXISTS `%s`', (table_name,)) + table_name = f"pass_gen_table_{user_id}" + self.cur.execute("DROP TABLE IF EXISTS `%s`", (table_name,)) self.create_pass_gen_table(table_name) - def trigger_on_delete_password(self, trigger_name, table_name, user_id): self.cur.execute( - ''' + """ CREATE TRIGGER IF NOT EXISTS `%s` BEFORE DELETE ON `%s` FOR EACH ROW BEGIN DECLARE date_now DATE; DECLARE get_user_id INT; - + SET date_now = CURDATE(); SET get_user_id = %s; - + UPDATE statistics SET pass_gen_delete_num = pass_gen_delete_num + 1 WHERE usage_date = date_now AND user_id = get_user_id; END - ''', (trigger_name, table_name, user_id) + """, + (trigger_name, table_name, user_id), ) - def trigger_change_desc_password(self, trigger_name, table_name, user_id): self.cur.execute( - ''' + """ CREATE TRIGGER IF NOT EXISTS `%s` BEFORE UPDATE ON `%s` FOR EACH ROW BEGIN DECLARE date_now DATE; DECLARE get_user_id INT; - + SET date_now = CURDATE(); SET get_user_id = %s; - + UPDATE statistics SET pass_gen_update_num = pass_gen_update_num + 1 WHERE usage_date = date_now AND user_id = get_user_id; END - ''', (trigger_name, table_name, user_id) + """, + (trigger_name, table_name, user_id), ) - def trigger_add_password(self, trigger_name, table_name, user_id): self.cur.execute( - ''' + """ CREATE TRIGGER IF NOT EXISTS `%s` BEFORE INSERT ON `%s` FOR EACH ROW BEGIN DECLARE date_now DATE; DECLARE get_user_id INT; - + SET date_now = CURDATE(); SET get_user_id = %s; - + UPDATE statistics SET pass_gen_insert_num = pass_gen_insert_num + 1 WHERE usage_date = date_now AND user_id = get_user_id; END - ''', (trigger_name, table_name, user_id) + """, + (trigger_name, table_name, user_id), ) - def select_pass_gen_table(self, user_id): self.create_pass_gen_table(user_id) self.cur.execute( - ''' + """ SELECT * FROM `%s` ORDER BY id; - ''', (user_id,) + """, + (user_id,), ) table_rows = self.cur.fetchall() @@ -320,123 +306,113 @@ def select_pass_gen_table(self, user_id): def select_pass_gen_id(self, user_id): self.create_pass_gen_table(user_id) self.cur.execute( - ''' + """ SELECT id FROM `%s` ORDER BY id; - ''', (user_id,) + """, + (user_id,), ) result = self.cur.fetchall() return result - def select_description(self, user_id): self.create_pass_gen_table(user_id) self.cur.execute( - ''' + """ SELECT password_description FROM `%s` ORDER BY id; - ''', (user_id,) + """, + (user_id,), ) result = self.cur.fetchall() return str(result) - def delete_from_password_table(self, user_id, table_id): self.create_table(user_id) converted_id = get_id_from_str(user_id) - trigger_name = f'delete_password_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_on_delete_password(trigger_name, user_id, converted_id) + # trigger_name = f"delete_password_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_on_delete_password(trigger_name, user_id, converted_id) self.cur.execute( - ''' + """ DELETE FROM `%s` WHERE id = %s - ''', - (user_id, table_id) + """, + (user_id, table_id), ) self.con.commit() - def update_password_desc(self, user_id, user_desc, table_id): self.create_pass_gen_table(user_id) - converted_id = get_id_from_str(user_id) - trigger_name = f'change_password_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_change_desc_password(trigger_name, user_id, converted_id) + # converted_id = get_id_from_str(user_id) + # trigger_name = f"change_password_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_change_desc_password(trigger_name, user_id, converted_id) self.cur.execute( - ''' + """ UPDATE `%s` SET password_description = %s WHERE id = %s; - ''', - (user_id, user_desc, table_id) + """, + (user_id, user_desc, table_id), ) self.con.commit() - def update_password(self, user_id, user_password, length, has_repetetive, table_id): self.create_pass_gen_table(user_id) - converted_id = get_id_from_str(user_id) - trigger_name = f'change_password_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_change_desc_password(trigger_name, user_id, converted_id) + # converted_id = get_id_from_str(user_id) + # trigger_name = f"change_password_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_change_desc_password(trigger_name, user_id, converted_id) self.cur.execute( - ''' + """ UPDATE `%s` SET generated_password = %s, password_length = %s, has_repetetive = %s WHERE id = %s; - ''', - (user_id, user_password, length, has_repetetive, table_id) + """, + (user_id, user_password, length, has_repetetive, table_id), ) self.con.commit() - - def insert_password_data(self, user_id, user_desc, generated_pass, password_length, has_repetetive): + def insert_password_data( + self, user_id, user_desc, generated_pass, password_length, has_repetetive + ): self.create_pass_gen_table(user_id) - converted_id = get_id_from_str(user_id) - trigger_name = f'add_password_{converted_id}' - self.cur.execute( - 'CALL add_date_and_id(%s)', (converted_id,) - ) - self.trigger_add_password(trigger_name, user_id, converted_id) + # converted_id = get_id_from_str(user_id) + # trigger_name = f"add_password_{converted_id}" + # self.cur.execute("CALL add_date_and_id(%s)", (converted_id,)) + # self.trigger_add_password(trigger_name, user_id, converted_id) self.cur.execute( - ''' + """ INSERT INTO `%s` (password_description, generated_password, password_length, has_repetetive) VALUES (%s, %s, %s, %s) - ''', - (user_id, user_desc, generated_pass, password_length, has_repetetive) + """, + (user_id, user_desc, generated_pass, password_length, has_repetetive), ) self.con.commit() - def check_tables_to_allow_token(self, user_id): - self.cur.execute('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s', ('bot_db',)) + self.cur.execute( + "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s", + ("bot_db",), + ) all_tables_tuple_lst = self.cur.fetchall() all_tables_lst = [table_tuple[0] for table_tuple in all_tables_tuple_lst] table_name = f"'pass_gen_table_{user_id}'" - if table_name in all_tables_lst: - return True - else: - return False + return table_name in all_tables_lst diff --git a/bot/enums/__init__.py b/bot/enums/__init__.py new file mode 100644 index 0000000..61bdc6c --- /dev/null +++ b/bot/enums/__init__.py @@ -0,0 +1,5 @@ +from .commands import Command + +__all__ = ( + "Command" +) diff --git a/bot/enums/commands.py b/bot/enums/commands.py new file mode 100644 index 0000000..b7bc7da --- /dev/null +++ b/bot/enums/commands.py @@ -0,0 +1,18 @@ +from enum import Enum + + +class Command(str, Enum): + START = "start" + HELP = "help" + TIME = "time" + CURRENT_WEATHER = "current_weather" + CURRENCY_CONVERTER = "currency_converter" + DATE_CALCULATOR = "date_calculator" + TASK_SCHEDULER = "task_scheduler" + IMAGE_EDITOR = "image_editor" + PASSWORD_GENERATOR = "password_generator" + KEYWORD_LIST = "keyword_list" + + @classmethod + def __len__(cls) -> int: + return len(cls._member_names_) diff --git a/bot/enums/currencies.py b/bot/enums/currencies.py new file mode 100644 index 0000000..c8bd880 --- /dev/null +++ b/bot/enums/currencies.py @@ -0,0 +1,19 @@ +from enum import Enum, auto +from typing import Type, Literal + + +class Currency(str, Enum): + HRYVNIAS = auto() + DOLLARS = auto() + EUROS = auto() + BITCOINS = auto() + + def __str__(self) -> str: + return self.name.lower() + + @classmethod + def __len__(cls) -> int: + return len(cls._member_names_) + + +# CurrencyType: Type[Currency] = Literal[Currency.DOLLARS, Currency.HRYVNIAS, Currency.EUROS, Currency.BITCOINS] diff --git a/bot/enums/keyboard_callbacks.py b/bot/enums/keyboard_callbacks.py new file mode 100644 index 0000000..6bde200 --- /dev/null +++ b/bot/enums/keyboard_callbacks.py @@ -0,0 +1,28 @@ +from enum import Enum + + +class CallbackDataStorage(str, Enum): + @classmethod + def __len__(cls) -> int: + return len(cls._member_names_) + + def __str__(self): + return self.value + + +class ConverterCallbackStorage(CallbackDataStorage): + CONVERT_DOLLAR = "convert_dollar" + CONVERT_EURO = "convert_euro" + CONVERT_HRYVNIA = "convert_hryvnia" + CURRENCY_RATE = "currency_rate" + CURRENCY_CONVERTER = "currency_converter" + CLOSE_CONVERTER = "close_converter" + MAIN_CONVERTER_MENU_BACK = "main_converter_menu_back" + + +class GeneratorCallbackStorage(CallbackDataStorage): + ... + + +class SchedulerCallbackStorage(CallbackDataStorage): + ... diff --git a/bot/enums/keywords.py b/bot/enums/keywords.py new file mode 100644 index 0000000..1b33540 --- /dev/null +++ b/bot/enums/keywords.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class Keyword(str, Enum): + + + @classmethod + def __len__(cls) -> int: + return len(cls._member_names_) diff --git a/bot/keyboards/__init__.py b/bot/keyboards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/keyboards/currency_converter/converter_keyboard.py b/bot/keyboards/currency_converter/converter_keyboard.py deleted file mode 100644 index 27e7e2e..0000000 --- a/bot/keyboards/currency_converter/converter_keyboard.py +++ /dev/null @@ -1,10 +0,0 @@ -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from bot.keyboards.currency_converter.return_keyboard import return_button - -converter_keyboard = InlineKeyboardMarkup() - -dollar_button = InlineKeyboardButton(text='Dollar conversion', callback_data='convert_dollar') -euro_button = InlineKeyboardButton(text='Euro conversion', callback_data='convert_euro') -hryvnia_button = InlineKeyboardButton(text='Hryvnia conversion', callback_data='convert_hryvnia') - -converter_keyboard.add(dollar_button).add(euro_button).add(hryvnia_button).add(return_button) diff --git a/bot/keyboards/currency_converter/currency_keyboard.py b/bot/keyboards/currency_converter/currency_keyboard.py deleted file mode 100644 index 61c0d81..0000000 --- a/bot/keyboards/currency_converter/currency_keyboard.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton - -currency_keyboard = InlineKeyboardMarkup() - -exchange_rate_button = InlineKeyboardButton(text='Exchange rate', callback_data='currency_rate') -converter_button = InlineKeyboardButton(text='Converter', callback_data='currency_converter') -close_button = InlineKeyboardButton(text='Close keyboard', callback_data='close_converter') - -currency_keyboard.add(exchange_rate_button).insert(converter_button).add(close_button) diff --git a/bot/keyboards/currency_converter/return_keyboard.py b/bot/keyboards/currency_converter/return_keyboard.py deleted file mode 100644 index 449e533..0000000 --- a/bot/keyboards/currency_converter/return_keyboard.py +++ /dev/null @@ -1,7 +0,0 @@ -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton - -return_keyboard = InlineKeyboardMarkup() - -return_button = InlineKeyboardButton(text='Back', callback_data='back_to_menu') - -return_keyboard.add(return_button) diff --git a/bot/keyboards/currency_converter_keyboard.py b/bot/keyboards/currency_converter_keyboard.py new file mode 100644 index 0000000..4a4898a --- /dev/null +++ b/bot/keyboards/currency_converter_keyboard.py @@ -0,0 +1,16 @@ +from .keyboard_base import KeyboardBuilder +from ..enums.keyboard_callbacks import ConverterCallbackStorage + +main_converter_menu = KeyboardBuilder() +main_converter_menu.add_column_button("Exchange rate", ConverterCallbackStorage.CURRENCY_RATE) +main_converter_menu.add_row_button("Converter", ConverterCallbackStorage.CURRENCY_CONVERTER) +main_converter_menu.add_column_button("Close keyboard", ConverterCallbackStorage.CLOSE_CONVERTER) + +currency_rate_menu = KeyboardBuilder() +currency_rate_menu.add_column_button("Back", ConverterCallbackStorage.MAIN_CONVERTER_MENU_BACK) + +convert_menu = KeyboardBuilder() +convert_menu.add_column_button("Dollar conversion", ConverterCallbackStorage.CONVERT_DOLLAR) +convert_menu.add_column_button("Euro conversion", ConverterCallbackStorage.CONVERT_EURO) +convert_menu.add_column_button("Hryvnia conversion", ConverterCallbackStorage.CONVERT_HRYVNIA) +convert_menu.add_column_button("Back", ConverterCallbackStorage.MAIN_CONVERTER_MENU_BACK) diff --git a/bot/keyboards/keyboard_base.py b/bot/keyboards/keyboard_base.py new file mode 100644 index 0000000..65a67cf --- /dev/null +++ b/bot/keyboards/keyboard_base.py @@ -0,0 +1,14 @@ +from typing import Any + +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton + + +class KeyboardBuilder(InlineKeyboardMarkup): + def _make_button(self, text: str, callback_data: str) -> InlineKeyboardButton: + return InlineKeyboardButton(text=text, callback_data=callback_data) + + def add_column_button(self, text: str, callback_data: str) -> Any: + return self.add(self._make_button(text, callback_data)) + + def add_row_button(self, text: str, callback_data: str) -> Any: + return self.insert(self._make_button(text, callback_data)) diff --git a/bot/other_functions/message_attribute_error.py b/bot/keyboards/keyboard_close.py similarity index 65% rename from bot/other_functions/message_attribute_error.py rename to bot/keyboards/keyboard_close.py index 3ba3935..30b2ff7 100644 --- a/bot/other_functions/message_attribute_error.py +++ b/bot/keyboards/keyboard_close.py @@ -8,3 +8,9 @@ async def message_attribute_control(call): pass except aiogram.utils.exceptions.MessageCantBeDeleted: pass + + +async def close_keyboard(call): + await message_attribute_control(call) + await call.message.delete() + await call.answer() diff --git a/bot/keyboards/password_generator/back_keyboard.py b/bot/keyboards/password_generator/back_keyboard.py index 48303f9..9c02c1d 100644 --- a/bot/keyboards/password_generator/back_keyboard.py +++ b/bot/keyboards/password_generator/back_keyboard.py @@ -2,6 +2,8 @@ back_to_telegram_generator_keyboard = InlineKeyboardMarkup() -back_button = InlineKeyboardButton(text='Back', callback_data='back_to_telegram_generator') +back_button = InlineKeyboardButton( + text="Back", callback_data="back_to_telegram_generator" +) -back_to_telegram_generator_keyboard.add(back_button) \ No newline at end of file +back_to_telegram_generator_keyboard.add(back_button) diff --git a/bot/keyboards/password_generator/download_app_keyboard.py b/bot/keyboards/password_generator/download_app_keyboard.py index 5334176..f4b932e 100644 --- a/bot/keyboards/password_generator/download_app_keyboard.py +++ b/bot/keyboards/password_generator/download_app_keyboard.py @@ -4,7 +4,11 @@ download_app_keyboard = InlineKeyboardMarkup() -windows_button = InlineKeyboardButton(text='Windows installer', callback_data='windows_installer') -linux_mac_button = InlineKeyboardButton(text='Linux/MacOS binary', callback_data='linux_macos') +windows_button = InlineKeyboardButton( + text="Windows installer", callback_data="windows_installer" +) +linux_mac_button = InlineKeyboardButton( + text="Linux/MacOS binary", callback_data="linux_macos" +) download_app_keyboard.add(windows_button).insert(linux_mac_button).add(return_button) diff --git a/bot/keyboards/password_generator/generation_keyboard.py b/bot/keyboards/password_generator/generation_keyboard.py index d6de65b..d4b99ba 100644 --- a/bot/keyboards/password_generator/generation_keyboard.py +++ b/bot/keyboards/password_generator/generation_keyboard.py @@ -2,13 +2,18 @@ third_generator_keyboard = InlineKeyboardMarkup() -generate_button = InlineKeyboardButton(text='Generate password', callback_data='generate_password') -write_to_db_button = InlineKeyboardButton(text='Store in database', callback_data='store_in_db') -back_button = InlineKeyboardButton(text='Back', callback_data='back_to_second') -return_to_start = InlineKeyboardButton(text='Main generator menu', callback_data='main_generator_menu') +generate_button = InlineKeyboardButton( + text="Generate password", callback_data="generate_password" +) +write_to_db_button = InlineKeyboardButton( + text="Store in database", callback_data="store_in_db" +) +back_button = InlineKeyboardButton(text="Back", callback_data="back_to_second") +return_to_start = InlineKeyboardButton( + text="Main generator menu", callback_data="main_generator_menu" +) third_generator_keyboard.add(generate_button) third_generator_keyboard.insert(write_to_db_button) third_generator_keyboard.add(back_button) third_generator_keyboard.insert(return_to_start) - diff --git a/bot/keyboards/password_generator/main_keyboard.py b/bot/keyboards/password_generator/main_keyboard.py index 7f42283..58bd37a 100644 --- a/bot/keyboards/password_generator/main_keyboard.py +++ b/bot/keyboards/password_generator/main_keyboard.py @@ -2,8 +2,12 @@ password_start_keyboard = InlineKeyboardMarkup() -telegram_app_button = InlineKeyboardButton(text='Move to telegram generator', callback_data='telegram_password') -app_button = InlineKeyboardButton(text='Download desktop application', callback_data='password_app') -close_button = InlineKeyboardButton(text='Close this menu', callback_data='close_app') +telegram_app_button = InlineKeyboardButton( + text="Move to telegram generator", callback_data="telegram_password" +) +app_button = InlineKeyboardButton( + text="Download desktop application", callback_data="password_app" +) +close_button = InlineKeyboardButton(text="Close this menu", callback_data="close_app") password_start_keyboard.add(telegram_app_button).insert(app_button).add(close_button) diff --git a/bot/keyboards/password_generator/radio_keyboard.py b/bot/keyboards/password_generator/radio_keyboard.py index 3a6aedd..7a1ac4e 100644 --- a/bot/keyboards/password_generator/radio_keyboard.py +++ b/bot/keyboards/password_generator/radio_keyboard.py @@ -3,13 +3,19 @@ first_generator_keyboard = InlineKeyboardMarkup() -all_button = InlineKeyboardButton(text='All characters', callback_data='all_characters') -letters_button = InlineKeyboardButton(text='Only letters', callback_data='only_letters') -digits_button = InlineKeyboardButton(text='Only digits', callback_data='only_digits') -let_dig_button = InlineKeyboardButton(text='Letters & digits', callback_data='letters_digits') -let_sig_button = InlineKeyboardButton(text='Letters & signs', callback_data='letters_signs') -dig_sig_button = InlineKeyboardButton(text='Digits & signs', callback_data='digits_signs') -next_button = InlineKeyboardButton(text='Next', callback_data='next_to_second') +all_button = InlineKeyboardButton(text="All characters", callback_data="all_characters") +letters_button = InlineKeyboardButton(text="Only letters", callback_data="only_letters") +digits_button = InlineKeyboardButton(text="Only digits", callback_data="only_digits") +let_dig_button = InlineKeyboardButton( + text="Letters & digits", callback_data="letters_digits" +) +let_sig_button = InlineKeyboardButton( + text="Letters & signs", callback_data="letters_signs" +) +dig_sig_button = InlineKeyboardButton( + text="Digits & signs", callback_data="digits_signs" +) +next_button = InlineKeyboardButton(text="Next", callback_data="next_to_second") first_generator_keyboard.add(all_button) first_generator_keyboard.add(letters_button) diff --git a/bot/keyboards/password_generator/return_keyboard.py b/bot/keyboards/password_generator/return_keyboard.py index 92d598f..23b4a55 100644 --- a/bot/keyboards/password_generator/return_keyboard.py +++ b/bot/keyboards/password_generator/return_keyboard.py @@ -2,6 +2,8 @@ return_app_keyboard = InlineKeyboardMarkup() -return_button = InlineKeyboardButton(text='Return to main menu', callback_data='back_to_main_menu') +return_button = InlineKeyboardButton( + text="Return to main menu", callback_data="back_to_main_menu" +) -return_app_keyboard.add(return_button) \ No newline at end of file +return_app_keyboard.add(return_button) diff --git a/bot/keyboards/password_generator/return_to_update_keyboard.py b/bot/keyboards/password_generator/return_to_update_keyboard.py index 9fb0d1b..d079b8a 100644 --- a/bot/keyboards/password_generator/return_to_update_keyboard.py +++ b/bot/keyboards/password_generator/return_to_update_keyboard.py @@ -2,6 +2,6 @@ password_generator_return_to_update = InlineKeyboardMarkup() -return_button = InlineKeyboardButton(text='Back', callback_data='return_to_update_menu') +return_button = InlineKeyboardButton(text="Back", callback_data="return_to_update_menu") -password_generator_return_to_update.add(return_button) \ No newline at end of file +password_generator_return_to_update.add(return_button) diff --git a/bot/keyboards/password_generator/set_length_keyboard.py b/bot/keyboards/password_generator/set_length_keyboard.py index 5028fc2..37e4349 100644 --- a/bot/keyboards/password_generator/set_length_keyboard.py +++ b/bot/keyboards/password_generator/set_length_keyboard.py @@ -2,15 +2,17 @@ second_generator_keyboard = InlineKeyboardMarkup() -very_easy_button = InlineKeyboardButton(text='Very easy', callback_data='very_easy') -easy_button = InlineKeyboardButton(text='Easy', callback_data='easy') -normal_button = InlineKeyboardButton(text='Normal', callback_data='normal') -hard_button = InlineKeyboardButton(text='Hard', callback_data='hard') -very_hard_button = InlineKeyboardButton(text='Very hard', callback_data='very_hard') -unbreakable_button = InlineKeyboardButton(text='Unbreakable', callback_data='unbreakable') +very_easy_button = InlineKeyboardButton(text="Very easy", callback_data="very_easy") +easy_button = InlineKeyboardButton(text="Easy", callback_data="easy") +normal_button = InlineKeyboardButton(text="Normal", callback_data="normal") +hard_button = InlineKeyboardButton(text="Hard", callback_data="hard") +very_hard_button = InlineKeyboardButton(text="Very hard", callback_data="very_hard") +unbreakable_button = InlineKeyboardButton( + text="Unbreakable", callback_data="unbreakable" +) -back_button = InlineKeyboardButton(text='Back', callback_data='back_to_first') -next_button = InlineKeyboardButton(text='Next', callback_data='next_to_third') +back_button = InlineKeyboardButton(text="Back", callback_data="back_to_first") +next_button = InlineKeyboardButton(text="Next", callback_data="next_to_third") second_generator_keyboard.add(very_easy_button) diff --git a/bot/keyboards/password_generator/telegram_keyboard.py b/bot/keyboards/password_generator/telegram_keyboard.py index 679958f..a730838 100644 --- a/bot/keyboards/password_generator/telegram_keyboard.py +++ b/bot/keyboards/password_generator/telegram_keyboard.py @@ -4,11 +4,21 @@ password_telegram_keyboard = InlineKeyboardMarkup() -show_passwords_button = InlineKeyboardButton(text='Show all passwords', callback_data='show_password') -create_password_button = InlineKeyboardButton(text='Generate password', callback_data='create_password') -change_password_button = InlineKeyboardButton(text='Change password', callback_data='change_desc_pass') -delete_password_button = InlineKeyboardButton(text='Delete password', callback_data='delete_password') -get_json_button = InlineKeyboardButton(text='Get all passwords in JSON', callback_data='json_password') +show_passwords_button = InlineKeyboardButton( + text="Show all passwords", callback_data="show_password" +) +create_password_button = InlineKeyboardButton( + text="Generate password", callback_data="create_password" +) +change_password_button = InlineKeyboardButton( + text="Change password", callback_data="change_desc_pass" +) +delete_password_button = InlineKeyboardButton( + text="Delete password", callback_data="delete_password" +) +get_json_button = InlineKeyboardButton( + text="Get all passwords in JSON", callback_data="json_password" +) password_telegram_keyboard.add(show_passwords_button) password_telegram_keyboard.insert(create_password_button) diff --git a/bot/keyboards/password_generator/update_keyboard.py b/bot/keyboards/password_generator/update_keyboard.py index a118372..b81a986 100644 --- a/bot/keyboards/password_generator/update_keyboard.py +++ b/bot/keyboards/password_generator/update_keyboard.py @@ -4,8 +4,12 @@ password_generator_update_keyboard = InlineKeyboardMarkup() -change_description_button = InlineKeyboardButton(text='Change description', callback_data='change_description') -create_password_button = InlineKeyboardButton(text='Change password', callback_data='change_password') +change_description_button = InlineKeyboardButton( + text="Change description", callback_data="change_description" +) +create_password_button = InlineKeyboardButton( + text="Change password", callback_data="change_password" +) password_generator_update_keyboard.add(change_description_button) password_generator_update_keyboard.insert(create_password_button) diff --git a/bot/keyboards/random_number/random_numbers_keyboard.py b/bot/keyboards/random_number/random_numbers_keyboard.py deleted file mode 100644 index 17fd9c8..0000000 --- a/bot/keyboards/random_number/random_numbers_keyboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton - -random_number_keyboard = InlineKeyboardMarkup() - -random_button1 = InlineKeyboardButton(text='Range 0 — 10', callback_data='random_value_1') -random_button2 = InlineKeyboardButton(text='Range 0 — 100', callback_data='random_value_2') -random_button3 = InlineKeyboardButton(text='Range -100 — 100', callback_data='random_value_3') -random_button4 = InlineKeyboardButton(text='Range -1000000 — 1000000', callback_data='random_value_4') -close_button = InlineKeyboardButton(text='Close keyboard', callback_data='close_random') - -random_number_keyboard.add(random_button1) -random_number_keyboard.insert(random_button2) -random_number_keyboard.add(random_button3) -random_number_keyboard.insert(random_button4) -random_number_keyboard.add(close_button) diff --git a/bot/keyboards/task_scheduler/back_to_planner_keyboard.py b/bot/keyboards/task_scheduler/back_to_planner_keyboard.py index 33d6bad..710e76a 100644 --- a/bot/keyboards/task_scheduler/back_to_planner_keyboard.py +++ b/bot/keyboards/task_scheduler/back_to_planner_keyboard.py @@ -2,6 +2,6 @@ return_keyboard = InlineKeyboardMarkup() -return_button = InlineKeyboardButton(text='Back', callback_data='back_to_scheduler') +return_button = InlineKeyboardButton(text="Back", callback_data="back_to_scheduler") return_keyboard.add(return_button) diff --git a/bot/keyboards/task_scheduler/scheduler_keyboard.py b/bot/keyboards/task_scheduler/scheduler_keyboard.py index 20d38bf..46aebbe 100644 --- a/bot/keyboards/task_scheduler/scheduler_keyboard.py +++ b/bot/keyboards/task_scheduler/scheduler_keyboard.py @@ -2,10 +2,14 @@ scheduler_keyboard = InlineKeyboardMarkup() -task_button1 = InlineKeyboardButton(text='View tasks', callback_data='select') -task_button2 = InlineKeyboardButton(text='Add task', callback_data='insert') -task_button3 = InlineKeyboardButton(text='Change task', callback_data='update') -task_button4 = InlineKeyboardButton(text='Delete task', callback_data='delete') -close_scheduler = InlineKeyboardButton(text='Close keyboard', callback_data='close_scheduler') +task_button1 = InlineKeyboardButton(text="View tasks", callback_data="select") +task_button2 = InlineKeyboardButton(text="Add task", callback_data="insert") +task_button3 = InlineKeyboardButton(text="Change task", callback_data="update") +task_button4 = InlineKeyboardButton(text="Delete task", callback_data="delete") +close_scheduler = InlineKeyboardButton( + text="Close keyboard", callback_data="close_scheduler" +) -scheduler_keyboard.add(task_button1).add(task_button2).insert(task_button3).add(task_button4).add(close_scheduler) +scheduler_keyboard.add(task_button1).add(task_button2).insert(task_button3).add( + task_button4 +).add(close_scheduler) diff --git a/bot/keyboards/token/token_keyboard.py b/bot/keyboards/token/token_keyboard.py deleted file mode 100644 index 92bdb5d..0000000 --- a/bot/keyboards/token/token_keyboard.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton - -token_keyboard = InlineKeyboardMarkup() - -new_token_button = InlineKeyboardButton(text='Generate new token', callback_data='add_token') -delete_token_button = InlineKeyboardButton(text='Remove token', callback_data='delete_token') -close_keyboard_button = InlineKeyboardButton(text='Close keyboard', callback_data='close_token') - -token_keyboard.add(new_token_button).insert(delete_token_button).add(close_keyboard_button) diff --git a/bot/keywords/__init__.py b/bot/keywords/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/keywords/keyword_utils/__init__.py b/bot/keywords/keyword_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/keywords/keyword_utils/check_date_words.py b/bot/keywords/keyword_utils/check_date_words.py new file mode 100644 index 0000000..64cdba8 --- /dev/null +++ b/bot/keywords/keyword_utils/check_date_words.py @@ -0,0 +1,10 @@ +def check_year(years: int) -> str: + return "year" if years == 1 else "years" + + +def check_month(months: int) -> str: + return "month" if months == 1 else "months" + + +def check_day(days: int) -> str: + return "day" if days == 1 else "days" diff --git a/bot/keywords/keyword_utils/check_document_extension.py b/bot/keywords/keyword_utils/check_document_extension.py new file mode 100644 index 0000000..a80fe2d --- /dev/null +++ b/bot/keywords/keyword_utils/check_document_extension.py @@ -0,0 +1,65 @@ +import os +from typing import Union, Optional, Tuple + +from aiogram import types +from aiogram.types import ContentType, PhotoSize, Document + + +def get_conversion_path_name( + dir_type: str, + user_id: int, + file_ext: Optional[str] = None, + filename: Optional[str] = None, +) -> str: + path = os.path.join( + "temp_data", + f"temp_user_{dir_type}_{user_id}", + f"{filename}.{file_ext}" if file_ext and filename else "", + ) + return os.path.abspath(path) + + +def get_file_conversion_extension(file_ext: str) -> Optional[str]: + doc_extensions = ("docx", "doc") + pdf_extension = ("pdf",) + document_extension = file_ext + + if document_extension in doc_extensions: + return pdf_extension[0] + elif document_extension in pdf_extension: + return doc_extensions[0] + return None + + +def is_file_ext_allowed( + message: types.Message, allowed_extensions: Tuple[str, str, str] +) -> bool: + document_extension = message.document.file_name.split(".")[-1] + return document_extension in allowed_extensions + + +def is_document(message: types.Message) -> bool: + return message.content_type == ContentType.DOCUMENT + + +def is_photo(message: types.Message) -> bool: + return message.content_type == ContentType.PHOTO + + +def file_option_selector( + message: types.Message, +) -> Union[PhotoSize, Optional[Document]]: + if is_photo(message): + return message.photo[-1] + + if is_file_ext_allowed(message, allowed_extensions=("jpg", "jpeg", "png")): + return message.document + + +def parse_args(message: types.Message, keyword: str) -> Optional[int]: + arg_list = message.text.strip().lower().split(keyword) + try: + compression_level = int(arg_list[1]) + return compression_level + except ValueError: + return None diff --git a/bot/keywords/keyword_utils/file_size_contoller.py b/bot/keywords/keyword_utils/file_size_contoller.py new file mode 100644 index 0000000..86f9199 --- /dev/null +++ b/bot/keywords/keyword_utils/file_size_contoller.py @@ -0,0 +1,34 @@ +from typing import Optional + +from aiogram import types +from aiogram.types import InputFile +from aiogram.utils.exceptions import NetworkError + +from bot.config import bot + + +async def is_document_sent( + message: types.Message, file_to_send: str, new_filename: Optional[str] = None +) -> bool: + try: + await bot.send_document( + chat_id=message.chat.id, + document=InputFile(file_to_send, filename=new_filename), + ) + except NetworkError: + return False + + return True + + +async def send_documents(message: types.Message, pdf_path: str, docx_path: str) -> str: + pdf_sent = await is_document_sent(message, pdf_path) + docx_sent = await is_document_sent(message, docx_path) + + if pdf_sent and docx_sent: + return "Conversion was successfully ended!" + elif pdf_sent and not docx_sent: + return "Conversion error: converted DOCX is to large (must be less than 50 MB)!" + elif not pdf_sent and docx_sent: + return "Conversion error: converted PDF is to large (must be less than 50 MB)!" + return "Conversion failed because converted files are larger than 50MB!" diff --git a/bot/keywords/keyword_utils/image_to_file_converter.py b/bot/keywords/keyword_utils/image_to_file_converter.py new file mode 100644 index 0000000..cbdc181 --- /dev/null +++ b/bot/keywords/keyword_utils/image_to_file_converter.py @@ -0,0 +1,191 @@ +import os +import shutil + +from abc import ABC, abstractmethod +from typing import List, Dict, Tuple, Optional + +from PIL import Image + +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas +from docx import Document +from docx.shared import Inches +from aiogram import types +from bot.config import bot + + +class FileLoader(ABC): + TEMP_DATA_DIR = os.path.abspath("temp_data") + + def __init__(self, message: types.Message, dir_type: str): + self.message = message + self.user_id = message.from_user.id + self.temp_dir = os.path.join( + self.TEMP_DATA_DIR, f"temp_user_{dir_type}_{self.user_id}" + ) + self.create_temp_dir() + + def create_temp_dir(self) -> None: + if not os.path.exists(self.temp_dir): + os.makedirs(self.temp_dir) + + def remove_temp_dir(self) -> None: + if os.path.exists(self.temp_dir): + shutil.rmtree(self.temp_dir) + + @abstractmethod + async def save_files(self, file_ids: List[str], **kwargs) -> List[str]: + raise NotImplementedError + + @abstractmethod + async def _files_processing(self, file_ids: List[str], **kwargs) -> List[str]: + raise NotImplementedError + + +class ImageLoader(FileLoader): + async def save_files(self, image_ids: List[str], **kwargs) -> List[str]: + photo_list = await self._files_processing(image_ids) + return photo_list + + async def _files_processing(self, image_ids: List[str], **kwargs) -> List[str]: + photo_path_list = [] + + for image_index, file_id in enumerate(image_ids): + image_info = await bot.get_file(file_id) + image_path = image_info.file_path + image = await bot.download_file(image_path) + + filename = f"image_{image_index}.jpg" + temp_image_path = os.path.join(self.temp_dir, filename) + with open(temp_image_path, "wb") as temp_image_file: + temp_image_file.write(image.getvalue()) + + photo_path_list.append(temp_image_path) + + return photo_path_list + + +class ImageQualityReducer: + def __init__(self, image_path: str): + self.image_path = image_path + + def reduce( + self, quality: int, file_specifier: Optional[str] = None + ) -> Dict[str, str | Image.Image]: + image = Image.open(self.image_path) + image = image.convert("RGB") + new_path = self.change_path(file_specifier) + image.save(fp=f"{new_path}", quality=quality) + return {"reduced_image": image, "new_file_path": new_path} + + def change_path(self, file_specifier: str) -> str: + dot_symbol = "." + sep_ext = self.image_path.split(dot_symbol) + new_path = ( + f"{sep_ext[0]}_reduced_{file_specifier}" + if file_specifier + else f"{sep_ext[0]}_reduced" + ) + full_path = new_path + dot_symbol + sep_ext[1] + return full_path + + +class FileConverter(ABC): + @abstractmethod + def convert( + self, conversion_file_path: str, image_paths: List[str], **kwargs + ) -> None: + raise NotImplementedError + + def compress_check( + self, image_file: str, compression_level: Optional[int], **kwargs + ) -> Tuple[str, int, int]: + file_extension = kwargs["file_extension"] + if compression_level: + img_reducer = ImageQualityReducer(image_file) + img_obj = img_reducer.reduce( + quality=compression_level, file_specifier=file_extension + ) + image_file = img_obj["new_file_path"] + img = img_obj["reduced_image"] + width, height = img.size + else: + img = Image.open(image_file) + width, height = img.size + + return image_file, width, height + + +class PDFConverter(FileConverter): + def convert( + self, + conversion_file_path: str, + image_paths: List[str], + compression_level: Optional[int] = None, + ): + conversion_canvas = canvas.Canvas(conversion_file_path, pagesize=letter) + + for image_file in image_paths: + img_data = self.compress(image_file, compression_level) + image_file, width, height = img_data + aspect_ratio = height / float(width) + new_width = letter[0] + new_height = letter[0] * aspect_ratio + conversion_canvas.drawImage( + image_file, 0, 0, width=new_width, height=new_height + ) + conversion_canvas.showPage() + + conversion_canvas.save() + + def compress( + self, image_file: str, compression_level: Optional[int] + ) -> Tuple[str, int, int]: + return self.compress_check(image_file, compression_level, file_extension="pdf") + + +class DOCXConverter(FileConverter): + PHONE_IMAGE_RATIO = 0.5 + DEFAULT_WIDTH_INCHES = Inches(6) + TALL_IMAGES_HEIGHT_INCHES = Inches(9) + + def convert( + self, + conversion_file_path: str, + image_paths: List[str], + compression_level: Optional[int] = None, + ): + doc = Document() + for image_file in image_paths: + img_data = self.compress(image_file, compression_level) + image_file, width, height = img_data + + if self.is_image_sides_ratio_tall(image_file): + print("Tall image") + self.add_picture(doc, image_file, self.TALL_IMAGES_HEIGHT_INCHES) + else: + print("Normal image") + self.add_picture(doc, image_file) + + doc.save(conversion_file_path) + + def add_picture( + self, doc: Document, image_file: str, height: Optional[Inches] = None + ) -> None: + if height: + doc.add_paragraph().add_run().add_picture(image_file, height=height) + return None + + doc.add_paragraph().add_run().add_picture( + image_file, width=self.DEFAULT_WIDTH_INCHES + ) + + def is_image_sides_ratio_tall(self, image_path: str) -> bool: + with Image.open(image_path) as img: + aspect_ratio = img.width / img.height + return aspect_ratio < self.PHONE_IMAGE_RATIO + + def compress( + self, image_file: str, compression_level: Optional[int] + ) -> Tuple[str, int, int]: + return self.compress_check(image_file, compression_level, file_extension="docx") diff --git a/bot/keywords/keyword_utils/morse_alphabet.py b/bot/keywords/keyword_utils/morse_alphabet.py new file mode 100644 index 0000000..9389843 --- /dev/null +++ b/bot/keywords/keyword_utils/morse_alphabet.py @@ -0,0 +1,100 @@ +MORSE_CODE_DICT = { + "EN": { + "A": "•-", + "B": "-•••", + "C": "-•-•", + "D": "-••", + "E": "•", + "F": "••-•", + "G": "--•", + "H": "••••", + "I": "••", + "J": "•---", + "K": "-•-", + "L": "•-••", + "M": "--", + "N": "-•", + "O": "---", + "P": "•--•", + "Q": "--•-", + "R": "•-•", + "S": "•••", + "T": "-", + "U": "••-", + "V": "•••-", + "W": "•--", + "X": "-••-", + "Y": "-•--", + "Z": "--••", + }, + "CYRILLIC": { + "А": "•-", + "Б": "-•••", + "В": "•--", + "Г": "••••", + "Д": "-••", + "Е": "•", + "Ж": "•••-", + "З": "--••", + "И": "-•--", + "Й": "•---", + "К": "-•-", + "Л": "•-••", + "М": "--", + "Н": "-•", + "О": "---", + "П": "•--•", + "Р": "•-•", + "С": "•••", + "Т": "-", + "У": "••-", + "Ф": "••-•", + "Х": "----", + "Ц": "-•-•", + "Ч": "---•", + "Ш": "--•-", + "Щ": "--•--", + "Ь": "-••-", + "Ю": "••--", + "Я": "•-•-", + }, + "UA": { + "Ґ": "--•", + "Є": "••-••", + "І": "••", + "Ї": "•---•", + }, + "RU": { + "Ъ": "•--•-•", + "Ы": "-•---", + "Э": "•••-•••", + "Ё": "•---•-", + }, + "OTHER": { + "1": "•----", + "2": "••---", + "3": "•••--", + "4": "••••-", + "5": "•••••", + "6": "-••••", + "7": "--•••", + "8": "---••", + "9": "----•", + "0": "-----", + ",": "•-•-•-", + ".": "••••••", + "?": "••--••", + "/": "-••-•", + "-": "-••••-", + "!": "--••--", + ":": "---•••", + ";": "-•-•-•", + "ʼ": "-••••-", + '"': "•-••-•", + "@": "•--•-•-", + "$": "•••-••-", + "(": "-•--•", + ")": "-•--•-", + " ": " ", + }, +} diff --git a/bot/keywords/keyword_utils/morse_encryption_decryption.py b/bot/keywords/keyword_utils/morse_encryption_decryption.py new file mode 100644 index 0000000..ff052da --- /dev/null +++ b/bot/keywords/keyword_utils/morse_encryption_decryption.py @@ -0,0 +1,32 @@ +from typing import Dict, List + + +class MorseCoder: + EMPTY_SPACE: str = " " + EMPTY_STRING: str = "" + UNKNOWN_SYMBOL: str = "�" + + def encrypt(self, text_data: str, morse_alphabet: Dict[str, str]) -> str: + text_data: str = text_data.upper() + + encrypted_message = [ + self.EMPTY_SPACE + if char == self.EMPTY_SPACE + else (morse_alphabet.get(char, self.UNKNOWN_SYMBOL) + self.EMPTY_SPACE) + for char in text_data + ] + return "".join(encrypted_message).strip() + + def decrypt(self, encrypted_data: str, morse_alphabet: Dict[str, str]) -> str: + encrypted_data_list: List[str] = encrypted_data.split(self.EMPTY_SPACE) + + decrypted_message = [ + self.EMPTY_SPACE + if enc_char == self.EMPTY_STRING + else next( + (key for key, val in morse_alphabet.items() if val == enc_char), + self.UNKNOWN_SYMBOL.strip(), + ) + for enc_char in encrypted_data_list + ] + return "".join(decrypted_message) diff --git a/bot/keywords/keyword_utils/morse_language_selector.py b/bot/keywords/keyword_utils/morse_language_selector.py new file mode 100644 index 0000000..0986c28 --- /dev/null +++ b/bot/keywords/keyword_utils/morse_language_selector.py @@ -0,0 +1,40 @@ +from typing import Dict + +from .morse_alphabet import MORSE_CODE_DICT + + +class MorseAlphabetCreator: + ENGLISH: str = "EN" + CYRILLIC_DEFAULT: str = "CYRILLIC" + UKRAINIAN: str = "UA" + RUSSIAN: str = "RU" + OTHER: str = "OTHER" + + def __init__(self): + self.language = None + + def alphabet_selector(self, user_input: str) -> Dict[str, str]: + data = user_input.upper() + data_list = [char for char in data] + + english_alphabet = self.alphabet_creator(self.ENGLISH) + ukrainian_alphabet = self.alphabet_creator(self.UKRAINIAN) + russian_alphabet = self.alphabet_creator(self.RUSSIAN) + + if all(char in ukrainian_alphabet for char in data_list): + return ukrainian_alphabet + elif all(char in russian_alphabet for char in data_list): + return russian_alphabet + return english_alphabet + + def alphabet_creator(self, language_choice: str) -> Dict[str, str]: + result_dict = {} + + if language_choice == self.ENGLISH: + result_dict.update(MORSE_CODE_DICT[language_choice]) + else: + result_dict.update(MORSE_CODE_DICT[self.CYRILLIC_DEFAULT]) + result_dict.update(MORSE_CODE_DICT[language_choice]) + + result_dict.update(MORSE_CODE_DICT[self.OTHER]) + return result_dict diff --git a/bot/keywords/keyword_utils/morse_options.py b/bot/keywords/keyword_utils/morse_options.py new file mode 100644 index 0000000..3481027 --- /dev/null +++ b/bot/keywords/keyword_utils/morse_options.py @@ -0,0 +1,47 @@ +import re +from typing import Optional + +from bot.keywords.keyword_utils.morse_encryption_decryption import MorseCoder +from bot.keywords.keyword_utils.morse_language_selector import MorseAlphabetCreator + +DEFAULT_DOT_SYMBOL = "." +PRETTY_DOT_SYMBOL = "•" +DEFAULT_DASH_SYMBOL = "-" +PRETTY_DASH_SYMBOL = "—" + + +def decoding(user_input: str, morse_data: MorseAlphabetCreator, morse_coder: MorseCoder, language: str) -> str: + language_decryption = morse_data.alphabet_creator(language) + return morse_coder.decrypt(user_input, language_decryption) + + +def get_option( + decryption_match: Optional[re.Match[str]], + user_input: str, + morse_data: MorseAlphabetCreator, + morse_coder: MorseCoder, +) -> str: + if decryption_match: + user_input = user_input.replace(DEFAULT_DOT_SYMBOL, PRETTY_DOT_SYMBOL) + user_input = user_input.replace(DEFAULT_DOT_SYMBOL, DEFAULT_DASH_SYMBOL) + english_result = decoding(user_input, morse_data, morse_coder, morse_data.ENGLISH) + ukrainian_result = decoding(user_input, morse_data, morse_coder, morse_data.UKRAINIAN) + russian_result = decoding(user_input, morse_data, morse_coder, morse_data.RUSSIAN) + + return "".join( + f"ENGLISH DECODING: {english_result}\n" + f"УКРАЇНСЬКЕ ДЕКОДУВАННЯ: {ukrainian_result}\n" + f"РУССКОЕ ДЕКОДИРОВАНИЕ: {russian_result}\n" + ) + + alphabet = morse_data.alphabet_selector(user_input) + return f"{morse_coder.encrypt(user_input, alphabet)}" + + +def get_result(user_input: str) -> str: + morse_data = MorseAlphabetCreator() + morse_coder = MorseCoder() + + decryption_pattern = re.compile("^[-—•.\\s]+$") + decryption_match = re.match(decryption_pattern, user_input) + return get_option(decryption_match, user_input, morse_data, morse_coder) diff --git a/bot/keywords/keyword_utils/pdf_docx_converter.py b/bot/keywords/keyword_utils/pdf_docx_converter.py new file mode 100644 index 0000000..c319ab2 --- /dev/null +++ b/bot/keywords/keyword_utils/pdf_docx_converter.py @@ -0,0 +1,116 @@ +import asyncio +import os + +from typing import List, Tuple + +from pdf2docx import Converter +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions + +from bot.config import bot +from bot.keywords.keyword_utils.image_to_file_converter import FileConverter, FileLoader + + +class DocumentLoader(FileLoader): + async def save_files(self, file_ids: List[str], **kwargs) -> List[str]: + file_extension = kwargs["file_extension"] + document_list = await self._files_processing( + file_ids, file_extension=file_extension + ) + return document_list + + async def _files_processing( + self, file_ids: List[str], file_extension: str = None + ) -> List[str]: + file_path_list = [] + + for file_index, file_id in enumerate(file_ids): + file_info = await bot.get_file(file_id) + file_path = file_info.file_path + file = await bot.download_file(file_path) + + filename = f"document_{file_index}.{file_extension}" + temp_file_path = os.path.join(self.temp_dir, filename) + with open(temp_file_path, "wb") as temp_file: + temp_file.write(file.getvalue()) + + file_path_list.append(temp_file_path) + + return file_path_list + + +class DocxToPdf(FileConverter): + LOAD_TIMEOUT = 10 + + async def convert( + self, conversion_file_path: str, paths: List[str], **kwargs + ) -> None: + await self._parse_page(conversion_file_path, paths) + + async def _parse_page(self, conversion_file_path: str, paths: List[str]) -> None: + docx_file = os.path.join(conversion_file_path) + dir_name = paths[0] + + chrome_options = webdriver.ChromeOptions() + chrome_options.add_experimental_option( + "prefs", + { + "download.default_directory": dir_name, + }, + ) + chrome_options.add_argument("--no-sandbox") + chrome_options.add_argument("--headless") + chrome_options.add_argument("--disable-gpu") + + driver = webdriver.Chrome(options=chrome_options) + driver.get("https://smallpdf.com/word-to-pdf") + + upload_file_button_xpath = "//input[@type='file']" + + file_input = WebDriverWait(driver, self.LOAD_TIMEOUT).until( + expected_conditions.presence_of_element_located( + (By.XPATH, upload_file_button_xpath) + ) + ) + file_input.send_keys(docx_file) + + download_file_button_xpath = "//div//span[contains(text(), 'Download')]" + file_output = WebDriverWait(driver, self.LOAD_TIMEOUT).until( + expected_conditions.element_to_be_clickable( + (By.XPATH, download_file_button_xpath) + ) + ) + file_output.click() + await asyncio.sleep(self.LOAD_TIMEOUT) + self.rename_pdf(docx_file) + + @staticmethod + def rename_pdf(docx_file_path: str) -> None: + pdf_file_path = os.path.splitext(docx_file_path)[0] + ".pdf" + if os.path.exists(pdf_file_path): + new_pdf_file_path = os.path.join( + os.path.dirname(pdf_file_path), "result.pdf" + ) + os.rename(pdf_file_path, new_pdf_file_path) + + def compress( + self, image_file: str, compression_allowed: bool + ) -> Tuple[str, int, int] | str: + raise NotImplementedError + + +class PdfToDocx(FileConverter): + def convert(self, conversion_file_path: str, paths: List[str], **kwargs) -> None: + pdf_file = os.path.join(conversion_file_path) + docx_file = os.path.join(paths[0], "result.docx") + + conversion_file = Converter(pdf_file) + conversion_file.convert(docx_file) + conversion_file.close() + + def compress( + self, image_file: str, compression_allowed: bool + ) -> Tuple[str, int, int] | str: + raise NotImplementedError diff --git a/bot/other_functions/remove_start_keyword.py b/bot/keywords/keyword_utils/remove_start_keyword.py similarity index 100% rename from bot/other_functions/remove_start_keyword.py rename to bot/keywords/keyword_utils/remove_start_keyword.py diff --git a/bot/bot_main/bot_classes/WeatherInfo.py b/bot/keywords/keyword_utils/states.py similarity index 55% rename from bot/bot_main/bot_classes/WeatherInfo.py rename to bot/keywords/keyword_utils/states.py index 9750bdd..6fb9252 100644 --- a/bot/bot_main/bot_classes/WeatherInfo.py +++ b/bot/keywords/keyword_utils/states.py @@ -1,5 +1,5 @@ from aiogram.dispatcher.filters.state import StatesGroup, State -class WeatherInfo(StatesGroup): - place = State() +class ImageSaver(StatesGroup): + image = State() diff --git a/bot/keywords/keywords_for_interaction.py b/bot/keywords/keywords_for_interaction.py new file mode 100644 index 0000000..cb16f6e --- /dev/null +++ b/bot/keywords/keywords_for_interaction.py @@ -0,0 +1,343 @@ +import asyncio +import re +from datetime import datetime, timedelta + +from aiogram import types +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters import IsReplyFilter +from aiogram.types import ContentType, InputFile + +from bot.commands.commands_utils.for_photo_creation.remake_user_photo import ( + create_new_photo_auto_config, +) +from .keyword_utils.states import ImageSaver +from ..config import ( + dp, + bot, + unique_table, +) +from ..commands.commands_utils import currency_cost as cc +from .keyword_utils.check_date_words import check_day +from .keyword_utils.check_document_extension import ( + file_option_selector, + parse_args, + get_conversion_path_name, +) +from .keyword_utils.file_size_contoller import send_documents +from bot.commands.commands_utils.get_days_and_date_num import ( + extract_from_user_input_days_and_date, + extract_from_user_input_days_num, +) +from .keyword_utils.image_to_file_converter import ( + ImageLoader, + PDFConverter, + DOCXConverter, +) +from .keyword_utils.morse_options import get_result +from .keyword_utils.remove_start_keyword import ( + remove_mem_from_start, + remove_rand_mem_from_start, +) + + +# from bot.other_functions.image_to_file_converter import ImageQualityReducer + +# reducer = ImageQualityReducer("test.jpg") +# reducer.reduce(100, "jpg") + + +# @dp.message_handler(IsReplyFilter(True), regexp="^audio$|^аудіо$", content_types=ContentType.AUDIO) +# async def process_audio(message: types.Message): +# print(message.audio) +# if message.reply_to_message.audio: +# print(message.reply_to_message.audio) +# else: +# print("BEBRA") + + +@dp.message_handler(regexp="^morsec|^морзек") +async def morse_encryptor(message: types.Message): + main_regex_len = 6 + user_input = message.text + + if len(user_input) == main_regex_len: + return + + fixed_input = user_input[main_regex_len:].strip() + result = get_result(fixed_input) + await message.reply(text=result, parse_mode="HTML") + + +@dp.message_handler(regexp="^dtr delete tasks$|^дтр видали завдання$") +async def tasks_delete(message: types.Message): + user_id = message.from_id + unique_table.drop_remake_task_table(user_id) + await message.reply(text="All tasks successfully deleted!!!") + + +@dp.message_handler(regexp="^dtr delete passwords$|^дтр видали паролі$") +async def passwords_delete(message: types.Message): + user_id = message.from_id + unique_table.drop_remake_password_table(user_id) + await message.reply(text="All passwords successfully deleted!!!") + + +@dp.message_handler(regexp="^location$|^місцезнаходження$") +async def bot_location(message: types.Message): + await bot.send_location(message.chat.id, latitude=49.924394, longitude=27.746868) + + +@dp.message_handler(regexp="^bitcoin$|^біткоін$|^біток$") +async def bitcoin_price(message: types.Message): + await message.reply( + f'Bitcoin price right now: {cc.bitcoin_price():.2f} $\n', + parse_mode="HTML", + ) + + +@dp.message_handler(regexp="^dtr example$|^дтр приклад$") +async def help_with_photo(message: types.Message): + await message.reply( + "Below you can see examples how to use command /photo\n\n" + 'example - only one argument. Text "example" ' + "will be printed in the top left corner, font size = 20.\n\n" + 'example//40 - two arguments. Text "example" ' + "will be printed in the top left corner, font size = 40.\n\n" + "100//200 - two arguments. Resize photo: width = 100, height = 200\n\n" + 'example//100//200 - three arguments. Text "example" ' + "will be printed in position where coordinate X = 100 " + "and coordinate Y = 200, font size = 20.\n\n" + 'example//100//200//40 - four arguments. Text "example" ' + "will be printed in position where coordinate X = 100 " + "and coordinate Y = 200, font size = 40.\n\n" + 'example//100//200//1000//2000//50 - six arguments. Text "example" ' + "will be printed in position where coordinate X = 100 " + "and coordinate Y = 200. New width will be equal to 1000 and height - 2000, font size = 50.\n", + parse_mode="HTML", + ) + + +@dp.message_handler(regexp="^random mem|^рандом мем", content_types=ContentType.PHOTO) +async def send_auto_config_photo_with_rand_text_clr(message: types.Message): + photo = message.photo[-1] + await photo.download(destination_file="imgs/test_auto_conf.jpg") + get_user_photo_caption = message.caption + formatted_text = remove_rand_mem_from_start(get_user_photo_caption) + + create_new_photo_auto_config(False, formatted_text) + + result_photo = InputFile("imgs/result_auto_conf.jpg") + await bot.send_photo(message.chat.id, photo=result_photo) + + +@dp.message_handler(regexp="^mem|^мем", content_types=ContentType.PHOTO) +async def send_auto_config_photo_with_text(message: types.Message): + photo = message.photo[-1] + await photo.download(destination_file="imgs/test_auto_conf.jpg") + get_user_photo_caption = message.caption + formatted_text = remove_mem_from_start(get_user_photo_caption) + + create_new_photo_auto_config(True, formatted_text) + + result_photo = InputFile("imgs/result_auto_conf.jpg") + await bot.send_photo(message.chat.id, photo=result_photo) + + +@dp.message_handler( + IsReplyFilter(True), + regexp=re.compile( + "^reduce\\s*([1-9][0-9]?|100)?$|^знизити\\s*([1-9][0-9]?|100)?$", re.IGNORECASE + ), +) +async def reduce_image(message: types.Message): + replied_message = message.reply_to_message.content_type + allowed_content = (ContentType.PHOTO, ContentType.DOCUMENT) + + if replied_message not in allowed_content: + return None + + print(message.reply_to_message) + + +@dp.message_handler(content_types=[ContentType.PHOTO, ContentType.DOCUMENT]) +async def image_pdf_converter_filter(message: types.Message, state: FSMContext): + first_photo = file_option_selector(message) + if not first_photo: + return + + await state.update_data( + photo_0=first_photo, + photo_counter=0, + allowed_message_id_list=[message.message_id], + ) + await ImageSaver.image.set() + print(await state.get_data()) + + +@dp.message_handler(content_types=[ContentType.PHOTO, ContentType.DOCUMENT], state=ImageSaver.image) +async def image_pdf_converter_helper(message: types.Message, state: FSMContext): + photo = file_option_selector(message) + print(await state.get_data()) + if not photo: + await state.finish() + return + + async with state.proxy() as data: + data["photo_counter"] += 1 + photo_counter = data["photo_counter"] + data[f"photo_{photo_counter}"] = photo + data[f"allowed_message_id_list"].append(message.message_id) + + if data["photo_counter"] > 20: + await state.finish() + + +@dp.message_handler( + IsReplyFilter(True), + regexp=re.compile( + "^pdf\\s*([1-9][0-9]?|100)?$|^пдф\\s*([1-9][0-9]?|100)?$", re.IGNORECASE + ), + state=ImageSaver.image, +) +async def image_pdf_converter_end_state_handler( + message: types.Message, state: FSMContext +): + state_data = await state.get_data() + user_message_ids = state_data["allowed_message_id_list"] + check_message_id = message.reply_to_message.message_id + replied_message = message.reply_to_message.content_type + allowed_content = (ContentType.PHOTO, ContentType.DOCUMENT) + + if replied_message in allowed_content and check_message_id in user_message_ids: + images_server_ids = [ + state_data[key]["file_id"] + for key in state_data + if key.startswith("photo") and not isinstance(state_data[key], int) + ] + file_loader = ImageLoader(message, "images") + image_path_list = await file_loader.save_files(images_server_ids) + + bot_message = await message.reply("PDF and DOCX conversion started...") + + compression_level = parse_args(message, "pdf") + print(compression_level) + user_id = message.from_user.id + conversion_pdf_path = get_conversion_path_name( + "images", user_id, "pdf", filename="result" + ) + pdf_converter = PDFConverter() + pdf_converter.convert( + conversion_pdf_path, image_path_list, compression_level=compression_level + ) + + conversion_docx_path = get_conversion_path_name( + "images", user_id, "docx", filename="result" + ) + docx_converter = DOCXConverter() + docx_converter.convert( + conversion_docx_path, image_path_list, compression_level=compression_level + ) + + bot_message_data = await send_documents( + message, conversion_pdf_path, conversion_docx_path + ) + await bot_message.edit_text(text=bot_message_data) + + file_loader.remove_temp_dir() + await state.finish() + + +@dp.message_handler(state="*", regexp="^clear states$|^очистити стани$") +async def clear_states(message: types.Message, state: FSMContext): + bot_message = await message.reply("Clearing states...") + await asyncio.sleep(0.5) + await state.finish() + await bot.edit_message_text( + "States are cleared.", + chat_id=message.chat.id, + message_id=bot_message.message_id, + ) + + +@dp.message_handler(IsReplyFilter(True), regexp="^msgd$|пвдв$") +async def delete_two_messages(message: types.Message): + if message.reply_to_message.from_user.id == bot.id: + replied_msg_id = message.reply_to_message.message_id + await bot.delete_message(chat_id=message.chat.id, message_id=replied_msg_id) + await message.delete() + + +@dp.message_handler( + regexp=re.compile( + "^(Яка дата|Який день) буде через [1-9]+[0-9]* (день|днів|дня)[?]*$|" + "^What (date|day) will be after [1-9]+[0-9]* (day|days)[?]*$", + re.IGNORECASE, + ) +) +async def find_date_after_days_from_current_date(message: types.Message): + user_input = message.text + extracted_days = extract_from_user_input_days_num(user_input) + + today = datetime.now() + answer = today + timedelta(days=extracted_days) + format_answer = answer.strftime("%d.%m.%Y") + + await message.reply( + f"After {extracted_days} {check_day(extracted_days)} date will be {format_answer}!" + ) + + +@dp.message_handler( + regexp=re.compile( + "^(Яка дата|Який день) був [1-9]+[0-9]* (день|днів|дня) тому[?]*$|" + "^What (date|day) was (it )?[1-9]+[0-9]* (day|days) ago[?]*$", + re.IGNORECASE, + ) +) +async def find_date_before_days_from_current_date(message: types.Message): + user_input = message.text + extracted_days = extract_from_user_input_days_num(user_input) + + today = datetime.now() + answer = today - timedelta(days=extracted_days) + format_answer = answer.strftime("%d.%m.%Y") + + await message.reply( + f"{extracted_days} {check_day(extracted_days)} ago it was {format_answer}!" + ) + + +@dp.message_handler( + regexp=re.compile( + "^(Яка дата|Який день) буде через [1-9]+[0-9]* (день|днів|дня),* якщо починати з " + "([1-9]|0[1-9]|[1-2][0-9]|3[0-1]).([1-9]|0[1-9]|1[0-2]).([1-9]+.*[0-9]+)[?]*$|" + "^What (date|day) will be after [1-9]+[0-9]* (day|days) if we start from " + "([1-9]|0[1-9]|[1-2][0-9]|3[0-1]).([1-9]|0[1-9]|1[0-2]).([1-9]+.*[0-9]+)[?]*$", + re.IGNORECASE, + ) +) +async def find_date_after_days(message: types.Message): + user_input = message.text + extracted_data = extract_from_user_input_days_and_date(user_input) + days_num = extracted_data[0] + month_day = extracted_data[1][0] + month = extracted_data[1][1] + year = extracted_data[1][2] + + try: + user_date = datetime(day=month_day, month=month, year=year) + format_user_date = user_date.strftime("%d.%m.%Y") + answer = user_date + timedelta(days=days_num) + format_answer = answer.strftime("%d.%m.%Y") + + await message.reply( + f"If start from {format_user_date} after " + f"{days_num} {check_day(days_num)}, date will be {format_answer}!" + ) + except ValueError: + await message.reply( + f"Some data is entered incorrectly!!!\n\n" + f"I think the problem is the {month_day} (first number in your date) because " + f"the month you entered does not contain such day of the month.", + parse_mode="HTML", + ) diff --git a/bot/other_functions/cancel_states.py b/bot/other_functions/cancel_states.py deleted file mode 100644 index 76c4648..0000000 --- a/bot/other_functions/cancel_states.py +++ /dev/null @@ -1,4 +0,0 @@ -async def cancel_all_states_if_they_were(state): - current_state = await state.get_state() - if current_state is not None: - await state.finish() diff --git a/bot/other_functions/check_date_words.py b/bot/other_functions/check_date_words.py deleted file mode 100644 index 0ab6232..0000000 --- a/bot/other_functions/check_date_words.py +++ /dev/null @@ -1,25 +0,0 @@ -def check_year(years): - if years == 1: - years_message = 'year' - else: - years_message = 'years' - - return years_message - - -def check_month(months): - if months == 1: - months_message = 'month' - else: - months_message = 'months' - - return months_message - - -def check_day(days): - if days == 1: - days_message = 'day' - else: - days_message = 'days' - - return days_message diff --git a/bot/other_functions/check_for_comma_and_dot.py b/bot/other_functions/check_for_comma_and_dot.py deleted file mode 100644 index a3fa447..0000000 --- a/bot/other_functions/check_for_comma_and_dot.py +++ /dev/null @@ -1,26 +0,0 @@ -def check_user_id(message): - user_id = '' - if ', ' in message: - user_id = message[:message.find(',')] - elif ',' in message: - user_id = message[:message.find(',')] - elif '. ' in message: - user_id = message[:message.find('.')] - elif '.' in message: - user_id = message[:message.find('.')] - - return user_id - - -def check_user_data(message): - user_data = '' - if ', ' in message: - user_data = message[message.find(',') + 2:] - elif ',' in message: - user_data = message[message.find(',') + 1:] - elif '. ' in message: - user_data = message[message.find('.') + 2:] - elif '.' in message: - user_data = message[message.find('.') + 1:] - - return user_data diff --git a/bot/other_functions/check_length_keyboard.py b/bot/other_functions/check_length_keyboard.py deleted file mode 100644 index abcc22c..0000000 --- a/bot/other_functions/check_length_keyboard.py +++ /dev/null @@ -1,13 +0,0 @@ -def keyboard_length_choice(user_choice) -> int: - if user_choice == 'very_easy': - return 10 - elif user_choice == 'easy': - return 20 - elif user_choice == 'normal': - return 30 - elif user_choice == 'hard': - return 40 - elif user_choice == 'very_hard': - return 50 - elif user_choice == 'unbreakable': - return 100 diff --git a/bot/other_functions/check_radio_keyboard.py b/bot/other_functions/check_radio_keyboard.py deleted file mode 100644 index 8fd8485..0000000 --- a/bot/other_functions/check_radio_keyboard.py +++ /dev/null @@ -1,19 +0,0 @@ -from string import digits, ascii_letters - -from bot.bot_main.for_password_generation.generate_password import exclude_invalid_symblols_for_markup - - -def keyboard_content_choice(user_choice) -> str: - allowed_punctuation = exclude_invalid_symblols_for_markup() - if user_choice == 'all_characters': - return digits + ascii_letters + allowed_punctuation - elif user_choice == 'only_letters': - return ascii_letters - elif user_choice == 'only_digits': - return digits - elif user_choice == 'letters_digits': - return digits + ascii_letters - elif user_choice == 'letters_signs': - return ascii_letters + allowed_punctuation - elif user_choice == 'digits_signs': - return digits + allowed_punctuation diff --git a/bot/other_functions/close_keyboard.py b/bot/other_functions/close_keyboard.py deleted file mode 100644 index a7fbae9..0000000 --- a/bot/other_functions/close_keyboard.py +++ /dev/null @@ -1,7 +0,0 @@ -from bot.other_functions.message_attribute_error import message_attribute_control - - -async def close_keyboard(call): - await message_attribute_control(call) - await call.message.delete() - await call.answer() diff --git a/bot/other_functions/currency_cost.py b/bot/other_functions/currency_cost.py deleted file mode 100644 index 60d3134..0000000 --- a/bot/other_functions/currency_cost.py +++ /dev/null @@ -1,61 +0,0 @@ -import json -from config import CURRENCY_URL, BITCOIN_URL -from urllib.request import urlopen - -response = urlopen(CURRENCY_URL) -data_json_list = json.loads(response.read()) - -bitcoin_response = urlopen(BITCOIN_URL) -bitcoin_json = json.loads(bitcoin_response.read()) - - -def find_dollars_buy_in_hryvnias() -> float: - return 1 / float(data_json_list[1]['buy']) - - -def find_euros_buy_in_hryvnias() -> float: - return 1 / float(data_json_list[0]['buy']) - - -def find_dollars_buy_in_euros() -> float: - return float(data_json_list[0]['buy']) / float(data_json_list[1]['buy']) - - -def find_hryvnias_buy_in_euros() -> float: - return float(data_json_list[0]['buy']) - - -def find_euros_buy_in_dollars() -> float: - return float(data_json_list[1]['buy']) / float(data_json_list[0]['buy']) - - -def find_hryvnias_buy_in_dollars() -> float: - return float(data_json_list[1]['buy']) - - -def find_dollars_sale_in_hryvnias() -> float: - return 1 / float(data_json_list[1]['sale']) - - -def find_euros_sale_in_hryvnias() -> float: - return 1 / float(data_json_list[0]['sale']) - - -def find_dollars_sale_in_euros() -> float: - return float(data_json_list[0]['sale']) / float(data_json_list[1]['sale']) - - -def find_hryvnias_sale_in_euros() -> float: - return float(data_json_list[0]['sale']) - - -def find_euros_sale_in_dollars() -> float: - return float(data_json_list[1]['sale']) / float(data_json_list[0]['sale']) - - -def find_hryvnias_sale_in_dollars() -> float: - return float(data_json_list[1]['sale']) - - -def bitcoin_price() -> float: - return float(bitcoin_json['price']) diff --git a/bot/other_functions/encryption_decryption.py b/bot/other_functions/encryption_decryption.py deleted file mode 100644 index c1e7a03..0000000 --- a/bot/other_functions/encryption_decryption.py +++ /dev/null @@ -1,12 +0,0 @@ -import base64 - - -def encrypt(data) -> str: - encode = base64.b85encode(data.encode('UTF-16')) - sub_start_symbols = (encode.decode()).split('|N', 1)[1] # -> into database - return sub_start_symbols - -def decrypt(data) -> str: - add_start_symbols = '|N' + data - decode = base64.b85decode(add_start_symbols).decode('UTF-16') # -> from database - return decode diff --git a/bot/other_functions/extract_random_data.py b/bot/other_functions/extract_random_data.py deleted file mode 100644 index 2fdb884..0000000 --- a/bot/other_functions/extract_random_data.py +++ /dev/null @@ -1,6 +0,0 @@ -from random import choice - - -def get_random_data(data_arg): - result = choice(data_arg) - return result[0] diff --git a/bot/other_functions/get_days_and_date_num.py b/bot/other_functions/get_days_and_date_num.py deleted file mode 100644 index fd2ef76..0000000 --- a/bot/other_functions/get_days_and_date_num.py +++ /dev/null @@ -1,22 +0,0 @@ -import re - - -def exctract_from_user_input_days_num(user_input): - days_num_pattern = re.compile('[1-9]+[0-9]*') - - days_num = re.findall(days_num_pattern, user_input) - return int(days_num[0]) - - -def exctract_from_user_input_days_and_date(user_input): - days_num = exctract_from_user_input_days_num(user_input) - date_pattern = re.compile('([1-9]|0[1-9]|[1-2][0-9]|3[0-1]).([1-9]|0[1-9]|1[0-2]).([1-9]+.*[0-9]+)') - - result_date = re.findall(date_pattern, user_input) - - int_list = [] - for date_element in result_date[0]: - int_list.append(int(date_element)) - result_date = int_list - - return days_num, result_date diff --git a/bot/other_functions/get_id_and_convert.py b/bot/other_functions/get_id_and_convert.py deleted file mode 100644 index 05e1476..0000000 --- a/bot/other_functions/get_id_and_convert.py +++ /dev/null @@ -1,8 +0,0 @@ -def get_id_from_str(table_name): - return int( - ''.join( - decimal - for decimal in table_name - if decimal.isdecimal() - ) - ) diff --git a/bot/other_functions/work_with_json.py b/bot/other_functions/work_with_json.py deleted file mode 100644 index b355dc5..0000000 --- a/bot/other_functions/work_with_json.py +++ /dev/null @@ -1,37 +0,0 @@ -import os - -from bot.bot_main.main_objects_initialization import unique_table -from bot.other_functions.write_password_data_to_json import write_to_json - - -async def send_json(call): - make_json_dir() - user_id = call.from_user.id - password_data_list = unique_table.select_pass_gen_table(f'pass_gen_table_{user_id}') - write_to_json(user_id, password_data_list) - - with open(f'users_json_files/user_json_{user_id}.json', 'rb') as json_file: - json_content = json_file.read() - - sent_message = await call.bot.send_document( - chat_id=call.message.chat.id, - document=('Passwords.json', json_content), - caption=f'Json file for @{call.from_user.username}', - parse_mode='HTML' - ) - - return sent_message - - -def make_json_dir(): - path = f'users_json_files' - - if not os.path.exists(path): - os.makedirs(path) - - -def remove_json(call): - path_to_json = f'users_json_files/user_json_{call.from_user.id}.json' - - if os.path.exists(path_to_json): - os.remove(path_to_json) diff --git a/bot/other_functions/write_password_data_to_json.py b/bot/other_functions/write_password_data_to_json.py deleted file mode 100644 index caf209f..0000000 --- a/bot/other_functions/write_password_data_to_json.py +++ /dev/null @@ -1,29 +0,0 @@ -import base64 -import os -import json - -from bot.other_functions.encryption_decryption import decrypt - -NUMBER_OF_JSON_IDENTS = 12 - -def write_to_json(user_id, table_rows): - data = [ - { - 'Password №': password_number, - 'ID': password_data[0], - 'Password description': password_data[1], - 'Password': decrypt(password_data[2]), - 'Length': password_data[3], - 'Has repetitive?': password_data[4], - } - for password_number, password_data in enumerate(table_rows, 1) - ] - - directory = 'users_json_files' - file_path = os.path.join(directory, f'user_json_{user_id}.json') - - if not os.path.exists(directory): - os.makedirs(directory) - - with open(file_path, 'w') as outfile: - json.dump(data, outfile, indent=NUMBER_OF_JSON_IDENTS, ensure_ascii=False) diff --git a/bot/parsing/parse_link.py b/bot/parsing/parse_link.py deleted file mode 100644 index cf93dbe..0000000 --- a/bot/parsing/parse_link.py +++ /dev/null @@ -1,18 +0,0 @@ -from youtubesearchpython import VideosSearch - - -def parse_link(i: int, serch_term: str) -> str: - videos_search = VideosSearch(f'{serch_term}', limit=i) - return str(videos_search.result()) - - -def links_split(serch_term: str) -> list: - list_of_links = [] - for i in range(1, 6): - result_str = parse_link(i, serch_term) - result_str = result_str.split("'link': ")[-1] - result_str = result_str.split(",")[0] - result_str = result_str.strip("'") - list_of_links.append(result_str) - - return list_of_links diff --git a/bot/parsing/parse_temprature.py b/bot/parsing/parse_temprature.py deleted file mode 100644 index 2dfd74c..0000000 --- a/bot/parsing/parse_temprature.py +++ /dev/null @@ -1,94 +0,0 @@ -import re -import requests -from bs4 import BeautifulSoup as Bs - - -def parse_temp_at_time(url: str) -> str: - r = requests.get(url) - html = Bs(r.content, 'html.parser') - time = html.select('.imgBlock p') - temp_at_time = html.select('.today-temp') - - return time[0].text + ': ' + temp_at_time[0].text - - -def parse_minmax_temp(url: str) -> str: - r = requests.get(url) - html = Bs(r.content, 'html.parser') - min_temp = html.select('.min span') - max_temp = html.select('.max span') - - return 'Min temprature: ' + min_temp[0].text + 'C\n' + \ - 'Max temprature: ' + max_temp[0].text + 'C\n' - - -def parse_today_temp(url: str) -> list: - r = requests.get(url) - html = Bs(r.content, 'html.parser') - - today_temp = html.select('.temperature td') - - new_list = [] - for td in today_temp: - new_list.append(td.text) - - return new_list - - -def list_of_tuples(url: str) -> list: - initial_list = parse_today_temp(url) - new_list = [(initial_list[i - 1], initial_list[i]) for i in range(1, len(initial_list), 2)] - return new_list - - -def parse_avarage_precipitation_probability(url: str) -> int: - r = requests.get(url) - html = Bs(r.content, 'html.parser') - - precipitation_probability = html.select('tr:nth-child(8) td') - count = 0 - temp = 0 - precipitation_probability_list = [] - for td in precipitation_probability: - if td.text == '-': - precipitation_probability_list.append(0) - temp = temp - else: - precipitation_probability_list.append(int(td.text)) - temp += int(td.text) - count += 1 - - return max(precipitation_probability_list) - - -def check_for_negative_temp(temprature: float) -> str: - if temprature > 0: - return '+' + str(temprature) + '°C' - else: - return str(temprature) + '°C' - - -def find_avarage_temp_between_two(url: str) -> list: - temp_lst = list_of_tuples(url) - result_list = [] - - for i in temp_lst: - sum_of_two = 0 - - for j in range(2): - result = int(re.search(r'-*\d+', i[j]).group()) - sum_of_two += result - - result_list.append(check_for_negative_temp(sum_of_two / 2)) - - return result_list - - -def avarage_day_temp(url: str) -> str: - avarage_temp = 0 - count = 0 - for i in parse_today_temp(url): - avarage_temp += int(re.search(r'-*\d+', i).group()) - count += 1 - - return 'Avarage temprature: ' + check_for_negative_temp(avarage_temp / count) + '\n\n' diff --git a/config.py b/config.py deleted file mode 100644 index 0fb3446..0000000 --- a/config.py +++ /dev/null @@ -1,56 +0,0 @@ -API_TOKEN = '' # add your Telegram BOT API into quotes - -UA_COMMAND_DESC_LIST = ''' -/start - перевірка роботи бота -/random - генерація випадкового цілого числа -/eugene - функція пошуку відео -/token - генерація токенів -/time - дата, поточний час та інша інформація стосовно часу -/sticker - відправляє випадковий стікер -/currency - конвертер валют -/weather - погода в заданому населеному пункті -/birthday - обрахунок днів до заданої дати -/taskscheduler - планувальник завдань -/photo - додавання тексту на фото, зміна розмірів фото -/password - генератор випадкових паролів -/keywords - список ключових слів, на які реагує бот -/help - список команд -''' - -EN_COMMAND_DESC_LIST = ''' -/start - check if bot is working -/random - generate random integer number -/eugene - youtube video search -/token - token generation -/time - current date, time and other time info -/sticker - send a random sticker -/currency - currency converter -/weather - show the weather in the entered settlement -/birthday - calculate number of days from current date to another -/taskscheduler - create a list of tasks -/photo - adding text to photo, change photo size -/password - random password generator -/keywords - list of keywords that the bot responds to -/help - command list -''' - -COMMAND_LIST = [ - '/start', - '/random', - '/eugene', - '/id', - '/time', - '/sticker', - '/currency', - '/weather', - '/birthday', - '/taskscheduler', - '/photo', - '/password', - '/keywords', - '/help', -] - -# CURRENCY_URL = 'https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5' -CURRENCY_URL = 'https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=11' -BITCOIN_URL = 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT' diff --git a/imgs/result.jpg b/imgs/result.jpg index 8cccfb8..14b2089 100644 Binary files a/imgs/result.jpg and b/imgs/result.jpg differ diff --git a/imgs/result_auto_conf.jpg b/imgs/result_auto_conf.jpg index 5b2261e..f325fe7 100644 Binary files a/imgs/result_auto_conf.jpg and b/imgs/result_auto_conf.jpg differ diff --git a/imgs/test.jpg b/imgs/test.jpg index 0214c5f..dbc351f 100644 Binary files a/imgs/test.jpg and b/imgs/test.jpg differ diff --git a/imgs/test_auto_conf.jpg b/imgs/test_auto_conf.jpg index 2f90a69..229118c 100644 Binary files a/imgs/test_auto_conf.jpg and b/imgs/test_auto_conf.jpg differ diff --git a/main.py b/main.py index 31b7cb5..a386119 100644 --- a/main.py +++ b/main.py @@ -1,18 +1,10 @@ -from aiogram import executor - -from bot.bot_main import main_objects_initialization -from bot.bot_main.commands_and_keywords import main_commands, random_numbers_generation_command, user_info_command, \ - sticker_command, keywords_command, time_commands, add_text_to_photo_command, weather_command, \ - youtube_search_command, currency_converter_command, task_scheduler_command, password_generator_command, \ - keywords_for_interaction - -bot = main_objects_initialization.bot -dp = main_objects_initialization.dp +from bot import MyBot def main(): - executor.start_polling(dp, skip_updates=True) + custom_bot = MyBot() + custom_bot.start() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/password_generator_app/Password_Generator_Linux_MacOS.zip b/password_generator_app/Password_Generator_Linux_MacOS.zip deleted file mode 100644 index 0292fbb..0000000 Binary files a/password_generator_app/Password_Generator_Linux_MacOS.zip and /dev/null differ diff --git a/password_generator_app/Password_Generator_v2_Linux.zip b/password_generator_app/Password_Generator_v2_Linux.zip new file mode 100644 index 0000000..b0fe929 Binary files /dev/null and b/password_generator_app/Password_Generator_v2_Linux.zip differ diff --git a/requirements.txt b/requirements.txt index a256816..9f6a42a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,14 @@ aiogram==2.23.1 -beautifulsoup4==4.11.1 -mysql-connector-python==8.0.31 +mysql-connector-python==8.1.0 mysql==0.0.3 -mysqlclient==2.1.1 -Pillow==9.3.0 -pip==22.3.1 -urllib3==1.26.13 -requests==2.28.1 -youtube-search-python==1.6.6 -config~=0.5.1 -pytz~=2022.7 \ No newline at end of file +mysqlclient==2.2.0 +urllib3>=2.0.6 +Pillow>=10.0.1 +requests==2.31.0 +python-dotenv~=1.0.0 +beautifulsoup4~=4.12.2 +reportlab~=4.0.6 +python-docx~=1.0.0 +# youtube-search-python==1.6.6 +# pdf2docx~=0.5.6 +# selenium~=4.13.0