diff --git a/CHANGELOG.md b/CHANGELOG.md index 9507ebe..14db197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - bugfix: учитывание редактируемого сообщения обратной связи - удаление приветствия для покинувших канал без ответа - обработка ответов на сообщения в чате отзывов +- рефакторинг [0.0.2] diff --git a/api/index.py b/api/index.py deleted file mode 100644 index 34f264f..0000000 --- a/api/index.py +++ /dev/null @@ -1,178 +0,0 @@ -import os - -from tgbot.rest import delete_message, register_webhook, send_message, ban_member, forward_message -from sanic import Sanic -from sanic.response import json, text -import redis -import json as codec - -app = Sanic() - -REDIS_URL = os.environ.get('REDIS_URL') or 'redis://localhost:6379' -storage = redis.from_url(REDIS_URL) # сохраняет сессии и пересылаемые сообщения между перезагрузками - -WEBHOOK = os.environ.get('VERCEL_URL') -CHAT_ID = os.environ.get('CHAT_ID').replace("-", "-100") -WELCOME_MSG = os.environ.get('WELCOME_MSG') or 'Welcome! Press the button' - -BUTTON_OK = os.environ.get('BUTTON_OK') or 'Ok' -BUTTON_OK2 = os.environ.get('BUTTON_OK2') or 'I see' -BUTTON_NO = os.environ.get('BUTTON_NO') or 'No' - -FEEDBACK_CHAT_ID = os.environ.get('FEEDBACK_CHAT_ID').replace("-", "-100") - -app.config.REGISTERED = False - - -@app.route('/', methods=["GET"]) -async def register(req): - if not app.config.REGISTERED: - r = register_webhook(WEBHOOK) - print(f'\n\t\t\tWEBHOOK REGISTERED:\n{r.json()}') - app.config.REGISTERED = True - return json(r.json()) - return text('skipped') - - -@app.post('/') -async def handle(req): - print(req) - try: - update = req.json - if 'message' in update: - print(update) - msg = update.get('message', update.get('edited_message')) - if msg['chat']['type'] == 'private': - mid = msg['message_id'] - cid = msg['chat']['id'] - r = forward_message(cid, mid, FEEDBACK_CHAT_ID) - print(r.json()) - storage.set(f'fbk-{cid}-{mid}', r['id']) - elif str(msg['chat']['id']) == FEEDBACK_CHAT_ID: - print(f'handle answer from support') - private_chat_id = str(msg['reply_to_message']['from']['id']) - replied_msg_id = str(msg['reply_to_message']['message_id']) - r = send_message(private_chat_id, msg['body'], reply_to=replied_msg_id) - print(r.json()) - elif str(msg['chat']['id']) == CHAT_ID: - print(f'message in chat') - if 'new_chat_member' in msg: - chat_id = str(msg['chat']['id']) - from_id = str(msg['from']['id']) - member_id = str(msg['new_chat_member']['id']) - s = {} - if from_id == member_id: - print(f'new self-joined member {member_id}') - reply_markup = { - "inline_keyboard": [ - [ - {"text": BUTTON_NO, "callback_data": BUTTON_NO}, - {"text": BUTTON_OK, "callback_data": BUTTON_OK} - ] - ] - } - r = send_message( - chat_id, - WELCOME_MSG, - reply_to=msg['message_id'], - reply_markup=reply_markup - ) - welcome_msg_id = r.json()['result']['message_id'] - print(f'welcome message id: {welcome_msg_id}') - s["newcomer"] = True - s["welcome_id"] = welcome_msg_id - else: - s['newcomer'] = False - - # create new member session - storage.set(f'usr-{member_id}', codec.dumps(s)) - - elif 'left_chat_member' in msg: - member_id = msg["left_chat_member"]["id"] - - # read member session - s = storage.get(f'usr-{member_id}') - if s: - s = codec.parse(s) - r = delete_message(CHAT_ID, s['welcome_id']) - print(r.json()) - - # remove left member session - storage.delete(f'usr-{member_id}') - - elif 'text' in msg: - chat_id = str(msg['chat']['id']) - member_id = str(msg['from']['id']) - - # check if author is self-joined newcomer - author = storage.get(f'usr-{member_id}') - - if author: - author = codec.parse(author) - if author.get("newcomer"): - print(f'new member speaks {msg["text"]}') - answer = msg['text'] - if BUTTON_OK.lower() in answer.lower() or \ - BUTTON_OK2.lower() in answer.lower(): - print('found answer, cleanup') - r = delete_message(CHAT_ID, author["welcome_id"]) - print(r.json()) - author["newcomer"] = False - - # set author as not a newcomer - storage.set(f'usr-{member_id}', codec.dumps(author)) - - else: - print('remove some message') - r = delete_message(CHAT_ID, msg['message_id']) - print(r.json()) - else: - print(f'old member speaks {msg["text"]}') - if 'callback_query' in update: - callback_query = update['callback_query'] - chat_id = str(callback_query['message']['chat']['id']) - if chat_id == CHAT_ID: - member_id = str(callback_query['from']['id']) - callback_data = callback_query['data'] - reply_owner = str(callback_query['message']['reply_to_message']['from']['id']) - welcome_msg_id = str(callback_data['message']['message_id']) - enter_msg_id = str(callback_data['message']['reply_to_message']['message_id']) - if reply_owner == member_id: - print(update) - print(f'callback_query in {CHAT_ID}') - - # read session - s = storage.get(f'usr-{member_id}') - if s: - s = codec.parse(s) - else: - print('no user session found, create') - storage.set(f'usr-{member_id}', codec.dumps({ - 'newcomer': True, - 'welcome_id': welcome_msg_id - })) - - if callback_data == BUTTON_NO: - print('wrong answer, cleanup') - r = delete_message(CHAT_ID, enter_msg_id) - print(r.json()) - r = delete_message(CHAT_ID, welcome_msg_id) - print(r.json()) - - # remove banned member session - storage.delete(f'usr-{member_id}') - - print('ban member') - r = ban_member(CHAT_ID, member_id) - print(r.json()) - elif callback_data == BUTTON_OK: - print('proper answer, cleanup') - r = delete_message(CHAT_ID, welcome_msg_id) - print(r.json()) - s['newcomer'] = False - - # store new member session - storage.set(f'usr-{member_id}', codec.dumps(s)) - except Exception: - pass - return text('ok') diff --git a/api/webhook.py b/api/webhook.py new file mode 100644 index 0000000..bead17d --- /dev/null +++ b/api/webhook.py @@ -0,0 +1,50 @@ +from tgbot.config import WEBHOOK, FEEDBACK_CHAT_ID, CHAT_ID # init storage there +from tgbot.handlers import handle_feedback, handle_answer, handle_welcome, \ + handle_left, handle_text, handle_button +from tgbot.api import register_webhook +from sanic import Sanic +from sanic.response import text + + +app = Sanic() +app.config.REGISTERED = False + + +@app.route('/', methods=["GET"]) +async def register(req): + if not app.config.REGISTERED: + r = register_webhook(WEBHOOK) + print(f'\n\t\t\tWEBHOOK REGISTERED:\n{r.json()}') + app.config.REGISTERED = True + print(r.json()) + return text('skipped') + + +@app.post('/') +async def handle(req): + print(req) + try: + update = req.json + print(update) + if 'message' in update: + msg = update.get('message', update.get('edited_message')) + if msg['chat']['type'] == 'private': + handle_feedback(msg) + elif str(msg['chat']['id']) == FEEDBACK_CHAT_ID: + handle_answer(msg) + elif str(msg['chat']['id']) == CHAT_ID: + if 'new_chat_member' in msg: + handle_welcome(msg) + elif 'left_chat_member' in msg: + handle_left(msg) + elif 'text' in msg: + handle_text(msg) + if 'callback_query' in update: + callback_query = update['callback_query'] + chat_id = str(callback_query['message']['chat']['id']) + if chat_id == CHAT_ID: + handle_button(callback_query) + except Exception: + import traceback + traceback.print_exc() + return text('ok') diff --git a/tgbot/rest.py b/tgbot/api.py similarity index 94% rename from tgbot/rest.py rename to tgbot/api.py index 93fecf8..4324996 100644 --- a/tgbot/rest.py +++ b/tgbot/api.py @@ -19,7 +19,7 @@ def delete_message(cid: str, mid: str): return r -def send_message(cid, body, reply_to=None, reply_markup=None): +def send_message(cid: str, body, reply_to=None, reply_markup=None): url = apiBase + f"sendMessage?chat_id={cid}&text={body}" if reply_to: url += f'&reply_to_message_id={reply_to}' @@ -28,6 +28,7 @@ def send_message(cid, body, reply_to=None, reply_markup=None): reply_markup = requests.utils.quote(reply_markup) url += f'&reply_markup={reply_markup}' r = requests.post(url) + print(f'{url}') return r diff --git a/tgbot/config.py b/tgbot/config.py new file mode 100644 index 0000000..2f7266d --- /dev/null +++ b/tgbot/config.py @@ -0,0 +1,12 @@ +import os + + +REDIS_URL = os.environ.get('REDIS_URL') or 'redis://localhost:6379' +WEBHOOK = os.environ.get('VERCEL_URL') or 'http://localhost:8000' +WELCOME_MSG = os.environ.get('WELCOME_MSG') or 'Welcome! Press the button' +BUTTON_OK = os.environ.get('BUTTON_OK') or 'Ok' +BUTTON_OK2 = os.environ.get('BUTTON_OK2') or 'I see' +BUTTON_NO = os.environ.get('BUTTON_NO') or 'No' + +CHAT_ID = os.environ.get('CHAT_ID').replace("-", "-100") +FEEDBACK_CHAT_ID = os.environ.get('FEEDBACK_CHAT_ID').replace("-", "-100") \ No newline at end of file diff --git a/tgbot/handlers.py b/tgbot/handlers.py new file mode 100644 index 0000000..64d5456 --- /dev/null +++ b/tgbot/handlers.py @@ -0,0 +1,150 @@ +from tgbot.api import send_message, forward_message, delete_message, ban_member +from tgbot.config import FEEDBACK_CHAT_ID, WELCOME_MSG, BUTTON_NO, \ + BUTTON_OK, CHAT_ID, BUTTON_OK2, REDIS_URL +import json +import redis + +# сохраняет сессии и пересылаемые сообщения между перезагрузками +storage = redis.from_url(REDIS_URL) + + +def handle_feedback(msg): + mid = msg['message_id'] + cid = msg['chat']['id'] + r = forward_message(cid, mid, FEEDBACK_CHAT_ID).json() + support_msg_id = r['result']['message_id'] + # store private chat message id + # fbk- -> : + storage.set(f'fbk-{support_msg_id}', json.dumps({ + "message_id": mid, + "chat_id": cid + })) + + +def handle_answer(msg): + print(f'handle answer from support') + support_msg_id = str(msg['reply_to_message']['message_id']) + # get stored private chat id + stored_feedback = storage.get(f'fbk-{support_msg_id}') + stored_feedback = json.loads(stored_feedback) + r = send_message(f'{stored_feedback["chat_id"]}', msg['text'], reply_to=stored_feedback["message_id"]) # notice 'u' before private chat ID + print(r.json()) + + +def handle_welcome(msg): + chat_id = str(msg['chat']['id']) + from_id = str(msg['from']['id']) + member_id = str(msg['new_chat_member']['id']) + s = {} + if from_id == member_id: + print(f'new self-joined member {member_id}') + reply_markup = { + "inline_keyboard": [ + [ + {"text": BUTTON_NO, "callback_data": BUTTON_NO}, + {"text": BUTTON_OK, "callback_data": BUTTON_OK} + ] + ] + } + r = send_message( + chat_id, + WELCOME_MSG, + reply_to=msg['message_id'], + reply_markup=reply_markup + ) + welcome_msg_id = r.json()['result']['message_id'] + print(f'welcome message id: {welcome_msg_id}') + s["newcomer"] = True + s["welcome_id"] = welcome_msg_id + else: + s['newcomer'] = False + + # create new member session + storage.set(f'usr-{member_id}', json.dumps(s)) + + +def handle_left(msg): + member_id = msg["left_chat_member"]["id"] + + # read member session + s = storage.get(f'usr-{member_id}') + if s: + s = json.loads(s) + r = delete_message(CHAT_ID, s['welcome_id']) + print(r.json()) + + # remove left member session + storage.delete(f'usr-{member_id}') + + +def handle_text(msg): + member_id = str(msg['from']['id']) + + # check if author is self-joined newcomer + author = storage.get(f'usr-{member_id}') + + if author: + author = json.loads(author) + if author.get("newcomer"): + print(f'new member speaks {msg["text"]}') + answer = msg['text'] + if BUTTON_OK.lower() in answer.lower() or \ + BUTTON_OK2.lower() in answer.lower(): + print('found answer, cleanup') + r = delete_message(CHAT_ID, author["welcome_id"]) + print(r.json()) + author["newcomer"] = False + + # set author as not a newcomer + storage.set(f'usr-{member_id}', json.dumps(author)) + + else: + print('remove some message') + r = delete_message(CHAT_ID, msg['message_id']) + print(r.json()) + else: + print(f'old member speaks {msg["text"]}') + + +def handle_button(callback_query): + member_id = str(callback_query['from']['id']) + callback_data = callback_query['data'] + reply_owner = str(callback_query['message']['reply_to_message']['from']['id']) + welcome_msg_id = str(callback_query['message']['message_id']) + enter_msg_id = str(callback_query['message']['reply_to_message']['message_id']) + if reply_owner == member_id: + print(f'callback_query in {CHAT_ID}') + + # read session + s = storage.get(f'usr-{member_id}') + if s: + s = json.loads(s) + else: + print('no user session found, create') + s = { + 'newcomer': True, + 'welcome_id': welcome_msg_id + } + storage.set(f'usr-{member_id}', json.dumps(s)) + + if callback_data == BUTTON_NO: + print('wrong answer, cleanup') + r = delete_message(CHAT_ID, enter_msg_id) + print(r.json()) + r = delete_message(CHAT_ID, welcome_msg_id) + print(r.json()) + + # remove banned member session + storage.delete(f'usr-{member_id}') + + print('ban member') + r = ban_member(CHAT_ID, member_id) + print(r.json()) + elif callback_data == BUTTON_OK: + print('proper answer, cleanup') + r = delete_message(CHAT_ID, welcome_msg_id) + print(r.json()) + s['newcomer'] = False + + # store new member session + storage.set(f'usr-{member_id}', json.dumps(s)) \ No newline at end of file diff --git a/vercel.json b/vercel.json index 53188ca..e2e757a 100755 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,7 @@ { "version": 2, "functions": { - "api/index.py": { + "api/webhook.py": { "memory": 1024, "maxDuration": 10 } @@ -9,7 +9,7 @@ "routes": [ { "src": "/", - "dest": "/api/index.py" + "dest": "/api/webhook.py" } ] }