diff --git a/.gitignore b/.gitignore index 57f7cd3..5de7aa3 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ main_yaml_groups/* Новая таблица - 2 курс весна 2025 copy.csv Новая таблица - 2 курс весна 2025.csv schedule copy.sql +tmp/ +credentials.json \ No newline at end of file diff --git a/1_kurs_vesna_2025_0.pdf b/1_kurs_vesna_2025_0.pdf deleted file mode 100644 index 82fc568..0000000 Binary files a/1_kurs_vesna_2025_0.pdf and /dev/null differ diff --git a/2_kurs_vesna_2025_0.pdf b/2_kurs_vesna_2025_0.pdf deleted file mode 100644 index 6890f6a..0000000 Binary files a/2_kurs_vesna_2025_0.pdf and /dev/null differ diff --git a/app/db.py b/app/db.py index 8f7d130..6bf992f 100644 --- a/app/db.py +++ b/app/db.py @@ -55,10 +55,18 @@ def into_list(rows: list[tuple], column_name: list[tuple]) -> list[dict]: def save_user(infos): + # infos = [ + # call.from_user.id, + # call.from_user.first_name, + # call.from_user.last_name, + # call.from_user.username, + # call.data, + # ] + conn = db.getconn() cursor = conn.cursor() cursor.execute( - "INSERT INTO users (id, username, first_name, last_name, group) VALUES (%s, %s, %s, %s, %s)", + 'INSERT INTO users (id, first_name, last_name, username, "group") VALUES (%s, %s, %s, %s, %s) ON CONFLICT (id) DO UPDATE SET first_name = EXCLUDED.first_name, last_name = EXCLUDED.last_name, username = EXCLUDED.username, "group" = EXCLUDED."group"', infos, ) @@ -96,15 +104,36 @@ def parse_lesson(lesson: dict[str, str]) -> str: if lesson["parity"] == 1: lesson["course"] += " (нечёт.)" - text = "{}-{} | {}\ - \n{}\ - \n{}".format( - start, - end, - lesson["room"], - lesson["course"], - lesson["lector"], - ) + if lesson["lector"]: + if "/" not in lesson["lector"]: + text = "{}-{} | {}\ + \n{}\ + \n{}".format( + start, + end, + lesson["room"], + lesson["course"], + lesson["lector"], + ) + else: + lectors = lesson["lector"].split("/") + text = "{}-{} | {}\ + \n{}\ + \n{} / {}".format( + start, + end, + lesson["room"].replace("/", ", "), + lesson["course"], + lectors[0], + lectors[1], + ) + else: + text = "{}-{}\ + \n{}".format( + start, + end, + lesson["course"], + ) return text @@ -132,8 +161,10 @@ def create_schedule_tasks(manual=False): if group == "other": continue - cursor.execute("SELECT * FROM schedule WHERE group = %s", (group,)) + cursor.execute('SELECT * FROM schedule WHERE "group" = %s', (group,)) data = cursor.fetchall() + if not data: + continue schdl = into_list(data, cursor.description) for lesson in schdl: @@ -191,7 +222,7 @@ def create_schedule_tasks(manual=False): def groups_in_json() -> list[str]: conn = db.getconn() cursor = conn.cursor() - cursor.execute("SELECT DISTINCT group FROM schedule ORDER BY group") + cursor.execute('SELECT DISTINCT "group" FROM schedule ORDER BY "group"') data = cursor.fetchall() groups = [row[0] for row in data] db.putconn(conn) @@ -204,7 +235,7 @@ def students_in_group(group) -> list[str]: conn = db.getconn() cursor = conn.cursor() - cursor.execute("SELECT * FROM users WHERE group = %s", (group,)) + cursor.execute('SELECT * FROM users WHERE "group" = %s', (group,)) data = cursor.fetchall() students = [row[0] for row in data] db.putconn(conn) @@ -251,7 +282,7 @@ def get_schedule(id: str, day: str) -> str: conn = db.getconn() cursor = conn.cursor() - cursor.execute("SELECT group FROM users WHERE id = %s", (id,)) + cursor.execute('SELECT "group" FROM users WHERE id = %s', (id,)) data = cursor.fetchone() group = data[0] db.putconn(conn) @@ -259,9 +290,12 @@ def get_schedule(id: str, day: str) -> str: if group == "other": return "У тебя не выбрана группа для рассылки сообщений" + if group not in groups_in_json(): + return f"Такой группы нет в базе. Проверь в таблице существование расписания для твоей группы" + conn = db.getconn() cursor = conn.cursor() - cursor.execute("SELECT * FROM schedule WHERE group = %s", (group,)) + cursor.execute('SELECT * FROM schedule WHERE "group" = %s', (group,)) data = cursor.fetchall() schdl = into_list(data, cursor.description) db.putconn(conn) diff --git a/app/install.sh b/app/install.sh index 397ab76..846c9ad 100644 --- a/app/install.sh +++ b/app/install.sh @@ -1,7 +1,7 @@ #!/bin/bash # List of environment variables to check -env_vars=("TG_ADMIN_ID" "TG_API_TOKEN" "DB_HOST" "DB_PORT" "DB_USER" "DB_PASSWORD" "DB_NAME") +env_vars=("TG_ADMIN_ID" "TG_API_TOKEN" "DB_HOST" "DB_PORT" "DB_USER" "DB_PASSWORD" "DB_NAME" "TABLE_ID") for var in "${env_vars[@]}"; do if [ -z "${!var}" ]; then @@ -10,4 +10,5 @@ for var in "${env_vars[@]}"; do # else # echo "Environment variable $var is set to '${!var}'." fi -done \ No newline at end of file +done + diff --git a/app/schedules.py b/app/schedules.py index 7b12365..ffec3e0 100644 --- a/app/schedules.py +++ b/app/schedules.py @@ -10,7 +10,7 @@ from dotenv import load_dotenv load_dotenv() -bot = telebot.TeleBot(os.environ.get("TG_TEST_TOKEN")) +bot = telebot.TeleBot(os.environ.get("TG_TEST_TOKEN"), parse_mode=ParseMode.HTML) PARITY_FIRST = 0 @@ -31,6 +31,11 @@ def help(message): send_message(message.chat.id, help_msg) + help_msg2 = f"Бот берёт расписание из таблицы, которую можно посмотреть по ссылке:\ + \nРасписание" + + send_message(message.chat.id, help_msg2) + if is_admin(message): admin_help_msg = "И команды только для админа:\ \n/restart - перезапуск бота.\ @@ -262,16 +267,16 @@ def start(message): else: send_message( message.chat.id, - "Похоже, что расписания для этой группы ещё не существует. \ - \nРазработчик уже пинается, но можете дополнительно написать ему: @Kr0sH_512", + f"Похоже, что расписания для этой группы ещё не существует. \ + \nПроверьте расписание своей группы в этой таблице", ) - send_admin_message( - "В группе {} был запрос на {} группу.\ - \nid: {}".format( - message.chat.title, temp, message.chat.id - ) - ) + # send_admin_message( + # "В группе {} был запрос на {} группу.\ + # \nid: {}".format( + # message.chat.title, temp, message.chat.id + # ) + # ) temp = "other" db.save_user(infos) else: @@ -290,6 +295,8 @@ def start(message): # markup.add(types.InlineKeyboardButton(text=i, callback_data=i)) markup.add(types.InlineKeyboardButton(text="1 курс", callback_data="1course")) markup.add(types.InlineKeyboardButton(text="2 курс", callback_data="2course")) + markup.add(types.InlineKeyboardButton(text="3 курс", callback_data="2course")) + markup.add(types.InlineKeyboardButton(text="4 курс", callback_data="2course")) bot.send_message( message.chat.id, @@ -346,8 +353,9 @@ def callback_inline(call): bot.edit_message_text( chat_id=call.message.chat.id, message_id=call.message.message_id, - text="Используй команду /request и напиши номер группы, которую хочешь добавить\ - \nПосле создания расписания для твоей группы, я пришлю тебе сообщение", + text=f"Пожалуйста, проверь расписание для своей группы в этой таблице", + # text="Используй команду /request и напиши номер группы, которую хочешь добавить\ + # \nПосле создания расписания для твоей группы, я пришлю тебе сообщение", parse_mode=ParseMode.HTML, ) else: @@ -605,7 +613,7 @@ def send_message(id, text, thread_id="General", parity=None): if thread_id == "General": thread_id = None - if parity != None: + if parity and parity != "-": parity = int(parity) count_of_weeks = (datetime.now() - datetime(2024, 2, 5)).days // 7 is_odd = (count_of_weeks + 1) % 2 == PARITY_FIRST diff --git a/app/upload_sql.py b/app/upload_sql.py index e69de29..8da1562 100644 --- a/app/upload_sql.py +++ b/app/upload_sql.py @@ -0,0 +1,164 @@ +#!/usr/bin/python3.3 +import gspread, os, telebot +import pandas as pd +from dotenv import load_dotenv +from google.oauth2 import service_account +from sshtunnel import SSHTunnelForwarder +from psycopg2 import pool + +load_dotenv() + + +""" +CREATE TABLE IF NOT EXISTS schedule( + id BIGSERIAL PRIMARY KEY, + day_of_week VARCHAR(255), + begin_time VARCHAR(20), + end_time VARCHAR(20), + course VARCHAR(255), + lector VARCHAR(255), + is_lecture BOOLEAN DEFAULT FALSE, + room VARCHAR(20), + group VARCHAR(10), + parity VARCHAR(10), +); +""" + +""" +День недели Время начала Название Чётность пары Лекция? Преподаватель Кабинет 2-ой преподаватель Прочие кабинеты Остальная информация +Понедельник 8:45 Английский язык - FALSE Бим Мария Моисеевна 735 Шубина Юлия Витальевна 790 +Понедельник 10:30 Практикум на ЭВМ - FALSE Тюляева Вера Викторовна МЗ-4 +Понедельник 12:15 Действительный и комплексный анализ - TRUE Крицков Леонид Владимирович П-12 +Понедельник 15:00 Физическая культура - FALSE +Понедельник 16:20 FALSE +Вторник 8:45 Теория вероятностей и математическая статистика - TRUE Королёв Виктор Юрьевич П-13 +Вторник 10:30 Обыкновенные дифференциальные уравнения - FALSE Дмитриева Ирина Владимировна 607 +Вторник 12:15 Действительный и комплексный анализ - FALSE Сычугов Дмитрий Юрьевич 613 +Вторник 14:35 Электродинамика - FALSE Аксенов Владимир Николаевич 659 +Вторник 16:20 - FALSE +Среда 8:45 Практикум на ЭВМ - FALSE Тюляева Вера Викторовна 505 +Среда 10:30 Теория вероятностей и математическая статистика - FALSE Ирина Геннадьевна Шевцова 645 +Среда 12:15 Обыкновенные дифференциальные уравнения - TRUE Денисов Александр Михайлович П-13 +Среда 15:00 FALSE +Среда 16:20 FALSE +Четверг 9:00 FALSE +Четверг 10:30 FALSE +Четверг 12:15 FALSE +Четверг 15:00 FALSE +Четверг 16:20 FALSE +Пятница 9:00 FALSE +Пятница 10:30 FALSE +Пятница 12:15 FALSE +Пятница 15:00 FALSE +Пятница 16:20 FALSE +Суббота 9:00 FALSE +Суббота 10:30 FALSE +Суббота 12:15 FALSE +Суббота 15:00 FALSE +Суббота 16:20 FALSE + FALSE + FALSE + FALSE + FALSE + FALSE + FALSE + FALSE + FALSE + FALSE +""" + +SERVICE_ACCOUNT_FILE = "app/credentials.json" + +credentials = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_FILE, + scopes=[ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/documents", + ], +) + +gc = gspread.authorize(credentials) + +table = gc.open_by_key(os.environ.get("TABLE_ID")) + +ws = table.worksheets() + +bot = telebot.TeleBot(os.environ.get("TG_TEST_TOKEN")) + +if os.environ.get("ENV") == "dev": + print("DEV: Connecting to local database") + server = SSHTunnelForwarder( + (os.environ.get("SSH_HOST"), 22), + ssh_private_key=os.environ.get("SSH_KEY"), + ssh_username=os.environ.get("SSH_USER"), + ssh_password=( + os.environ.get("SSH_PASSWORD") if os.environ.get("SSH_PASSWORD") else None + ), + remote_bind_address=("localhost", int(os.environ.get("DB_PORT"))), + ) + server.start() + +db = pool.SimpleConnectionPool( + 1, + 20, + user=os.environ.get("DB_USER"), + password=os.environ.get("DB_PASSWORD"), + host="localhost" if os.environ.get("ENV") == "dev" else os.environ.get("DB_HOST"), + port=( + server.local_bind_port + if os.environ.get("ENV") == "dev" + else os.environ.get("DB_PORT") + ), + database=os.environ.get("DB_NAME"), +) + + +for i in ws: + print(i.title) + + if not (i.title == "Пример" or i.title == "main") and i.title.isdigit(): + df = pd.DataFrame(i.get_all_records()) + if not df.empty: + with db.getconn() as conn: + with conn.cursor() as cursor: + cursor.execute( + 'DELETE FROM schedule WHERE "group" = %s', (str(i.title),) + ) + conn.commit() + for index, row in df.iterrows(): + day_of_week_map = { + "Понедельник": "mon", + "Вторник": "tue", + "Среда": "wed", + "Четверг": "thu", + "Пятница": "fri", + "Суббота": "sat", + "Воскресенье": "sun", + } + row["День недели"] = day_of_week_map.get( + row["День недели"], row["День недели"] + ) + if row["Название"] == "": + continue + end_time = ( + pd.to_datetime(row["Время начала"], format="%H:%M") + + pd.Timedelta(hours=1, minutes=35) + ).strftime("%H:%M") + cursor.execute( + """ + INSERT INTO schedule (day_of_week, begin_time, end_time, course, lector, is_lecture, room, "group", parity) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + row["День недели"], + row["Время начала"], + end_time, + row["Название"], + f'{row["Преподаватель"]}{"/" + str(row["2-ой преподаватель"]) if row["2-ой преподаватель"] else ""}', + row["Лекция?"], + f'{row["Кабинет"]}{"/" + str(row["Прочие кабинеты"]) if row["Прочие кабинеты"] else ""}', + i.title, + row["Чётность пары"], + ), + ) + conn.commit() diff --git a/database.sql b/database.sql index 723e044..17a0c16 100644 --- a/database.sql +++ b/database.sql @@ -1,11 +1,11 @@ -CREATE DATABASE IF NOT EXISTS tg_schedule; +CREATE DATABASE tg_schedule; CREATE TABLE IF NOT EXISTS users ( id BIGINT PRIMARY KEY, username VARCHAR(255), - first_name VARCHAR(255) NOT NULL, + first_name VARCHAR(255), last_name VARCHAR(255), - group VARCHAR(255) DEFAULT 'other', + "group" VARCHAR(255) DEFAULT 'other', timeout INT DEFAULT 10, allow_message BOOLEAN DEFAULT TRUE, thread VARCHAR(255) DEFAULT 'General', @@ -21,6 +21,6 @@ CREATE TABLE IF NOT EXISTS schedule( lector VARCHAR(255), is_lecture BOOLEAN DEFAULT FALSE, room VARCHAR(20), - group VARCHAR(10), - parity VARCHAR(10), + "group" VARCHAR(10), + parity VARCHAR(10) ); \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f688902..e2e308b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,11 @@ pytelegrambotapi +python-telegram-bot coloredlogs psycopg2-binary sshtunnel python-dotenv -schedule \ No newline at end of file +schedule +google-api-python-client +oauth2client +gspread +pandas \ No newline at end of file diff --git a/schedule.sql b/schedule.sql deleted file mode 100644 index cc15518..0000000 --- a/schedule.sql +++ /dev/null @@ -1,104 +0,0 @@ -CREATE TABLE IF NOT EXISTS schedule( - id BIGSERIAL PRIMARY KEY, - day_of_week VARCHAR(255), - begin_time VARCHAR(20), - end_time VARCHAR(20), - course VARCHAR(255), - lector VARCHAR(255), - is_lecture BOOLEAN DEFAULT FALSE, - room VARCHAR(20), - group VARCHAR(10), - parity INT, -); - --- ('mon', '101', '08:45', '10:20', '', '', ''), --- ('mon', '101', '10:30', '12:05', '', '', ''), --- ('mon', '101', '12:50', '14:25', '', '', ''), --- ('mon', '101', '14:35', '16:10', '', '', ''), --- ('mon', '101', '16:50', '18:20', '', '', ''), - -INSERT INTO schedule(day_of_week, group, begin_time, end_time, course, lector, room) VALUES -('mon', '101', '10:30', '12:05', 'Практикум на ЭВМ', 'Кузьменкова Е.А.', '731'), -('mon', '101', '10:30', '12:05', 'Практикум на ЭВМ', 'Малышев Н.Е.', '778'), -('mon', '101', '12:50', '14:25', 'Английский язык', 'Бим М.М.', '735'), -('mon', '101', '12:50', '14:25', 'Английский язык', 'Шубина Ю.В.', '790'), -('mon', '101', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '102', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '103', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '104', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '105', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '106', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '107', '14:35', '16:10', 'Русский язык и культура речи', 'Рождественская Ольга Юрьевна', 'П-6'), -('mon', '101', '16:50', '18:20', 'Физическая культура', '', ''), -('mon', '102', '16:50', '18:20', 'Физическая культура', '', ''), -('mon', '103', '16:50', '18:20', 'Физическая культура', '', ''), -('mon', '104', '16:50', '18:20', 'Физическая культура', '', ''), -('mon', '105', '16:50', '18:20', 'Физическая культура', '', ''), -('mon', '106', '16:50', '18:20', 'Физическая культура', '', ''), -('mon', '107', '16:50', '18:20', 'Физическая культура', '', ''), - -('mon', '102', '10:30', '12:05', 'Английский язык', 'Бим М.М.', '735'), -('mon', '102', '10:30', '12:05', 'Английский язык', 'Шубина Ю.В.', '790'), -('mon', '102', '12:50', '14:25', 'Математический анализ', 'Лапонин В.С.', '682'), - -('mon', '103', '08:45', '10:20', 'Английский язык', 'Лаптинова А.И.', '73'), -('mon', '103', '08:45', '10:20', 'Английский язык', 'Каришева П.В.', '72'), -('mon', '103', '10:30', '12:05', 'Математический анализ', 'Зайцева Н.В.', '71'), -('mon', '103', '12:50', '14:25', 'Практикум на ЭВМ', 'Кузьменкова Е.А.', '731'), -('mon', '103', '12:50', '14:25', 'Практикум на ЭВМ', 'Малышев Н.Е.', '778'), - -('mon', '104', '10:30', '12:05', 'Математический анализ', 'Лапонин В.С.', '682'), -('mon', '104', '12:50', '14:25', 'Английский язык', 'Каришева П.В.', '72'), - -('mon', '105', '10:30', '12:05', 'Математический анализ', 'Никитин А.А.', '72'), -('mon', '105', '12:50', '14:25', 'Алгебра и геометрия', 'Лебедева О.С.', '503'), - -('mon', '106', '10:30', '12:05', 'Практикум на ЭВМ', 'Корухова Л.С.', '779'), -('mon', '106', '10:30', '12:05', 'Практикум на ЭВМ', 'Соловьев М.А.', '780'), -('mon', '106', '12:15', '13:50', 'Алгебра и геометрия', 'Есикова Н.Б.', '504'), - -('mon', '107', '08:45', '10:20', 'Математический анализ', 'Зайцева Н.В.', '71'), -('mon', '107', '10:30', '12:05', 'Алгебра и геометрия', 'Есикова Н.Б.', '504'), -('mon', '107', '12:50', '14:25', 'Практикум на ЭВМ', 'Журихин Д.М.', 'МЗ-3'), -('mon', '107', '12:50', '14:25', 'Практикум на ЭВМ', 'Гуляев Д.А.', 'МЗ-2'), - - - - - - - - - -('tue', '101', '8:45', '10:20', 'Алгебра и геометрия', 'Матвеев С.А.', '713'), -('tue', '101', '10:30', '12:05', 'Алгебра и геометрия', 'академик РАН Тыртышников Евгений Евгеньевич', 'П-6'), -('tue', '101', '12:50', '14:25', 'Математический анализ', 'профессор Садовничая Инна Викторовна', 'П-6'), -('tue', '101', '14:35', '16:10', 'Математический анализ', 'Хорошилова Е.В.', '605'), -('tue', '101', '16:20', '17:55', 'Английский язык', 'Бим М.М.', '790'), - -('tue', '102', '8:45', '10:20', 'Английский язык', 'Шубина Ю.В.', '72'), -('tue', '102', '10:30', '12:05', 'Алгебра и геометрия', 'академик РАН Тыртышников Евгений Евгеньевич', 'П-6'), -('tue', '102', '12:50', '14:25', 'Математический анализ', 'профессор Садовничая Инна Викторовна', 'П-6'), -('tue', '102', '14:35', '16:10', 'Алгебра и геометрия', 'Руднев С.Г.', '607'), -('tue', '102', '16:20', '17:55', 'Практикум на ЭВМ', 'Цыбров Е.Г.', 'МЗ-1'), -('tue', '102', '16:20', '17:55', 'Практикум на ЭВМ', 'Гуляев Д.А.', 'МЗ-1'), - -('tue', '103', '8:45', '10:20', 'Конс. Алгебра и геометрия', 'Будак А.Б.', '612'), -('tue', '103', '10:30', '12:05', 'Алгебра и геометрия', 'академик РАН Тыртышников Евгений Евгеньевич', 'П-6'), -('tue', '103', '12:50', '14:25', 'Математический анализ', 'профессор Садовничая Инна Викторовна', 'П-6'), -('tue', '103', '14:35', '16:10', 'Алгебра и геометрия', 'Будак А.Б.', '613'), -('tue', '103', '16:20', '17:55', 'Английский язык', 'Лаптинова А.И.', '72'), -('tue', '103', '16:20', '17:55', 'Английский язык', 'Каришева П.В.', '582-а'), - -('tue', '104', '8:45', '10:20', 'Конс. Алгебра и геометрия', 'Будак А.Б.', '612'), -('tue', '104', '10:30', '12:05', 'Алгебра и геометрия', 'академик РАН Тыртышников Евгений Евгеньевич', 'П-6'), -('tue', '104', '12:50', '14:25', 'Математический анализ', 'профессор Садовничая Инна Викторовна', 'П-6'), -('tue', '104', '14:35', '16:10', 'Практикум на ЭВМ', 'Цыбров Е.Г.', 'МЗ-1'), -('tue', '104', '14:35', '16:10', 'Практикум на ЭВМ', 'Гуляев Д.А.', 'МЗ-1'), -('tue', '104', '16:20', '17:55', 'Алгебра и геометрия', 'Будак А.Б.', '613'), - -('tue', '105', '8:45', '10:20', 'Алгебра и геометрия', 'Лебедева О.С.', '503'), -('tue', '105', '10:30', '12:05', 'Алгебра и геометрия', 'академик РАН Тыртышников Евгений Евгеньевич', 'П-6'), -('tue', '105', '12:50', '14:25', 'Математический анализ', 'профессор Садовничая Инна Викторовна', 'П-6'), -('tue', '104', '14:35', '16:10', 'Практикум на ЭВМ', 'Русол А.В.', '574 '), -('tue', '104', '14:35', '16:10', 'Практикум на ЭВМ', 'Гуляев А.В.', '574 '),