diff --git a/callbacks.py b/callbacks.py new file mode 100644 index 0000000..e0a22c6 --- /dev/null +++ b/callbacks.py @@ -0,0 +1,137 @@ +import datetime + +import dateutil.parser +import pytz +import telegram + +import database +import gauth +import gcalendar + + +def get_menu(): + buttons = [["/plan", "/get_today_tasks"], ["/logout"]] + reply_markup = telegram.ReplyKeyboardMarkup(buttons, resize_keyboard=True) + return reply_markup + + +def text_callback(update, context): + reply_markup = telegram.ReplyKeyboardMarkup([["/cancel"]], resize_keyboard=True) + if "state" in context.user_data: + if context.user_data["state"] == "summary": + context.user_data["state"] = "date" + context.user_data["summary"] = update.message.text + context.bot.send_message(chat_id=update.message.chat_id, text="Введите дату в формате ГГГГ-ММ-ДД", + reply_markup=reply_markup) + elif context.user_data["state"] == "date": + context.user_data["state"] = "time" + # TODO: check if it is correct date + context.user_data["date"] = update.message.text + context.bot.send_message(chat_id=update.message.chat_id, text="Введите время события", + reply_markup=reply_markup) + elif context.user_data["state"] == "time": + gcalendar.set_new_task(update.message.chat_id, update.message.text, context.user_data["date"], + context.user_data["summary"]) + context.bot.send_message(chat_id=update.message.chat_id, text="Задача успешно добавлена", + reply_markup=get_menu()) + else: + context.bot.send_message(chat_id=update.message.chat_id, text="Команда не распознана", reply_markup=get_menu()) + + +def plan_callback(update, context): + context.user_data["state"] = "summary" + reply_markup = telegram.ReplyKeyboardMarkup([["/cancel"]], resize_keyboard=True) + context.bot.send_message(chat_id=update.message.chat_id, text="Введите текст для задачи.", + reply_markup=reply_markup) + + +def back_callback(update, context): + pass + + +def cancel_callback(update, context): + context.user_data.clear() + context.bot.send_message(chat_id=update.message.chat_id, text="Выберете команду", reply_markup=get_menu()) + + +def logout_callback(update, context): + db = database.Database() + chat_id = update.message.chat_id + db.delete_cred(chat_id) + start_callback(update, context) + + +def get_today_tasks_callback(update, context): + chat_id = update.message.chat_id + db = database.Database() + if db.is_auth(chat_id): + response = gcalendar.get_today_tasks_list(chat_id) + if not response: + context.bot.send_message(chat_id=chat_id, text="Ваш день сегодня свободен. Везет же)", + reply_markup=get_menu()) + else: + text = "События на сегодня: \n" + counter = 1 + for event in response: + text = text + str(counter) + ". " + event["start"].get("dateTime", event['start'].get('date')) + "\n" + text = text + event["summary"] + "\n" + counter += 1 + context.bot.send_message(chat_id=chat_id, text=text, reply_markup=get_menu()) + else: + reply_markup = telegram.ReplyKeyboardMarkup([["/login"]], resize_keyboard=True) + context.bot.send_message(chat_id=chat_id, text="Вам необходимо войти в аккаунт. Используйте /login", + reply_markup=reply_markup) + + +def login_callback(update, context): + db = database.Database() + chat_id = update.message.chat_id + if db.is_auth(chat_id): + context.bot.send_message(chat_id=chat_id, text="Вы уже вошли в свой аккаунт.", reply_markup=get_menu()) + else: + googleAuth = gauth.GoogleAuth(chat_id) + reply_markup = telegram.ReplyKeyboardMarkup([["/login"]], resize_keyboard=True) + # TODO : strange behavior with /login twice + context.bot.send_message(chat_id=chat_id, text=googleAuth.generate_url(), reply_markup=reply_markup) + + +def start_callback(update, context): + args = "".join(context.args) + if args == "": + reply_markup = telegram.ReplyKeyboardMarkup([["/login"]], resize_keyboard=True) + context.bot.send_message(chat_id=int(update.message.chat_id), + text="Здравствуйте! Вам нужно войти в свой аккаунт Google для использования этого бота", + reply_markup=reply_markup) + else: + db = database.Database() + chat_id = args + if db.is_auth(chat_id): + context.bot.send_message(chat_id=int(chat_id), text="Вы успешно вошли в аккаунт", reply_markup=get_menu()) + context.job_queue.run_daily(daily_announce, datetime.time(hour=8, minute=0, second=0, + tzinfo=pytz.timezone("Europe/Moscow"))) + else: + reply_markup = telegram.ReplyKeyboardMarkup([["/login"]], resize_keyboard=True) + context.bot.send_message(chat_id=int(chat_id), text="Вам необходимо войти в аккаунт. Используйте /login", + reply_markup=reply_markup) + + +def daily_announce(bot, job): + # TODO: Repeating code + chat_id = job.context + db = database.Database() + if db.is_auth(chat_id): + bot.send_message(chat_id=chat_id, text="Доброе утро!") + response = gcalendar.get_today_tasks_list(chat_id) + if not response: + bot.send_message(chat_id=chat_id, text="Ваш день сегодня свободен. Везет же)", + reply_markup=get_menu()) + else: + text = "События на сегодня: \n" + counter = 1 + # TODO: add enumerate + for event in response: + date = dateutil.parser.parse(event["start"].get("dateTime", event['start'].get('date'))) + text = text + str(counter) + ". {}:{}".format(date.time().hour, date.time().minute) + + "\n" + text = text + event["summary"] + "\n" + counter += 1 + bot.send_message(chat_id=chat_id, text=text, reply_markup=get_menu()) diff --git a/database.py b/database.py new file mode 100644 index 0000000..38384c6 --- /dev/null +++ b/database.py @@ -0,0 +1,40 @@ +import logging +import os + +import psycopg2 +from psycopg2.extras import RealDictCursor + + +class Database: + __instance = None + + def __new__(cls, *args, **kwargs): + if cls.__instance is None: + cls.__instance = super().__new__(cls, *args, **kwargs) + DATABASE_URL = os.environ['DATABASE_URL'] + try: + cls.__instance.conn = psycopg2.connect(DATABASE_URL, sslmode='require') + except ConnectionError: + logging.error("No database in DATABASE_URL environ") + return cls.__instance + + def get_cred(self, chat_id): + cursor = self.conn.cursor(cursor_factory=RealDictCursor) + cursor.execute("SELECT * FROM auth WHERE chat_id = %s", (str(chat_id),)) + return cursor.fetchall()[0] + + def is_auth(self, chat_id): + cursor = self.conn.cursor(cursor_factory=RealDictCursor) + cursor.execute("SELECT chat_id FROM auth WHERE chat_id = %s", (str(chat_id),)) + if cursor.fetchone() is not None: + cursor.close() + return True + else: + cursor.close() + return False + + def delete_cred(self, chat_id): + cursor = self.conn.cursor(cursor_factory=RealDictCursor) + cursor.execute("DELETE FROM auth WHERE chat_id = %s", (str(chat_id),)) + self.conn.commit() + cursor.close() diff --git a/gauth.py b/gauth.py new file mode 100644 index 0000000..3be9945 --- /dev/null +++ b/gauth.py @@ -0,0 +1,19 @@ +import google_auth_oauthlib.flow + + +class GoogleAuth: + def __init__(self, chat_id): + self.chat_id = chat_id + + def generate_url(self): + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + 'client_secret.json', + scopes=['https://www.googleapis.com/auth/calendar.events']) + + flow.redirect_uri = "https://server-for-task-bot.herokuapp.com/login" + state = str(self.chat_id) + authorization_url, st = flow.authorization_url( + access_type='offline', + state=state, + include_granted_scopes='true') + return authorization_url diff --git a/gcalendar.py b/gcalendar.py new file mode 100644 index 0000000..1321288 --- /dev/null +++ b/gcalendar.py @@ -0,0 +1,50 @@ +import datetime +import logging + +import google.oauth2.credentials +import googleapiclient.discovery +import pytz + +import database + + +def get_today_tasks_list(chat_id): + db = database.Database() + creds_dict = db.get_cred(chat_id) + del creds_dict["chat_id"] + logging.info("Get new task list request{}".format(creds_dict)) + credentials = google.oauth2.credentials.Credentials(**creds_dict) + calendar = googleapiclient.discovery.build("calendar", "v3", credentials=credentials) + # TODO: fix this for other UTC + date = datetime.datetime.now(pytz.timezone('Europe/Moscow')) + today = datetime.datetime.combine(datetime.date(date.year, date.month, date.day), datetime.datetime.min.time()) + start_of_day = today.isoformat() + '+03:00' + end = datetime.datetime.combine(datetime.date(date.year, date.month, date.day), datetime.datetime.max.time()) + end_of_day = end.isoformat() + '+03:00' + events_result = calendar.events().list(calendarId="primary", singleEvents=True, orderBy='startTime', + timeMin=start_of_day, timeMax=end_of_day).execute() + events = events_result.get('items', []) + if not events: + return False + else: + return events + + +def set_new_task(chat_id, time, date, summary): + db = database.Database() + creds_dict = db.get_cred(chat_id) + del creds_dict["chat_id"] + credentials = google.oauth2.credentials.Credentials(**creds_dict) + calendar = googleapiclient.discovery.build("calendar", "v3", credentials=credentials) + event = { + 'summary': summary, + 'start': { + 'dateTime': date + "T" + time + ":00+03:00", + 'timeZone': 'Europe/Moscow', + }, + 'end': { + 'dateTime': date + "T" + time + ":00+03:00", + 'timeZone': 'Europe/Moscow', + }, + } + calendar.events().insert(calendarId="primary", body=event).execute() diff --git a/requirements.txt b/requirements.txt index cd15d2f..d9fa4dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,13 @@ python-telegram-bot==12.0.0b1 +google-api-python-client +google +google-auth +google-auth-oauthlib +google-auth-httplib2 +psycopg2-binary +google +oauth2client +googleapis-common-protos +google-cloud +pytz +python-dateutil \ No newline at end of file diff --git a/setup.py b/setup.py index 1984203..20695fe 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,32 @@ -import telegram.ext -import os import logging -logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=logging.INFO) +import os +import telegram.ext -def echo(update, context): - context.bot.send_message(chat_id=update.message.chat_id, text=update.message.text) +from callbacks import text_callback, login_callback, start_callback, logout_callback, get_today_tasks_callback, \ + plan_callback, cancel_callback +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +TOKEN = os.environ["TOKEN"] +SECRET_CLIENT = os.environ["SECRET_TOKEN"] +with open("client_secret.json", "w") as f: + print(SECRET_CLIENT, file=f) -TOKEN = "820703303:AAHZXbuIVVA4oQm4s6SrxNtV7WX9m1xNGw8" PORT = int(os.environ.get('PORT', '8443')) -updater = telegram.ext.Updater(token=TOKEN) +updater = telegram.ext.Updater(token=TOKEN, use_context=True) dispatcher = updater.dispatcher -dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.text, echo)) + +dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.text, text_callback)) +dispatcher.add_handler(telegram.ext.CommandHandler('login', login_callback)) +dispatcher.add_handler(telegram.ext.CommandHandler("start", start_callback, pass_args=True)) +dispatcher.add_handler(telegram.ext.CommandHandler("logout", logout_callback)) +dispatcher.add_handler(telegram.ext.CommandHandler("get_today_tasks", get_today_tasks_callback)) +dispatcher.add_handler(telegram.ext.CommandHandler("plan", plan_callback)) +dispatcher.add_handler(telegram.ext.CommandHandler("cancel", cancel_callback)) updater.start_webhook(listen="0.0.0.0", port=PORT, url_path=TOKEN) updater.bot.set_webhook("https://yet-another-task-bot.herokuapp.com/" + TOKEN) - -updater.idle() \ No newline at end of file +updater.idle()