From 4641931ed95a1b290692733fd879e90815e66cc6 Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 21 May 2020 13:27:50 +0800 Subject: [PATCH] Add support for openMessageContent and updateMessageContentOpened (#43) * Add support for openMessageContent and updateMessageContentOpened Co-authored-by: Alexander Zveruk --- tg/controllers/__init__.py | 12 ++++++++++-- tg/main.py | 39 +++++++++++++++++++++++++++++--------- tg/models/__init__.py | 19 ++++++++++++++++++- tg/msg.py | 36 +++++++++++++++++++++++++++++++---- tg/views/__init__.py | 15 +++++++++++---- 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/tg/controllers/__init__.py b/tg/controllers/__init__.py index 868e8e1..b347025 100644 --- a/tg/controllers/__init__.py +++ b/tg/controllers/__init__.py @@ -61,6 +61,7 @@ class Controller: "updateMessageContent": self.update_msg_content, "updateMessageSendSucceeded": self.update_msg_send_succeeded, "updateNewMessage": self.update_new_msg, + "updateMessageContentOpened": self.update_message_content_opened, } self.chat_size = 0.5 signal(SIGWINCH, self.resize_handler) @@ -106,7 +107,6 @@ class Controller: def open_current_msg(self): msg = MsgProxy(self.model.current_msg()) - log.info("Open msg: %s", msg.msg) if msg.is_text: text = msg["content"]["text"]["text"] with NamedTemporaryFile("w", suffix=".txt") as f: @@ -118,8 +118,9 @@ class Controller: path = msg.local_path if path: + chat_id = self.model.chats.id_by_index(self.model.current_chat) + self.tg.open_message_content(chat_id, msg.msg_id) with suspend(self.view) as s: - log.info("Opening file: %s", path) s.open_file(path) def write_long_msg(self): @@ -523,3 +524,10 @@ class Controller: if proxy.is_downloaded: self.model.downloads.pop(file_id) break + + @handle_exception + def update_message_content_opened(self, update: Dict[str, Any]): + chat_id = update["chat_id"] + message_id = update["message_id"] + self.model.msgs.update_msg_content_opened(chat_id, message_id) + self.refresh_msgs() diff --git a/tg/main.py b/tg/main.py index f2e4c64..6d681ad 100644 --- a/tg/main.py +++ b/tg/main.py @@ -5,7 +5,7 @@ import threading from curses import window, wrapper # type: ignore from functools import partial -from telegram.client import Telegram +from telegram.client import AsyncResult, Telegram from tg import config, utils from tg.controllers import Controller @@ -48,7 +48,7 @@ class TelegramApi(Telegram): ) result.wait() - def send_doc(self, file_path, chat_id): + def send_doc(self, file_path: str, chat_id: int) -> AsyncResult: data = { "@type": "sendMessage", "chat_id": chat_id, @@ -59,7 +59,7 @@ class TelegramApi(Telegram): } return self._send_data(data) - def send_audio(self, file_path, chat_id): + def send_audio(self, file_path: str, chat_id: int) -> AsyncResult: data = { "@type": "sendMessage", "chat_id": chat_id, @@ -70,7 +70,7 @@ class TelegramApi(Telegram): } return self._send_data(data) - def send_photo(self, file_path, chat_id): + def send_photo(self, file_path: str, chat_id: int) -> AsyncResult: data = { "@type": "sendMessage", "chat_id": chat_id, @@ -81,7 +81,14 @@ class TelegramApi(Telegram): } return self._send_data(data) - def send_video(self, file_path, chat_id, width, height, duration): + def send_video( + self, + file_path: str, + chat_id: int, + width: int, + height: int, + duration: int, + ) -> AsyncResult: data = { "@type": "sendMessage", "chat_id": chat_id, @@ -95,7 +102,9 @@ class TelegramApi(Telegram): } return self._send_data(data) - def send_voice(self, file_path, chat_id, duration, waveform): + def send_voice( + self, file_path: str, chat_id: int, duration: int, waveform: int + ): data = { "@type": "sendMessage", "chat_id": chat_id, @@ -110,7 +119,7 @@ class TelegramApi(Telegram): def toggle_chat_is_marked_as_unread( self, chat_id: int, is_marked_as_unread: bool - ): + ) -> AsyncResult: data = { "@type": "toggleChatIsMarkedAsUnread", "chat_id": chat_id, @@ -118,7 +127,9 @@ class TelegramApi(Telegram): } return self._send_data(data) - def toggle_chat_is_pinned(self, chat_id: int, is_pinned: bool): + def toggle_chat_is_pinned( + self, chat_id: int, is_pinned: bool + ) -> AsyncResult: data = { "@type": "toggleChatIsPinned", "chat_id": chat_id, @@ -138,7 +149,7 @@ class TelegramApi(Telegram): def view_messages( self, chat_id: int, message_ids: list, force_read: bool = True - ): + ) -> AsyncResult: data = { "@type": "viewMessages", "chat_id": chat_id, @@ -147,6 +158,16 @@ class TelegramApi(Telegram): } return self._send_data(data) + def open_message_content( + self, chat_id: int, message_id: int + ) -> AsyncResult: + data = { + "@type": "openMessageContent", + "chat_id": chat_id, + "message_id": message_id, + } + return self._send_data(data) + def main(): def signal_handler(sig, frame): diff --git a/tg/models/__init__.py b/tg/models/__init__.py index 568d0ca..b9e4f25 100644 --- a/tg/models/__init__.py +++ b/tg/models/__init__.py @@ -4,6 +4,8 @@ from typing import Any, Dict, List, Optional, Set, Tuple from telegram.client import Telegram +from tg.msg import MsgProxy + log = logging.getLogger(__name__) @@ -249,7 +251,22 @@ class MsgModel: if msg["id"] != msg_id: continue msg["content"] = message_content - return True + return True + return False + + def update_msg_content_opened(self, chat_id: int, msg_id: int): + for message in self.msgs[chat_id]: + if message["id"] != msg_id: + continue + msg = MsgProxy(message) + if msg.type == "voice": + msg.is_listened = True + elif msg.type == "recording": + msg.is_viewed = True + # TODO: start the TTL timer for self-destructing messages + # that is the last case to implement + # https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1update_message_content_opened.html + return def add_message(self, chat_id: int, message: Dict[str, Any]) -> bool: msg_id = message["id"] diff --git a/tg/msg.py b/tg/msg.py index 363909a..50afa05 100644 --- a/tg/msg.py +++ b/tg/msg.py @@ -1,6 +1,6 @@ import logging from datetime import datetime -from typing import Any, Dict +from typing import Any, Dict, Optional from tg import utils @@ -58,8 +58,8 @@ class MsgProxy: self.msg[key] = value @property - def type(self): - return self.msg["@type"] + def type(self) -> Optional[str]: + return self.msg.get("@type") @property def date(self) -> datetime: @@ -145,7 +145,35 @@ class MsgProxy: return doc["local"]["is_downloading_completed"] @property - def reply_msg_id(self): + def is_listened(self) -> Optional[bool]: + if self.type != "voice": + return None + return self.msg["content"]["is_listened"] + + @is_listened.setter + def is_listened(self, value: bool): + if self.type != "voice": + return None + self.msg["content"]["is_listened"] = value + + @property + def is_viewed(self) -> Optional[bool]: + if self.type != "recording": + return None + return self.msg["content"]["is_viewed"] + + @is_viewed.setter + def is_viewed(self, value: bool): + if self.type != "recording": + return None + self.msg["content"]["is_viewed"] = value + + @property + def msg_id(self) -> int: + return self.msg["id"] + + @property + def reply_msg_id(self) -> Optional[int]: return self.msg.get("reply_to_message_id") @property diff --git a/tg/views/__init__.py b/tg/views/__init__.py index 71493a1..0d228e1 100644 --- a/tg/views/__init__.py +++ b/tg/views/__init__.py @@ -5,8 +5,7 @@ from datetime import datetime from typing import Any, Dict, List, Optional, Tuple, cast from tg.colors import blue, cyan, get_color, magenta, reverse, white -from tg.models import MsgModel -from tg.models import UserModel +from tg.models import MsgModel, UserModel from tg.msg import MsgProxy from tg.utils import emoji_pattern, num, truncate_to_len @@ -420,15 +419,23 @@ def parse_content(content: Dict[str, Any]) -> str: fields = dict( name=msg.file_name, - duration=msg.duration, - size=msg.human_size, download=get_download(msg.local, msg.size), + size=msg.human_size, + duration=msg.duration, + listened=format_bool(msg.is_listened), + viewed=format_bool(msg.is_viewed), ) info = ", ".join(f"{k}={v}" for k, v in fields.items() if v) return f"[{msg.content_type}: {info}]" +def format_bool(value: Optional[bool]) -> Optional[str]: + if value is None: + return None + return "yes" if value else "no" + + def get_download(local, size): if local["is_downloading_completed"]: return "yes"