mirror of
https://github.com/paul-nameless/tg
synced 2024-11-25 13:20:25 +00:00
Show list of contacts (#119)
* Get contacts * Count min user name column length, sort by user name * Fix black formatting * Fix black formatting * Order users in contacts by last seen status
This commit is contained in:
parent
f168fcf01d
commit
532fa0154a
6 changed files with 82 additions and 15 deletions
|
@ -33,7 +33,7 @@ MAX_DOWNLOAD_SIZE = "10MB"
|
||||||
# TODO: check platform
|
# TODO: check platform
|
||||||
NOTIFY_CMD = "/usr/local/bin/terminal-notifier -title {title} -subtitle {subtitle} -message {msg} -appIcon {icon_path}"
|
NOTIFY_CMD = "/usr/local/bin/terminal-notifier -title {title} -subtitle {subtitle} -message {msg} -appIcon {icon_path}"
|
||||||
|
|
||||||
HELP_CMD = "less"
|
VIEW_TEXT_CMD = "less"
|
||||||
|
|
||||||
if _os_name == _linux:
|
if _os_name == _linux:
|
||||||
# for more info see https://trac.ffmpeg.org/wiki/Capture/ALSA
|
# for more info see https://trac.ffmpeg.org/wiki/Capture/ALSA
|
||||||
|
|
|
@ -12,7 +12,7 @@ from telegram.utils import AsyncResult
|
||||||
from tg import config
|
from tg import config
|
||||||
from tg.models import Model
|
from tg.models import Model
|
||||||
from tg.msg import MsgProxy
|
from tg.msg import MsgProxy
|
||||||
from tg.tdlib import ChatAction, Tdlib
|
from tg.tdlib import ChatAction, Tdlib, UserStatus
|
||||||
from tg.utils import (
|
from tg.utils import (
|
||||||
get_duration,
|
get_duration,
|
||||||
get_video_resolution,
|
get_video_resolution,
|
||||||
|
@ -21,7 +21,7 @@ from tg.utils import (
|
||||||
notify,
|
notify,
|
||||||
suspend,
|
suspend,
|
||||||
)
|
)
|
||||||
from tg.views import View
|
from tg.views import View, get_user_label
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -109,13 +109,13 @@ class Controller:
|
||||||
def show_chat_help(self) -> None:
|
def show_chat_help(self) -> None:
|
||||||
_help = self.format_help(chat_handler)
|
_help = self.format_help(chat_handler)
|
||||||
with suspend(self.view) as s:
|
with suspend(self.view) as s:
|
||||||
s.run_with_input(config.HELP_CMD, _help)
|
s.run_with_input(config.VIEW_TEXT_CMD, _help)
|
||||||
|
|
||||||
@bind(msg_handler, ["?"])
|
@bind(msg_handler, ["?"])
|
||||||
def show_msg_help(self) -> None:
|
def show_msg_help(self) -> None:
|
||||||
_help = self.format_help(msg_handler)
|
_help = self.format_help(msg_handler)
|
||||||
with suspend(self.view) as s:
|
with suspend(self.view) as s:
|
||||||
s.run_with_input(config.HELP_CMD, _help)
|
s.run_with_input(config.VIEW_TEXT_CMD, _help)
|
||||||
|
|
||||||
@bind(chat_handler, ["bp"])
|
@bind(chat_handler, ["bp"])
|
||||||
@bind(msg_handler, ["bp"])
|
@bind(msg_handler, ["bp"])
|
||||||
|
@ -453,6 +453,33 @@ class Controller:
|
||||||
self.model.edit_message(text=text)
|
self.model.edit_message(text=text)
|
||||||
self.present_info("Message edited")
|
self.present_info("Message edited")
|
||||||
|
|
||||||
|
@bind(chat_handler, ["c"])
|
||||||
|
def view_contacts(self) -> None:
|
||||||
|
contacts = self.model.users.get_contacts()
|
||||||
|
if contacts is None:
|
||||||
|
return self.present_error("Can't get contacts")
|
||||||
|
|
||||||
|
total = contacts["total_count"]
|
||||||
|
users = []
|
||||||
|
for user_id in contacts["user_ids"]:
|
||||||
|
user_name = get_user_label(self.model.users, user_id)
|
||||||
|
status = self.model.users.get_status(user_id)
|
||||||
|
order = self.model.users.get_user_status_order(user_id)
|
||||||
|
users.append((user_name, status, order))
|
||||||
|
|
||||||
|
_, cols = self.view.stdscr.getmaxyx()
|
||||||
|
limit = min(
|
||||||
|
int(cols / 2), max(len(user_name) for user_name, *_ in users)
|
||||||
|
)
|
||||||
|
users_out = "\n".join(
|
||||||
|
f"{user_name:<{limit}} | {status}"
|
||||||
|
for user_name, status, _ in sorted(users, key=lambda it: it[2])
|
||||||
|
)
|
||||||
|
with suspend(self.view) as s:
|
||||||
|
s.run_with_input(
|
||||||
|
config.VIEW_TEXT_CMD, f"{total} users:\n" + users_out
|
||||||
|
)
|
||||||
|
|
||||||
@bind(chat_handler, ["l", "^J", "^E"])
|
@bind(chat_handler, ["l", "^J", "^E"])
|
||||||
def handle_msgs(self) -> Optional[str]:
|
def handle_msgs(self) -> Optional[str]:
|
||||||
rc = self.handle(msg_handler, 0.2)
|
rc = self.handle(msg_handler, 0.2)
|
||||||
|
|
40
tg/models.py
40
tg/models.py
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||||
|
@ -27,9 +28,6 @@ class Model:
|
||||||
def is_me(self, user_id: int) -> bool:
|
def is_me(self, user_id: int) -> bool:
|
||||||
return self.get_me()["id"] == user_id
|
return self.get_me()["id"] == user_id
|
||||||
|
|
||||||
def get_user(self, user_id: int) -> Dict:
|
|
||||||
return self.users.get_user(user_id)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_chat_id(self) -> Optional[int]:
|
def current_chat_id(self) -> Optional[int]:
|
||||||
return self.chats.id_by_index(self.current_chat)
|
return self.chats.id_by_index(self.current_chat)
|
||||||
|
@ -464,6 +462,7 @@ class UserModel:
|
||||||
self.supergroups: Dict[int, Dict] = {}
|
self.supergroups: Dict[int, Dict] = {}
|
||||||
self.actions: Dict[int, Dict] = {}
|
self.actions: Dict[int, Dict] = {}
|
||||||
self.not_found: Set[int] = set()
|
self.not_found: Set[int] = set()
|
||||||
|
self.contacts: Dict[str, Any] = {}
|
||||||
|
|
||||||
def get_me(self) -> Dict[str, Any]:
|
def get_me(self) -> Dict[str, Any]:
|
||||||
if self.me:
|
if self.me:
|
||||||
|
@ -519,6 +518,28 @@ class UserModel:
|
||||||
return f"last seen {ago}"
|
return f"last seen {ago}"
|
||||||
return f"last seen {status.value}"
|
return f"last seen {status.value}"
|
||||||
|
|
||||||
|
def get_user_status_order(self, user_id: int) -> int:
|
||||||
|
if user_id not in self.users:
|
||||||
|
return sys.maxsize
|
||||||
|
user_status = self.users[user_id]["status"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
status = UserStatus[user_status["@type"]]
|
||||||
|
except KeyError:
|
||||||
|
log.error(f"UserStatus type {user_status} not implemented")
|
||||||
|
return sys.maxsize
|
||||||
|
if status == UserStatus.userStatusOnline:
|
||||||
|
return 0
|
||||||
|
elif status == UserStatus.userStatusOffline:
|
||||||
|
was_online = user_status["was_online"]
|
||||||
|
return time.time() - was_online
|
||||||
|
order = {
|
||||||
|
UserStatus.userStatusRecently: 1,
|
||||||
|
UserStatus.userStatusLastWeek: 2,
|
||||||
|
UserStatus.userStatusLastMonth: 3,
|
||||||
|
}
|
||||||
|
return order.get(status, sys.maxsize)
|
||||||
|
|
||||||
def is_online(self, user_id: int) -> bool:
|
def is_online(self, user_id: int) -> bool:
|
||||||
user = self.get_user(user_id)
|
user = self.get_user(user_id)
|
||||||
if (
|
if (
|
||||||
|
@ -557,3 +578,16 @@ class UserModel:
|
||||||
return self.supergroups[supergroup_id]
|
return self.supergroups[supergroup_id]
|
||||||
self.tg.get_supergroup(supergroup_id)
|
self.tg.get_supergroup(supergroup_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_contacts(self) -> Optional[Dict[str, Any]]:
|
||||||
|
if self.contacts:
|
||||||
|
return self.contacts
|
||||||
|
|
||||||
|
result = self.tg.get_contacts()
|
||||||
|
result.wait()
|
||||||
|
|
||||||
|
if result.error:
|
||||||
|
log.error("get contacts error: %s", result.error_info)
|
||||||
|
return None
|
||||||
|
self.contacts = result.update
|
||||||
|
return self.contacts
|
||||||
|
|
|
@ -257,6 +257,12 @@ class Tdlib(Telegram):
|
||||||
}
|
}
|
||||||
return self._send_data(data)
|
return self._send_data(data)
|
||||||
|
|
||||||
|
def get_contacts(self) -> AsyncResult:
|
||||||
|
data = {
|
||||||
|
"@type": "getContacts",
|
||||||
|
}
|
||||||
|
return self._send_data(data)
|
||||||
|
|
||||||
|
|
||||||
def get_chat_type(chat: Dict[str, Any]) -> Optional[ChatType]:
|
def get_chat_type(chat: Dict[str, Any]) -> Optional[ChatType]:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -252,7 +252,7 @@ def pretty_ts(ts: int) -> str:
|
||||||
if second_diff < 86400:
|
if second_diff < 86400:
|
||||||
return f"{int(second_diff / 3600)} hours ago"
|
return f"{int(second_diff / 3600)} hours ago"
|
||||||
if day_diff == 1:
|
if day_diff == 1:
|
||||||
return "Yesterday"
|
return "yesterday"
|
||||||
if day_diff < 7:
|
if day_diff < 7:
|
||||||
return f"{day_diff} days ago"
|
return f"{day_diff} days ago"
|
||||||
if day_diff < 31:
|
if day_diff < 31:
|
||||||
|
|
12
tg/views.py
12
tg/views.py
|
@ -224,7 +224,7 @@ class ChatView:
|
||||||
) -> Tuple[Optional[str], Optional[str]]:
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
user, last_msg = get_last_msg(chat)
|
user, last_msg = get_last_msg(chat)
|
||||||
if user:
|
if user:
|
||||||
last_msg_sender = _get_user_label(self.model.users, user)
|
last_msg_sender = get_user_label(self.model.users, user)
|
||||||
chat_type = get_chat_type(chat)
|
chat_type = get_chat_type(chat)
|
||||||
if chat_type and chat_type.is_group(chat_type):
|
if chat_type and chat_type.is_group(chat_type):
|
||||||
return last_msg_sender, last_msg
|
return last_msg_sender, last_msg
|
||||||
|
@ -328,7 +328,7 @@ class MsgView:
|
||||||
return msg
|
return msg
|
||||||
reply_msg = MsgProxy(_msg)
|
reply_msg = MsgProxy(_msg)
|
||||||
if reply_msg_content := self._parse_msg(reply_msg):
|
if reply_msg_content := self._parse_msg(reply_msg):
|
||||||
reply_sender = _get_user_label(
|
reply_sender = get_user_label(
|
||||||
self.model.users, reply_msg.sender_id
|
self.model.users, reply_msg.sender_id
|
||||||
)
|
)
|
||||||
sender_name = f" {reply_sender}:" if reply_sender else ""
|
sender_name = f" {reply_sender}:" if reply_sender else ""
|
||||||
|
@ -390,7 +390,7 @@ class MsgView:
|
||||||
dt = msg_proxy.date.strftime("%H:%M:%S")
|
dt = msg_proxy.date.strftime("%H:%M:%S")
|
||||||
user_id_item = msg_proxy.sender_id
|
user_id_item = msg_proxy.sender_id
|
||||||
|
|
||||||
user_id = _get_user_label(self.model.users, user_id_item)
|
user_id = get_user_label(self.model.users, user_id_item)
|
||||||
flags = self._get_flags(msg_proxy)
|
flags = self._get_flags(msg_proxy)
|
||||||
if user_id and flags:
|
if user_id and flags:
|
||||||
# if not channel add space between name and flags
|
# if not channel add space between name and flags
|
||||||
|
@ -617,7 +617,7 @@ def get_download(
|
||||||
return "no"
|
return "no"
|
||||||
|
|
||||||
|
|
||||||
def _get_user_label(users: UserModel, user_id: int) -> str:
|
def get_user_label(users: UserModel, user_id: int) -> str:
|
||||||
if user_id == 0:
|
if user_id == 0:
|
||||||
return ""
|
return ""
|
||||||
user = users.get_user(user_id)
|
user = users.get_user(user_id)
|
||||||
|
@ -629,7 +629,7 @@ def _get_user_label(users: UserModel, user_id: int) -> str:
|
||||||
|
|
||||||
if user.get("username"):
|
if user.get("username"):
|
||||||
return "@" + user["username"]
|
return "@" + user["username"]
|
||||||
return "Unknown?"
|
return "<Unknown>"
|
||||||
|
|
||||||
|
|
||||||
def _get_action_label(users: UserModel, chat: Dict[str, Any]) -> Optional[str]:
|
def _get_action_label(users: UserModel, chat: Dict[str, Any]) -> Optional[str]:
|
||||||
|
@ -638,7 +638,7 @@ def _get_action_label(users: UserModel, chat: Dict[str, Any]) -> Optional[str]:
|
||||||
label = f"{action}..."
|
label = f"{action}..."
|
||||||
chat_type = get_chat_type(chat)
|
chat_type = get_chat_type(chat)
|
||||||
if chat_type and chat_type.is_group(chat_type):
|
if chat_type and chat_type.is_group(chat_type):
|
||||||
user_label = _get_user_label(users, actioner)
|
user_label = get_user_label(users, actioner)
|
||||||
label = f"{user_label} {label}"
|
label = f"{user_label} {label}"
|
||||||
|
|
||||||
return label
|
return label
|
||||||
|
|
Loading…
Reference in a new issue