diff --git a/readme.md b/readme.md index 309db74..c5c7a5b 100644 --- a/readme.md +++ b/readme.md @@ -20,13 +20,13 @@ Telegram terminal client. - [X] message history - [X] list contacts - [X] show user status +- [X] secret chats +- [X] create new chat TODO: -- [ ] secret chats - [ ] search - [ ] show members in chat -- [ ] create new chat - [ ] bots (bot keyboard) @@ -245,6 +245,8 @@ For navigation arrow keys also can be used. - `r`: read current chat - `c`: show list of contacts - `dd`: delete chat or remove history +- `ng`: create new group chat +- `ns`: create new secret chat - `/`: search in chats - `?`: show help @@ -277,5 +279,7 @@ For navigation arrow keys also can be used. - `o`: open url present in message (if multiple urls, `urlview` will be opened) - `]`: next chat - `[`: prev chat +- `u`: show user info (username, bio, phone, etc.) +- `c`: show chat info (e.g. secret chat encryption key, chat id, state, etc.) - `?`: show help - `!`: open msg with custom cmd diff --git a/tg/controllers.py b/tg/controllers.py index 4f7e709..8973041 100644 --- a/tg/controllers.py +++ b/tg/controllers.py @@ -13,7 +13,7 @@ from telegram.utils import AsyncResult from tg import config from tg.models import Model from tg.msg import MsgProxy -from tg.tdlib import ChatAction, ChatType, Tdlib, UserType, get_chat_type +from tg.tdlib import ChatAction, ChatType, Tdlib, get_chat_type from tg.utils import ( get_duration, get_mime, @@ -76,29 +76,25 @@ class Controller: self.tg = tg self.chat_size = 0.5 + @bind(msg_handler, ["c"]) + def show_chat_info(self) -> None: + """Show chat information""" + chat = self.model.chats.chats[self.model.current_chat] + info = self.model.get_chat_info(chat) + + with suspend(self.view) as s: + s.run_with_input( + config.VIEW_TEXT_CMD, + "\n".join(f"{k}: {v}" for k, v in info.items() if v), + ) + @bind(msg_handler, ["u"]) - def user_info(self) -> None: + def show_user_info(self) -> None: """Show user profile""" msg = MsgProxy(self.model.current_msg) user_id = msg.sender_id - users = self.model.users - name = users.get_user_label(user_id) - status = users.get_status(user_id) - user = users.get_user(user_id) - user_info = users.get_user_full_info(user_id) - user_type = None - try: - user_type = UserType[user["type"]["@type"]].value - except KeyError: - pass - info = { - name: status, - "Username": user.get("username", ""), - "UserId": user_id, - "Bio": user_info.get("bio", ""), - "Phone": user.get("phone_number", ""), - "Type": user_type, - } + info = self.model.get_user_info(user_id) + with suspend(self.view) as s: s.run_with_input( config.VIEW_TEXT_CMD, diff --git a/tg/models.py b/tg/models.py index 07dd268..575ee25 100644 --- a/tg/models.py +++ b/tg/models.py @@ -1,3 +1,4 @@ +import base64 import logging import sys import time @@ -5,7 +6,15 @@ from collections import defaultdict, namedtuple from typing import Any, Dict, List, Optional, Set, Tuple from tg.msg import MsgProxy -from tg.tdlib import ChatAction, Tdlib, UserStatus +from tg.tdlib import ( + ChatAction, + ChatType, + SecretChatState, + Tdlib, + UserStatus, + UserType, + get_chat_type, +) from tg.utils import copy_to_clipboard, pretty_ts log = logging.getLogger(__name__) @@ -217,6 +226,114 @@ class Model: copy_to_clipboard("\n".join(buffer)) return True + def get_chat_info(self, chat: Dict[str, Any]) -> Dict[str, Any]: + chat_type = get_chat_type(chat) + if chat_type is None: + return {} + + info = {} + + if chat_type == ChatType.chatTypePrivate: + user_id = chat["id"] + user = self.users.get_user(user_id) + user_info = self.users.get_user_full_info(user_id) + status = self.users.get_status(user_id) + info.update( + { + chat["title"]: status, + "Username": user.get("username", ""), + "Phone": user.get("phone_number", ""), + "Bio": user_info.get("bio", ""), + } + ) + + if chat_type == ChatType.chatTypeBasicGroup: + group_id = chat["type"]["basic_group_id"] + result = self.tg.get_basic_group_full_info(group_id) + result.wait() + chat_info = result.update + basic_info = self.tg.get_basic_group(group_id) + basic_info.wait() + basic_info = basic_info.update + info.update( + { + chat["title"]: f"{basic_info['member_count']} members", + "Info": chat_info["description"], + "Share link": chat_info["invite_link"], + } + ) + + if chat_type == ChatType.chatTypeSupergroup: + result = self.tg.get_supergroup_full_info( + chat["type"]["supergroup_id"] + ) + result.wait() + chat_info = result.update + info.update( + { + chat["title"]: f"{chat_info['member_count']} members", + "Info": chat_info["description"], + "Share link": chat_info["invite_link"], + } + ) + + if chat_type == ChatType.channel: + chat_info = self.tg.get_supergroup_full_info( + chat["type"]["supergroup_id"] + ) + info.update( + { + chat["title"]: "subscribers", + "Info": chat_info["description"], + "Share link": chat_info["invite_link"], + } + ) + + if chat_type == ChatType.chatTypeSecret: + result = self.tg.get_secret_chat(chat["type"]["secret_chat_id"]) + result.wait() + chat_info = result.update + enc_key = base64.b64decode(chat_info["key_hash"])[:32].hex() + hex_key = " ".join( + [enc_key[i : i + 2] for i in range(0, len(enc_key), 2)] + ) + + state = "Unknown" + try: + state = SecretChatState[chat_info["state"]["@type"]].value + except KeyError: + pass + + user_id = chat_info["user_id"] + user_info = self.get_user_info(user_id) + + info.update( + {**user_info, "State": state, "Encryption Key": hex_key} + ) + + info.update({"Type": chat_type.value, "Chat Id": chat["id"]}) + return info + + def get_user_info(self, user_id: int) -> Dict[str, Any]: + name = self.users.get_user_label(user_id) + status = self.users.get_status(user_id) + user = self.users.get_user(user_id) + user_info = self.users.get_user_full_info(user_id) + user_type = "Unknown" + try: + user_type = UserType[user["type"]["@type"]].value + except KeyError: + pass + info = { + name: status, + "Username": user.get("username", ""), + "Bio": user_info.get("bio", ""), + "Phone": user.get("phone_number", ""), + "User Id": user_id, + "Type": user_type, + } + return info + class ChatModel: def __init__(self, tg: Tdlib) -> None: diff --git a/tg/tdlib.py b/tg/tdlib.py index 9f07094..a77223c 100644 --- a/tg/tdlib.py +++ b/tg/tdlib.py @@ -49,6 +49,12 @@ class TextParseModeInput(Enum): textParseModeHTML = "html" +class SecretChatState(Enum): + secretChatStatePending = "pending" + secretChatStateReady = "ready" + secretChatStateClosed = "closed" + + class Tdlib(Telegram): def parse_text_entities( self, @@ -295,6 +301,13 @@ class Tdlib(Telegram): } return self._send_data(data) + def get_basic_group_full_info(self, basic_group_id: int,) -> AsyncResult: + data = { + "@type": "getBasicGroupFullInfo", + "basic_group_id": basic_group_id, + } + return self._send_data(data) + def get_supergroup(self, supergroup_id: int,) -> AsyncResult: data = { "@type": "getSupergroup", @@ -302,6 +315,20 @@ class Tdlib(Telegram): } return self._send_data(data) + def get_supergroup_full_info(self, supergroup_id: int,) -> AsyncResult: + data = { + "@type": "getSupergroupFullInfo", + "supergroup_id": supergroup_id, + } + return self._send_data(data) + + def get_secret_chat(self, secret_chat_id: int,) -> AsyncResult: + data = { + "@type": "getSecretChat", + "secret_chat_id": secret_chat_id, + } + return self._send_data(data) + def send_chat_action( self, chat_id: int, action: ChatAction, progress: int = None ) -> AsyncResult: