From 297445fd501d242992915093f0d7500bdb0cb470 Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Sat, 22 Apr 2023 04:21:24 +0300 Subject: [PATCH] 0.0.7 fixes and graph --- CHANGELOG.md | 6 +++++ api/webhook.py | 9 ++++--- requirements.txt | 3 ++- tgbot/api.py | 11 +++++++- tgbot/config.py | 2 +- tgbot/graph.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++ tgbot/handlers.py | 58 ++++++++++++++++++++++++++++++++---------- tgbot/profile.py | 8 ++++-- 8 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 tgbot/graph.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb829a..a61cd88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.0.7] + +- исправления +- команда, генерирующая граф связей + + ## [0.0.6] - совместимость с механизмом заявок для публичных групп diff --git a/api/webhook.py b/api/webhook.py index 485fba3..15f2b84 100644 --- a/api/webhook.py +++ b/api/webhook.py @@ -1,6 +1,7 @@ from tgbot.config import WEBHOOK, FEEDBACK_CHAT_ID # init storage there from tgbot.handlers import handle_feedback, handle_answer, \ - handle_join, handle_left, handle_button, handle_join_request + handle_join, handle_left, handle_button, handle_join_request, \ + handle_graph from tgbot.api import register_webhook from sanic import Sanic from sanic.response import text @@ -31,9 +32,11 @@ async def handle(req): msg = update.get('message', update.get('edited_message')) if msg['chat']['type'] == 'private': handle_feedback(msg) - elif str(msg['chat']['id']) == FEEDBACK_CHAT_ID \ - and 'reply_to_message' in msg: + elif str(msg['chat']['id']) == FEEDBACK_CHAT_ID: + if 'reply_to_message' in msg: handle_answer(msg) + elif 'text' in msg and msg['text'] == '/graph': + handle_graph(msg) else: if 'new_chat_member' in msg: handle_join(msg) diff --git a/requirements.txt b/requirements.txt index 794dc30..db1d882 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ sanic==19.6.0 requests -redis \ No newline at end of file +redis +cairosvg \ No newline at end of file diff --git a/tgbot/api.py b/tgbot/api.py index 2023f38..58aeb49 100644 --- a/tgbot/api.py +++ b/tgbot/api.py @@ -85,4 +85,13 @@ def approve_chat_join_request(chat_id, user_id): url = apiBase + f"approveChatJoinRequest?chat_id={chat_id}" + \ f'&user_id={user_id}' r = requests.post(url) - return r \ No newline at end of file + return r + + +def send_graph(png_data, chat_id): + url = apiBase + f"sendPhoto" + headers = {"Content-Type": "multipart/form-data"} + files = {"photo": ("chart.png", png_data)} + params = {"chat_id": chat_id} + response = requests.post(url, headers=headers, files=files, params=params) + return response.json() \ No newline at end of file diff --git a/tgbot/config.py b/tgbot/config.py index b942a8f..fd6d6da 100644 --- a/tgbot/config.py +++ b/tgbot/config.py @@ -3,6 +3,6 @@ import os WEBHOOK = os.environ.get('VERCEL_URL') or 'http://localhost:8000' REDIS_URL = os.environ.get('REDIS_URL') or 'redis://localhost:6379' -NEWCOMER_MSG = os.environ.get('WELCOME_MSG') or "There is a newcomer, press the button if you are connected" +NEWCOMER_MSG = os.environ.get('NEWCOMER_MSG') or "There is a newcomer, press the button if you are connected" BUTTON_VOUCH = os.environ.get('BUTTON_VOUCH') or 'My connection!' FEEDBACK_CHAT_ID = os.environ.get('FEEDBACK_CHAT_ID').replace("-", "-100") \ No newline at end of file diff --git a/tgbot/graph.py b/tgbot/graph.py new file mode 100644 index 0000000..bb165b4 --- /dev/null +++ b/tgbot/graph.py @@ -0,0 +1,64 @@ +import cairosvg + +def generate_chart(members): + # Размеры прямоугольника узла + node_width = 150 + node_height = 50 + + # Размеры холста + canvas_width = 800 + canvas_height = 600 + + # Радиус узла (для закругленных прямоугольников) + node_radius = 10 + + # Цвета + background_color = "#F2F2F2" + node_color = "#EFEFEF" + node_stroke_color = "#999" + node_text_color = "#333" + line_color = "#CCC" + + # Список строк SVG-кода + svg_lines = [] + + # Рассчитываем координаты для каждого узла + coordinates = {} + for member in members: + x = member['x'] + y = member['y'] + coordinates[member['id']] = {'x': x, 'y': y} + + # Рисуем линии-связи между узлами + for member in members: + member_id = member['id'] + x1 = coordinates[member_id]['x'] * node_width + node_width / 2 + y1 = coordinates[member_id]['y'] * node_height + node_height / 2 + for parent_id in member['parents']: + x2 = coordinates[parent_id]['x'] * node_width + node_width / 2 + y2 = coordinates[parent_id]['y'] * node_height + node_height / 2 + svg_lines.append(f'') + + # Рисуем узлы + for member in members: + member_id = member['id'] + x = coordinates[member_id]['x'] * node_width + y = coordinates[member_id]['y'] * node_height + + # Рисуем фоновый прямоугольник + svg_lines.append(f'') + + # Добавляем текст в центр узла + member_name = member['name'][:16] + text_x = x + node_width / 2 + text_y = y + node_height / 2 + svg_lines.append(f'{member_name}') + + # Создаем SVG-код + svg = f'' + for line in svg_lines: + svg += line + svg += '' + # конвертировать SVG в PNG + png_data = cairosvg.svg2png(bytestring=svg_data) + return png_data diff --git a/tgbot/handlers.py b/tgbot/handlers.py index 86eab72..a39f518 100644 --- a/tgbot/handlers.py +++ b/tgbot/handlers.py @@ -1,6 +1,7 @@ from tgbot.api import send_message, forward_message, delete_message, \ ban_member, unban_member, mute_member, unmute_member, \ - approve_chat_join_request + approve_chat_join_request, send_graph +from tgbot.graph import generate_chart from tgbot.config import REDIS_URL, FEEDBACK_CHAT_ID, BUTTON_VOUCH, NEWCOMER_MSG import json import redis @@ -13,7 +14,7 @@ storage = redis.from_url(REDIS_URL) # хранение необходимой информации о пользователях Profile = ProfileObj(storage) -def show_request_msg(msg): +def newcomer_show(msg): reply_markup = { "inline_keyboard": [ [ @@ -24,10 +25,12 @@ def show_request_msg(msg): ] ] } - + identity = f"{msg['from']['first_name']} {msg['from'].get('last_name', '')}" + if 'username' in msg['from']: + identity += f" @{msg['from']['username']}" r = send_message( msg['chat']['id'], - NEWCOMER_MSG + f"{msg['from']['first_name']} {msg['from']['last_name']}({msg['from']['username']})", + NEWCOMER_MSG + identity, reply_to=msg['message_id'], reply_markup=reply_markup ) @@ -67,11 +70,15 @@ def handle_join(msg): actor["name"] = msg['from']['first_name'] + msg['from'].get('last_name', '') actor["mention"] = msg['from'].get('username', '') - - if from_id == str(msg['new_chat_member']['id']): + newcomer_id = str(msg['new_chat_member']['id']) + if from_id == newcomer_id: if len(actor['parents']) == 0: # показываем сообщение с кнопкой "поручиться" - welcome_msg_id = show_request_msg(msg) + welcome_msg_id = newcomer_show(msg) + + # до одобрения - мьют + r = mute_member(chat_id, newcomer_id) + print(r.json()) # обновляем профиль присоединившегося actor['welcome_id'] = welcome_msg_id @@ -88,6 +95,8 @@ def handle_join(msg): newcomer['parents'].append(from_id) Profile.save(newcomer) actor['children'].append(m['id']) + r = unmute_member(chat_id, newcomer['id']) + print(r.json()) # обновляем профиль пригласившего Profile.save(actor) @@ -118,7 +127,7 @@ def handle_button(callback_query): newcomer_id = callback_data[len(BUTTON_VOUCH):] newcomer = Profile.get(newcomer_id) - if newcomer_id not in inviter['children'] and \ + if newcomer_id not in actor['children'] and \ actor_id not in newcomer['parents']: newcomer['parents'].append(newcomer_id) actor['children'].append(actor_id) @@ -127,13 +136,14 @@ def handle_button(callback_query): try: chat_id = str(callback_query['message']['chat']['id']) - print('accept join request for public chat') - r = approve_chat_join_request(chat_id, newcomer_id) - print(r.json()) - print('unmute newcomer') r = unmute_member(chat_id, newcomer_id) print(r.json()) + + print('accept join request') + r = approve_chat_join_request(chat_id, newcomer_id) + print(r.json()) + except: pass @@ -150,4 +160,26 @@ def handle_join_request(update): if from_id == str(update['message']['new_chat_member']['id']): if len(actor['parents']) == 0: # показываем сообщение с кнопкой "поручиться" - welcome_msg_id = show_request_msg(update) \ No newline at end of file + welcome_msg_id = show_request_msg(update) + + +def handle_graph(_msg): + cursor = 0 + keys = [] + while True: + # Scan for keys starting with 'urs-*' in batches of 100 + cursor, batch_keys = r.scan(cursor=cursor, match='urs-*', count=100) + keys += batch_keys + # If the cursor is 0, then we've reached the end of the keys + if cursor == 0: + break + # Get the values of all the keys + values = r.mget(keys) + # Parse the JSON data from each value + members = [] + for value in values: + member = json.loads(value) + members.append(member) + png_data = generate_chart(values) + r = send_graph(png_data, chat_id) + print(r.json()) diff --git a/tgbot/profile.py b/tgbot/profile.py index a64a675..0339bf8 100644 --- a/tgbot/profile.py +++ b/tgbot/profile.py @@ -19,10 +19,14 @@ class Profile: return s def save(self, s): - self.storage.set(f'usr-{member_id}', json.dumps(s)) + self.storage.set(f'usr-{s["id"]}', json.dumps(s)) def get(self, member_id): - return json.loads(self.storage.get(f'usr-{member_id}')) or self.create_session(member_id) + data = self.storage.get(f'usr-{member_id}') + if data is None: + return self.create(member_id) + else: + return json.loads(data) def leaving(self, s): if len(s['parents']) == 0: