From 9d27f6c3d9e4627f045c6e869aa3196168131b57 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Sat, 27 Jun 2020 22:39:44 +0800 Subject: [PATCH 1/6] Draft: introduce top status panel --- tg/controllers.py | 23 ++++++++---- tg/models.py | 81 ++++++++++++++++++++++++++++++++++++++++++- tg/tdlib.py | 41 ++++++++++++++++++++++ tg/update_handlers.py | 34 +++++++++++++++--- tg/views.py | 69 ++++++++++++++++++++++++++++-------- 5 files changed, 222 insertions(+), 26 deletions(-) diff --git a/tg/controllers.py b/tg/controllers.py index 43b52ea..320a2b8 100644 --- a/tg/controllers.py +++ b/tg/controllers.py @@ -2,7 +2,6 @@ import curses import logging import os import shlex -import threading from datetime import datetime from functools import partial, wraps from queue import Queue @@ -12,12 +11,11 @@ from typing import Any, Callable, Dict, List, Optional from tg import config from tg.models import Model from tg.msg import MsgProxy -from tg.tdlib import Tdlib +from tg.tdlib import Action, Tdlib from tg.utils import ( get_duration, get_video_resolution, get_waveform, - handle_exception, is_yes, notify, suspend, @@ -238,10 +236,13 @@ class Controller: if not self.can_send_msg(): self.present_info("Can't send msg in this chat") return + chat_id = self.model.chats.id_by_index(self.model.current_chat) + self.tg.send_chat_action(chat_id, Action.chatActionTyping) if msg := self.view.status.get_input(): self.model.send_message(text=msg) self.present_info("Message sent") else: + self.tg.send_chat_action(chat_id, Action.chatActionCancel) self.present_info("Message wasn't sent") @bind(msg_handler, ["A", "I"]) @@ -252,11 +253,16 @@ class Controller: with NamedTemporaryFile("r+", suffix=".txt") as f, suspend( self.view ) as s: + chat_id = self.model.chats.id_by_index(self.model.current_chat) + self.tg.send_chat_action(chat_id, Action.chatActionTyping) s.call(config.LONG_MSG_CMD.format(file_path=shlex.quote(f.name))) with open(f.name) as f: if msg := f.read().strip(): self.model.send_message(text=msg) self.present_info("Message sent") + else: + self.tg.send_chat_action(chat_id, Action.chatActionCancel) + self.present_info("Message wasn't sent") @bind(msg_handler, ["sv"]) def send_video(self): @@ -543,14 +549,14 @@ class Controller: self.queue.put(self._render_chats) def _render_chats(self) -> None: - page_size = self.view.chats.h + page_size = self.view.chats.h - 1 chats = self.model.get_chats( self.model.current_chat, page_size, MSGS_LEFT_SCROLL_THRESHOLD ) selected_chat = min( self.model.current_chat, page_size - MSGS_LEFT_SCROLL_THRESHOLD ) - self.view.chats.draw(selected_chat, chats) + self.view.chats.draw(selected_chat, chats, self.model.chats.title) def render_msgs(self) -> None: self.queue.put(self._render_msgs) @@ -561,10 +567,13 @@ class Controller: return msgs = self.model.fetch_msgs( current_position=current_msg_idx, - page_size=self.view.msgs.h, + page_size=self.view.msgs.h - 1, msgs_left_scroll_threshold=MSGS_LEFT_SCROLL_THRESHOLD, ) - self.view.msgs.draw(current_msg_idx, msgs, MSGS_LEFT_SCROLL_THRESHOLD) + chat = self.model.chats.chats[self.model.current_chat] + self.view.msgs.draw( + current_msg_idx, msgs, MSGS_LEFT_SCROLL_THRESHOLD, chat + ) def notify_for_message(self, chat_id: int, msg: MsgProxy): # do not notify, if muted diff --git a/tg/models.py b/tg/models.py index c46fe89..169a626 100644 --- a/tg/models.py +++ b/tg/models.py @@ -4,7 +4,7 @@ from collections import defaultdict from typing import Any, Dict, List, Optional, Set, Tuple from tg.msg import MsgProxy -from tg.tdlib import Tdlib +from tg.tdlib import Action, Tdlib from tg.utils import copy_to_clipboard log = logging.getLogger(__name__) @@ -126,6 +126,8 @@ class Model: limit = offset + page_size return self.chats.fetch_chats(offset=offset, limit=limit) + # def send_action(self, action: Action) + def send_message(self, text: str) -> bool: chat_id = self.chats.id_by_index(self.current_chat) if chat_id is None: @@ -206,6 +208,7 @@ class ChatModel: self.chats: List[Dict[str, Any]] = [] self.chat_ids: List[int] = [] self.have_full_chat_list = False + self.title: str = "Chats" def id_by_index(self, index: int) -> Optional[int]: if index >= len(self.chats): @@ -474,6 +477,9 @@ class UserModel: self.tg = tg self.me = None self.users: Dict[int, Dict] = {} + self.groups: Dict[int, Dict] = {} + self.supergroups: Dict[int, Dict] = {} + self.actions: Dict[int, Dict] = {} self.not_found: Set[int] = set() def get_me(self): @@ -487,11 +493,39 @@ class UserModel: self.me = result.update return self.me + def get_action(self, chat_id: int) -> Optional[str]: + action = self.actions.get(chat_id) + if action is None: + return None + action_type = action["action"]["@type"] + try: + return Action[action_type].value + "..." + except KeyError: + log.error(f"Action type {action_type} not implemented") + return None + def set_status(self, user_id: int, status: Dict[str, Any]): if user_id not in self.users: self.get_user(user_id) self.users[user_id]["status"] = status + def get_status(self, user_id: int): + if user_id not in self.users: + return None + user_status = self.users[user_id]["status"] + log.info(f"user_status:: {user_status}") + status = self.statuses.get(user_status["@type"]) + if status == "online": + expires = user_status["expires"] + if expires < time.time(): + return None + return status + elif status == "offline": + was_online = user_status["was_online"] + ago = pretty_ts(was_online) + return f"last seen {ago}" + return f"last seen {status}" + def is_online(self, user_id: int): user = self.get_user(user_id) if ( @@ -516,3 +550,48 @@ class UserModel: return {} self.users[user_id] = result.update return result.update + + def get_group_info(self, group_id): + if group_id in self.groups: + return self.groups[group_id] + self.tg.get_basic_group(group_id) + + def get_supergroup_info(self, supergroup_id): + if supergroup_id in self.supergroups: + return self.supergroups[supergroup_id] + self.tg.get_supergroup(supergroup_id) + + +def pretty_ts(ts): + from datetime import datetime + + now = datetime.now() + diff = now - datetime.fromtimestamp(ts) + second_diff = diff.seconds + day_diff = diff.days + + if day_diff < 0: + return "" + + if day_diff == 0: + if second_diff < 10: + return "just now" + if second_diff < 60: + return f"{second_diff} seconds ago" + if second_diff < 120: + return "a minute ago" + if second_diff < 3600: + return f"{int(second_diff / 60)} minutes ago" + if second_diff < 7200: + return "an hour ago" + if second_diff < 86400: + return f"{int(second_diff / 3600)} hours ago" + if day_diff == 1: + return "Yesterday" + if day_diff < 7: + return f"{day_diff} days ago" + if day_diff < 31: + return f"{int(day_diff / 7)} weeks ago" + if day_diff < 365: + return f"{int(day_diff / 30)} months ago" + return f"{int(day_diff / 365)} years ago" diff --git a/tg/tdlib.py b/tg/tdlib.py index fe8c425..141df37 100644 --- a/tg/tdlib.py +++ b/tg/tdlib.py @@ -1,8 +1,25 @@ +from enum import Enum from typing import Any, Dict, List from telegram.client import AsyncResult, Telegram +class Action(Enum): + chatActionTyping = "typing" + chatActionCancel = "cancel" + chatActionRecordingVideo = "recording video" + chatActionUploadingVideo = "uploading video" + chatActionRecordingVoiceNote = "recording voice note" + chatActionUploadingVoiceNote = "uploading voice note" + chatActionUploadingPhoto = "uploading photo" + chatActionUploadingDocument = "uploading document" + chatActionChoosingLocation = "choosing location" + chatActionChoosingContact = "choosing contact" + chatActionStartPlayingGame = "start playing game" + chatActionRecordingVideoNote = "recording video note" + chatActionUploadingVideoNote = "uploading video note" + + class Tdlib(Telegram): def download_file( self, file_id, priority=16, offset=0, limit=0, synchronous=False, @@ -188,3 +205,27 @@ class Tdlib(Telegram): "options": options, } return self._send_data(data) + + def get_basic_group(self, basic_group_id: int,) -> AsyncResult: + data = { + "@type": "getBasicGroup", + "basic_group_id": basic_group_id, + } + return self._send_data(data) + + def get_supergroup(self, supergroup_id: int,) -> AsyncResult: + data = { + "@type": "getSupergroup", + "supergroup_id": supergroup_id, + } + return self._send_data(data) + + def send_chat_action( + self, chat_id: int, action: Action, progress: int = None + ) -> AsyncResult: + data = { + "@type": "sendChatAction", + "chat_id": chat_id, + "action": {"@type": action.name, "progress": progress}, + } + return self._send_data(data) diff --git a/tg/update_handlers.py b/tg/update_handlers.py index e530061..34d3e5f 100644 --- a/tg/update_handlers.py +++ b/tg/update_handlers.py @@ -1,4 +1,5 @@ import logging +import time from functools import wraps from typing import Any, Callable, Dict @@ -258,13 +259,38 @@ def update_connection_state(controller: Controller, update: Dict[str, Any]): "connectionStateConnectingToProxy": "Connecting to proxy...", "connectionStateConnecting": "Connecting...", "connectionStateUpdating": "Updating...", - "connectionStateReady": "Ready", + # "connectionStateReady": "Ready", } - msg = states.get(state, "Unknown state") - controller.present_info(msg) + controller.model.chats.title = states.get(state, "Chats") + controller.render_chats() @update_handler("updateUserStatus") def update_user_status(controller: Controller, update: Dict[str, Any]): controller.model.users.set_status(update["user_id"], update["status"]) - controller.render_chats() + controller.render() + + +@update_handler("updateBasicGroup") +def update_basic_group(controller: Controller, update: Dict[str, Any]): + basic_group = update["basic_group"] + controller.model.users.groups[basic_group["id"]] = basic_group + controller.render_msgs() + + +@update_handler("updateSupergroup") +def update_supergroup(controller: Controller, update: Dict[str, Any]): + supergroup = update["supergroup"] + controller.model.users.supergroups[supergroup["id"]] = supergroup + controller.render_msgs() + + +@update_handler("updateUserChatAction") +def update_user_chat_action(controller: Controller, update: Dict[str, Any]): + log.info("typing:: %s", update) + chat_id = update["chat_id"] + if update["action"]["@type"] == "chatActionCancel": + controller.model.users.actions.pop(chat_id, None) + else: + controller.model.users.actions[chat_id] = update + controller.render() diff --git a/tg/views.py b/tg/views.py index e7914f8..48797f3 100644 --- a/tg/views.py +++ b/tg/views.py @@ -161,19 +161,19 @@ class ChatView: return tuple(attr | reverse for attr in attrs) return attrs - def draw(self, current: int, chats: List[Dict[str, Any]]) -> None: + def draw( + self, current: int, chats: List[Dict[str, Any]], title: str = "Chats" + ) -> None: self.win.erase() line = curses.ACS_VLINE # type: ignore self.win.vline(0, self.w - 1, line, self.h) - for i, chat in enumerate(chats): - is_selected = i == current - unread_count = chat["unread_count"] - if chat["is_marked_as_unread"]: - unread_count = "unread" + self.win.addstr(0, 0, title.center(self.w - 1), get_color(cyan, -1)) + + for i, chat in enumerate(chats, 1): + is_selected = i == current + 1 date = get_date(chat) title = chat["title"] - is_pinned = chat["is_pinned"] last_msg = get_last_msg(chat) offset = 0 for attr, elem in zip( @@ -196,7 +196,7 @@ class ChatView: i, offset, last_msg, self._msg_color(is_selected) ) - if flags := self._get_flags(unread_count, is_pinned, chat): + if flags := self._get_flags(chat): flags_len = len(flags) + sum( map(len, emoji_pattern.findall(flags)) ) @@ -209,9 +209,7 @@ class ChatView: self._refresh() - def _get_flags( - self, unread_count: int, is_pinned: bool, chat: Dict[str, Any] - ) -> str: + def _get_flags(self, chat: Dict[str, Any]) -> str: flags = [] msg = chat.get("last_message") @@ -224,17 +222,22 @@ class ChatView: # last msg haven't been seen by recipient flags.append("unseen") + if action := self.model.users.get_action(chat["id"]): + flags.append(action) + if self.model.users.is_online(chat["id"]): flags.append("online") - if is_pinned: + if chat["is_pinned"]: flags.append("pinned") if chat["notification_settings"]["mute_for"]: flags.append("muted") - if unread_count: - flags.append(str(unread_count)) + if chat["is_marked_as_unread"]: + flags.append("unread") + elif chat["unread_count"]: + flags.append(str(chat["unread_count"])) label = " ".join(config.CHAT_FLAGS.get(flag, flag) for flag in flags) if label: @@ -425,6 +428,7 @@ class MsgView: current_msg_idx: int, msgs: List[Tuple[int, Dict[str, Any]]], min_msg_padding: int, + chat: Dict[str, Any], ) -> None: self.win.erase() msgs_to_draw = self._collect_msgs_to_draw( @@ -459,8 +463,45 @@ class MsgView: self.win.addstr(line_num, column, elem, attr) column += len(elem) + self.win.addstr(0, 0, self._msg_title(chat), get_color(cyan, -1)) self._refresh() + def _msg_title(self, chat: Dict[str, Any]): + chat_type = chat["type"]["@type"] + info = "" + _type = "unknown" + if chat_type == "chatTypePrivate": + _type = "private" + info = self.model.users.get_status(chat["id"]) or "" + elif chat_type == "chatTypeBasicGroup": + _type = "group" + group = self.model.users.get_group_info( + chat["type"]["basic_group_id"] + ) + log.info(f"group:: {group}") + if group: + info = f"{group['member_count']} members" + elif chat_type == "chatTypeSupergroup": + if chat["type"]["is_channel"]: + _type = "channel" + else: + _type = "supergroup" + supergroup = self.model.users.get_supergroup_info( + chat["type"]["supergroup_id"] + ) + log.info(f"supergroup:: {supergroup}") + if supergroup: + info = f"{supergroup['member_count']} members" + + elif chat_type == "chatTypeSecret": + _type = "secret" + + # return f" {chat['title']} [{_type}] {info}".center(self.w) + if action := self.model.users.get_action(chat["id"]): + info = action + + return f" {chat['title']}: {info}".center(self.w) + def _msg_attributes(self, is_selected: bool) -> Tuple[int, ...]: attrs = ( get_color(cyan, -1), From ba38cd207c8f84fa209ba24e0c543b4a7b24f045 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Sun, 28 Jun 2020 10:24:22 +0800 Subject: [PATCH 2/6] Use enum instead of dict and refactor code --- tg/controllers.py | 12 +++--- tg/models.py | 79 ++++++++++---------------------------- tg/tdlib.py | 29 +++++++++++--- tg/utils.py | 34 ++++++++++++++++ tg/views.py | 98 +++++++++++++++++++++++++++-------------------- 5 files changed, 141 insertions(+), 111 deletions(-) diff --git a/tg/controllers.py b/tg/controllers.py index 320a2b8..5b1972e 100644 --- a/tg/controllers.py +++ b/tg/controllers.py @@ -11,7 +11,7 @@ from typing import Any, Callable, Dict, List, Optional from tg import config from tg.models import Model from tg.msg import MsgProxy -from tg.tdlib import Action, Tdlib +from tg.tdlib import ChatAction, Tdlib from tg.utils import ( get_duration, get_video_resolution, @@ -237,12 +237,12 @@ class Controller: self.present_info("Can't send msg in this chat") return chat_id = self.model.chats.id_by_index(self.model.current_chat) - self.tg.send_chat_action(chat_id, Action.chatActionTyping) + self.tg.send_chat_action(chat_id, ChatAction.chatActionTyping) if msg := self.view.status.get_input(): self.model.send_message(text=msg) self.present_info("Message sent") else: - self.tg.send_chat_action(chat_id, Action.chatActionCancel) + self.tg.send_chat_action(chat_id, ChatAction.chatActionCancel) self.present_info("Message wasn't sent") @bind(msg_handler, ["A", "I"]) @@ -254,14 +254,16 @@ class Controller: self.view ) as s: chat_id = self.model.chats.id_by_index(self.model.current_chat) - self.tg.send_chat_action(chat_id, Action.chatActionTyping) + self.tg.send_chat_action(chat_id, ChatAction.chatActionTyping) s.call(config.LONG_MSG_CMD.format(file_path=shlex.quote(f.name))) with open(f.name) as f: if msg := f.read().strip(): self.model.send_message(text=msg) self.present_info("Message sent") else: - self.tg.send_chat_action(chat_id, Action.chatActionCancel) + self.tg.send_chat_action( + chat_id, ChatAction.chatActionCancel + ) self.present_info("Message wasn't sent") @bind(msg_handler, ["sv"]) diff --git a/tg/models.py b/tg/models.py index 169a626..78bf606 100644 --- a/tg/models.py +++ b/tg/models.py @@ -4,8 +4,8 @@ from collections import defaultdict from typing import Any, Dict, List, Optional, Set, Tuple from tg.msg import MsgProxy -from tg.tdlib import Action, Tdlib -from tg.utils import copy_to_clipboard +from tg.tdlib import ChatAction, Tdlib, UserStatus +from tg.utils import copy_to_clipboard, pretty_ts log = logging.getLogger(__name__) @@ -126,8 +126,6 @@ class Model: limit = offset + page_size return self.chats.fetch_chats(offset=offset, limit=limit) - # def send_action(self, action: Action) - def send_message(self, text: str) -> bool: chat_id = self.chats.id_by_index(self.current_chat) if chat_id is None: @@ -457,15 +455,6 @@ class MsgModel: class UserModel: - statuses = { - "userStatusEmpty": "", - "userStatusOnline": "online", - "userStatusOffline": "offline", - "userStatusRecently": "recently", - "userStatusLastWeek": "last week", - "userStatusLastMonth": "last month", - } - types = { "userTypeUnknown": "unknown", "userTypeBot": "bot", @@ -499,9 +488,9 @@ class UserModel: return None action_type = action["action"]["@type"] try: - return Action[action_type].value + "..." + return ChatAction[action_type].value + "..." except KeyError: - log.error(f"Action type {action_type} not implemented") + log.error(f"ChatAction type {action_type} not implemented") return None def set_status(self, user_id: int, status: Dict[str, Any]): @@ -509,22 +498,29 @@ class UserModel: self.get_user(user_id) self.users[user_id]["status"] = status - def get_status(self, user_id: int): + def get_status(self, user_id: int) -> str: if user_id not in self.users: - return None + return "" user_status = self.users[user_id]["status"] - log.info(f"user_status:: {user_status}") - status = self.statuses.get(user_status["@type"]) - if status == "online": + + try: + status = UserStatus[user_status["@type"]] + except KeyError: + log.error(f"UserStatus type {user_status} not implemented") + return "" + + if status == UserStatus.userStatusEmpty: + return "" + elif status == UserStatus.userStatusOnline: expires = user_status["expires"] if expires < time.time(): - return None - return status - elif status == "offline": + return "" + return status.value + elif status == UserStatus.userStatusOffline: was_online = user_status["was_online"] ago = pretty_ts(was_online) return f"last seen {ago}" - return f"last seen {status}" + return f"last seen {status.value}" def is_online(self, user_id: int): user = self.get_user(user_id) @@ -560,38 +556,3 @@ class UserModel: if supergroup_id in self.supergroups: return self.supergroups[supergroup_id] self.tg.get_supergroup(supergroup_id) - - -def pretty_ts(ts): - from datetime import datetime - - now = datetime.now() - diff = now - datetime.fromtimestamp(ts) - second_diff = diff.seconds - day_diff = diff.days - - if day_diff < 0: - return "" - - if day_diff == 0: - if second_diff < 10: - return "just now" - if second_diff < 60: - return f"{second_diff} seconds ago" - if second_diff < 120: - return "a minute ago" - if second_diff < 3600: - return f"{int(second_diff / 60)} minutes ago" - if second_diff < 7200: - return "an hour ago" - if second_diff < 86400: - return f"{int(second_diff / 3600)} hours ago" - if day_diff == 1: - return "Yesterday" - if day_diff < 7: - return f"{day_diff} days ago" - if day_diff < 31: - return f"{int(day_diff / 7)} weeks ago" - if day_diff < 365: - return f"{int(day_diff / 30)} months ago" - return f"{int(day_diff / 365)} years ago" diff --git a/tg/tdlib.py b/tg/tdlib.py index 141df37..7bfd6db 100644 --- a/tg/tdlib.py +++ b/tg/tdlib.py @@ -4,20 +4,37 @@ from typing import Any, Dict, List from telegram.client import AsyncResult, Telegram -class Action(Enum): +class ChatAction(Enum): chatActionTyping = "typing" chatActionCancel = "cancel" chatActionRecordingVideo = "recording video" chatActionUploadingVideo = "uploading video" - chatActionRecordingVoiceNote = "recording voice note" - chatActionUploadingVoiceNote = "uploading voice note" + chatActionRecordingVoiceNote = "recording voice" + chatActionUploadingVoiceNote = "uploading voice" chatActionUploadingPhoto = "uploading photo" chatActionUploadingDocument = "uploading document" chatActionChoosingLocation = "choosing location" chatActionChoosingContact = "choosing contact" chatActionStartPlayingGame = "start playing game" - chatActionRecordingVideoNote = "recording video note" - chatActionUploadingVideoNote = "uploading video note" + chatActionRecordingVideoNote = "recording video" + chatActionUploadingVideoNote = "uploading video" + + +class ChatType(Enum): + chatTypePrivate = "private" + chatTypeBasicGroup = "group" + chatTypeSupergroup = "supergroup" + channel = "channel" + chatTypeSecret = "secret" + + +class UserStatus(Enum): + userStatusEmpty = "" + userStatusOnline = "online" + userStatusOffline = "offline" + userStatusRecently = "recently" + userStatusLastWeek = "last week" + userStatusLastMonth = "last month" class Tdlib(Telegram): @@ -221,7 +238,7 @@ class Tdlib(Telegram): return self._send_data(data) def send_chat_action( - self, chat_id: int, action: Action, progress: int = None + self, chat_id: int, action: ChatAction, progress: int = None ) -> AsyncResult: data = { "@type": "sendChatAction", diff --git a/tg/utils.py b/tg/utils.py index c27f1da..7c63413 100644 --- a/tg/utils.py +++ b/tg/utils.py @@ -216,3 +216,37 @@ class suspend: def set_shorter_esc_delay(delay=25): os.environ.setdefault("ESCDELAY", str(delay)) + + +def pretty_ts(ts): + now = datetime.utcnow() + diff = now - datetime.utcfromtimestamp(ts) + second_diff = diff.seconds + day_diff = diff.days + log.info("diff:: %s, %s, %s", ts, second_diff, day_diff) + + if day_diff < 0: + return "" + + if day_diff == 0: + if second_diff < 10: + return "just now" + if second_diff < 60: + return f"{second_diff} seconds ago" + if second_diff < 120: + return "a minute ago" + if second_diff < 3600: + return f"{int(second_diff / 60)} minutes ago" + if second_diff < 7200: + return "an hour ago" + if second_diff < 86400: + return f"{int(second_diff / 3600)} hours ago" + if day_diff == 1: + return "Yesterday" + if day_diff < 7: + return f"{day_diff} days ago" + if day_diff < 31: + return f"{int(day_diff / 7)} weeks ago" + if day_diff < 365: + return f"{int(day_diff / 30)} months ago" + return f"{int(day_diff / 365)} years ago" diff --git a/tg/views.py b/tg/views.py index 48797f3..7063167 100644 --- a/tg/views.py +++ b/tg/views.py @@ -5,9 +5,19 @@ from datetime import datetime from typing import Any, Dict, List, Optional, Tuple, cast from tg import config -from tg.colors import blue, cyan, get_color, magenta, reverse, white, yellow -from tg.models import Model, MsgModel, UserModel +from tg.colors import ( + blue, + bold, + cyan, + get_color, + magenta, + reverse, + white, + yellow, +) +from tg.models import Model from tg.msg import MsgProxy +from tg.tdlib import ChatType from tg.utils import emoji_pattern, num, truncate_to_len log = logging.getLogger(__name__) @@ -166,9 +176,11 @@ class ChatView: ) -> None: self.win.erase() line = curses.ACS_VLINE # type: ignore - self.win.vline(0, self.w - 1, line, self.h) - - self.win.addstr(0, 0, title.center(self.w - 1), get_color(cyan, -1)) + width = self.w - 1 + self.win.vline(0, width, line, self.h) + self.win.addstr( + 0, 0, title.center(width)[:width], get_color(cyan, -1) | bold + ) for i, chat in enumerate(chats, 1): is_selected = i == current + 1 @@ -182,7 +194,7 @@ class ChatView: self.win.addstr( i, offset, - truncate_to_len(elem, max(0, self.w - offset - 1)), + truncate_to_len(elem, max(0, width - offset)), attr, ) offset += len(elem) + sum( @@ -202,8 +214,8 @@ class ChatView: ) self.win.addstr( i, - self.w - flags_len - 1, - flags, + max(0, width - flags_len), + flags[-width:], self._unread_color(is_selected), ) @@ -463,44 +475,48 @@ class MsgView: self.win.addstr(line_num, column, elem, attr) column += len(elem) - self.win.addstr(0, 0, self._msg_title(chat), get_color(cyan, -1)) + self.win.addstr( + 0, 0, self._msg_title(chat), get_color(cyan, -1) | bold + ) self._refresh() + def _get_chat_type(self, chat: Dict[str, Any]) -> Optional[ChatType]: + try: + chat_type = ChatType[chat["type"]["@type"]] + if ( + chat_type == ChatType.chatTypeSupergroup + and chat["type"]["is_channel"] + ): + chat_type = ChatType.channel + return chat_type + except KeyError: + log.error(f"ChatType {chat['type']} not implemented") + return None + def _msg_title(self, chat: Dict[str, Any]): - chat_type = chat["type"]["@type"] - info = "" - _type = "unknown" - if chat_type == "chatTypePrivate": - _type = "private" - info = self.model.users.get_status(chat["id"]) or "" - elif chat_type == "chatTypeBasicGroup": - _type = "group" - group = self.model.users.get_group_info( - chat["type"]["basic_group_id"] - ) - log.info(f"group:: {group}") - if group: - info = f"{group['member_count']} members" - elif chat_type == "chatTypeSupergroup": - if chat["type"]["is_channel"]: - _type = "channel" - else: - _type = "supergroup" - supergroup = self.model.users.get_supergroup_info( - chat["type"]["supergroup_id"] - ) - log.info(f"supergroup:: {supergroup}") - if supergroup: - info = f"{supergroup['member_count']} members" - - elif chat_type == "chatTypeSecret": - _type = "secret" - - # return f" {chat['title']} [{_type}] {info}".center(self.w) + chat_type = self._get_chat_type(chat) + status = "" if action := self.model.users.get_action(chat["id"]): - info = action + status = action + elif chat_type == ChatType.chatTypePrivate: + status = self.model.users.get_status(chat["id"]) + elif chat_type == ChatType.chatTypeBasicGroup: + if group := self.model.users.get_group_info( + chat["type"]["basic_group_id"] + ): + status = f"{group['member_count']} members" + elif chat_type == ChatType.chatTypeSupergroup: + if supergroup := self.model.users.get_supergroup_info( + chat["type"]["supergroup_id"] + ): + status = f"{supergroup['member_count']} members" + elif chat_type == ChatType.channel: + if supergroup := self.model.users.get_supergroup_info( + chat["type"]["supergroup_id"] + ): + status = f"{supergroup['member_count']} subscribers" - return f" {chat['title']}: {info}".center(self.w) + return f"{chat['title']}: {status}".center(self.w)[: self.w] def _msg_attributes(self, is_selected: bool) -> Tuple[int, ...]: attrs = ( From 46cd48f5560b27c7e363baec66ce2efb0f7a8698 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 30 Jun 2020 14:42:25 +0800 Subject: [PATCH 3/6] Add type hints and remove redundant logs --- tg/models.py | 2 +- tg/update_handlers.py | 14 +------------- tg/utils.py | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/tg/models.py b/tg/models.py index 78bf606..3f98df6 100644 --- a/tg/models.py +++ b/tg/models.py @@ -547,7 +547,7 @@ class UserModel: self.users[user_id] = result.update return result.update - def get_group_info(self, group_id): + def get_group_info(self, group_id: int): if group_id in self.groups: return self.groups[group_id] self.tg.get_basic_group(group_id) diff --git a/tg/update_handlers.py b/tg/update_handlers.py index 34d3e5f..f733984 100644 --- a/tg/update_handlers.py +++ b/tg/update_handlers.py @@ -1,5 +1,4 @@ import logging -import time from functools import wraps from typing import Any, Callable, Dict @@ -77,7 +76,6 @@ def update_new_message(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatOrder") def update_chat_order(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatOrder") current_chat_id = controller.model.current_chat_id chat_id = update["chat_id"] order = update["order"] @@ -88,7 +86,6 @@ def update_chat_order(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatTitle") def update_chat_title(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatTitle") chat_id = update["chat_id"] title = update["title"] @@ -101,7 +98,6 @@ def update_chat_title(controller: Controller, update: Dict[str, Any]): def update_chat_is_marked_as_unread( controller: Controller, update: Dict[str, Any] ): - log.info("Proccessing updateChatIsMarkedAsUnread") chat_id = update["chat_id"] is_marked_as_unread = update["is_marked_as_unread"] @@ -114,7 +110,6 @@ def update_chat_is_marked_as_unread( @update_handler("updateChatIsPinned") def update_chat_is_pinned(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatIsPinned") chat_id = update["chat_id"] is_pinned = update["is_pinned"] order = update["order"] @@ -128,7 +123,6 @@ def update_chat_is_pinned(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatReadOutbox") def update_chat_read_outbox(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatReadOutbox") chat_id = update["chat_id"] last_read_outbox_message_id = update["last_read_outbox_message_id"] @@ -141,7 +135,6 @@ def update_chat_read_outbox(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatReadInbox") def update_chat_read_inbox(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatReadInbox") chat_id = update["chat_id"] last_read_inbox_message_id = update["last_read_inbox_message_id"] unread_count = update["unread_count"] @@ -157,7 +150,6 @@ def update_chat_read_inbox(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatDraftMessage") def update_chat_draft_message(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatDraftMessage") chat_id = update["chat_id"] # FIXME: ignoring draft message itself for now because UI can't show it # draft_message = update["draft_message"] @@ -170,7 +162,6 @@ def update_chat_draft_message(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatLastMessage") def update_chat_last_message(controller: Controller, update: Dict[str, Any]): - log.info("Proccessing updateChatLastMessage") chat_id = update["chat_id"] last_message = update.get("last_message") if not last_message: @@ -188,7 +179,6 @@ def update_chat_last_message(controller: Controller, update: Dict[str, Any]): @update_handler("updateChatNotificationSettings") def update_chat_notification_settings(controller: Controller, update): - log.info("Proccessing update_chat_notification_settings") chat_id = update["chat_id"] notification_settings = update["notification_settings"] if controller.model.chats.update_chat( @@ -211,7 +201,6 @@ def update_message_send_succeeded(controller: Controller, update): @update_handler("updateFile") def update_file(controller: Controller, update): - log.info("update_file: %s", update) file_id = update["file"]["id"] local = update["file"]["local"] chat_id, msg_id = controller.model.downloads.get(file_id, (None, None)) @@ -252,13 +241,13 @@ def update_delete_messages(controller: Controller, update: Dict[str, Any]): @update_handler("updateConnectionState") def update_connection_state(controller: Controller, update: Dict[str, Any]): - log.info("state:: %s", update) state = update["state"]["@type"] states = { "connectionStateWaitingForNetwork": "Waiting for network...", "connectionStateConnectingToProxy": "Connecting to proxy...", "connectionStateConnecting": "Connecting...", "connectionStateUpdating": "Updating...", + # state exists, but when it's "Ready" we want to show "Chats" # "connectionStateReady": "Ready", } controller.model.chats.title = states.get(state, "Chats") @@ -287,7 +276,6 @@ def update_supergroup(controller: Controller, update: Dict[str, Any]): @update_handler("updateUserChatAction") def update_user_chat_action(controller: Controller, update: Dict[str, Any]): - log.info("typing:: %s", update) chat_id = update["chat_id"] if update["action"]["@type"] == "chatActionCancel": controller.model.users.actions.pop(chat_id, None) diff --git a/tg/utils.py b/tg/utils.py index 7c63413..f5626a6 100644 --- a/tg/utils.py +++ b/tg/utils.py @@ -218,7 +218,7 @@ def set_shorter_esc_delay(delay=25): os.environ.setdefault("ESCDELAY", str(delay)) -def pretty_ts(ts): +def pretty_ts(ts: int): now = datetime.utcnow() diff = now - datetime.utcfromtimestamp(ts) second_diff = diff.seconds From 1d21329b7baddfebad0177cb03eaaab8b70f866e Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 30 Jun 2020 14:43:35 +0800 Subject: [PATCH 4/6] Add type hint for return value --- tg/models.py | 2 +- tg/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tg/models.py b/tg/models.py index 3f98df6..2c9a0d9 100644 --- a/tg/models.py +++ b/tg/models.py @@ -547,7 +547,7 @@ class UserModel: self.users[user_id] = result.update return result.update - def get_group_info(self, group_id: int): + def get_group_info(self, group_id: int) -> None: if group_id in self.groups: return self.groups[group_id] self.tg.get_basic_group(group_id) diff --git a/tg/utils.py b/tg/utils.py index f5626a6..5e90c0a 100644 --- a/tg/utils.py +++ b/tg/utils.py @@ -218,7 +218,7 @@ def set_shorter_esc_delay(delay=25): os.environ.setdefault("ESCDELAY", str(delay)) -def pretty_ts(ts: int): +def pretty_ts(ts: int) -> str: now = datetime.utcnow() diff = now - datetime.utcfromtimestamp(ts) second_diff = diff.seconds From dd2433a7c7eecee413efff4da3bbe644271cf9d0 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 30 Jun 2020 14:46:34 +0800 Subject: [PATCH 5/6] Fix type hints --- tg/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tg/models.py b/tg/models.py index 2c9a0d9..dba7bfe 100644 --- a/tg/models.py +++ b/tg/models.py @@ -547,12 +547,14 @@ class UserModel: self.users[user_id] = result.update return result.update - def get_group_info(self, group_id: int) -> None: + def get_group_info(self, group_id: int) -> Optional[Dict[str, Any]]: if group_id in self.groups: return self.groups[group_id] self.tg.get_basic_group(group_id) + return None - def get_supergroup_info(self, supergroup_id): + def get_supergroup_info(self, supergroup_id: int) -> Optional[Dict[str, Any]]: if supergroup_id in self.supergroups: return self.supergroups[supergroup_id] self.tg.get_supergroup(supergroup_id) + return None From 268920f92a694a9956fcce9ca32ac72bfce1e1a1 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 30 Jun 2020 14:51:18 +0800 Subject: [PATCH 6/6] Fix black formatting --- tg/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tg/models.py b/tg/models.py index dba7bfe..eaf2e8a 100644 --- a/tg/models.py +++ b/tg/models.py @@ -553,7 +553,9 @@ class UserModel: self.tg.get_basic_group(group_id) return None - def get_supergroup_info(self, supergroup_id: int) -> Optional[Dict[str, Any]]: + def get_supergroup_info( + self, supergroup_id: int + ) -> Optional[Dict[str, Any]]: if supergroup_id in self.supergroups: return self.supergroups[supergroup_id] self.tg.get_supergroup(supergroup_id)