Add users status in chat flags

Rotate logs
Add ability to configure flags representation
This commit is contained in:
Paul Nameless 2020-06-11 18:13:42 +08:00
parent 468950bc6a
commit 0151e8df6a
8 changed files with 99 additions and 26 deletions

View file

@ -1,8 +1,6 @@
# tg
Terminal telegram client.
(!) usable but still in development
Telegram terminal client.
## Features
@ -55,6 +53,7 @@ docker run -it --rm tg
```sh
brew install tdlib
```
and then set in config `TDLIB_PATH`
- `python3.8`
- `pip3 install python-telegram` - dependency for running from sources
- `terminal-notifier` or other program for notifications (see configuration)
@ -69,7 +68,6 @@ Config file should be stored at `~/.config/tg/conf.py`. This is simple python fi
```python
PHONE = "[your phone number]"
ENC_KEY = "[telegram db encryption key]"
```
### Advanced configuration:
@ -86,6 +84,7 @@ def get_pass(key):
PHONE = get_pass("i/telegram-phone")
# encrypt you local tdlib database with the key
ENC_KEY = get_pass("i/telegram-enc-key")
# log level for debugging
@ -107,6 +106,25 @@ NOTIFY_CMD = '/usr/local/bin/terminal-notifier -title "{title}" -subtitle "{subt
# The voice note must be encoded with the Opus codec, and stored inside an OGG
# container. Voice notes can have only a single audio channel.
VOICE_RECORD_CMD = "ffmpeg -f avfoundation -i ':0' -c:a libopus -b:a 32k '{file_path}'"
# You can customize chat and msg flags however you want.
# By default words will be used for readability, but you can make
# it as simple as one letter flags like in mutt or add emojies
CHAT_FLAGS = {
"online": "●",
"pinned": "P",
"muted": "M",
"unread": "U",
}
MSG_FLAGS = {
"selected": "*",
"forwarded": "F",
"new": "N",
"unseen": "U",
"edited": "E",
"pending": "...",
"failed": "💩",
}
```

View file

@ -5,6 +5,7 @@ overwritten by external config file
import os
import platform
import runpy
from typing import Dict
_os_name = platform.system()
_darwin = "Darwin"
@ -55,6 +56,9 @@ if _os_name == _linux:
else:
COPY_CMD = "pbcopy"
CHAT_FLAGS: Dict[str, str] = {}
MSG_FLAGS: Dict[str, str] = {}
if os.path.isfile(CONFIG_FILE):
config_params = runpy.run_path(CONFIG_FILE)

View file

@ -477,6 +477,14 @@ class Controller:
self.queue.put(self._render)
def _render(self) -> None:
self.render_chats()
self.render_msgs()
self.view.status.draw()
def render_chats(self) -> None:
self.queue.put(self._render_chats)
def _render_chats(self) -> None:
page_size = self.view.chats.h
chats = self.model.get_chats(
self.model.current_chat, page_size, MSGS_LEFT_SCROLL_THRESHOLD
@ -484,10 +492,7 @@ class Controller:
selected_chat = min(
self.model.current_chat, page_size - MSGS_LEFT_SCROLL_THRESHOLD
)
self.view.chats.draw(selected_chat, chats)
self.render_msgs()
self.view.status.draw()
def render_msgs(self) -> None:
self.queue.put(self._render_msgs)

View file

@ -26,7 +26,7 @@ def run(tg: Tdlib, stdscr: window) -> None:
model = Model(tg)
status_view = StatusView(stdscr)
msg_view = MsgView(stdscr, model.msgs, model, model.users)
chat_view = ChatView(stdscr)
chat_view = ChatView(stdscr, model.users)
view = View(stdscr, chat_view, msg_view, status_view)
controller = Controller(model, view, tg)

View file

@ -1,4 +1,5 @@
import logging
import time
from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple
@ -431,6 +432,23 @@ class MsgModel:
class UserModel:
statuses = {
"userStatusEmpty": "",
"userStatusOnline": "online",
"userStatusOffline": "offline",
"userStatusRecently": "recently",
"userStatusLastWeek": "last week",
"userStatusLastMonth": "last month",
}
types = {
"userTypeUnknown": "unknown",
"userTypeBot": "bot",
"userTypeDeleted": "deleted",
"userTypeRegular": "regular",
}
def __init__(self, tg: Tdlib) -> None:
self.tg = tg
self.me = None
@ -447,6 +465,22 @@ class UserModel:
self.me = result.update
return self.me
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 is_online(self, user_id: int):
user = self.get_user(user_id)
if (
user
and user["type"]["@type"] != "userTypeBot"
and user["status"]["@type"] == "userStatusOnline"
and user["status"]["expires"] > time.time()
):
return True
return False
def get_user(self, user_id: int) -> Dict[str, Any]:
if user_id in self.users:
return self.users[user_id]

View file

@ -263,3 +263,9 @@ def update_connection_state(controller: Controller, update: Dict[str, Any]):
}
msg = states.get(state, "Unknown state")
controller.present_info(msg)
@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()

View file

@ -18,7 +18,6 @@ from typing import Optional
from tg import config
log = logging.getLogger(__name__)
emoji_pattern = re.compile(
"["
"\U0001F600-\U0001F64F" # emoticons
@ -51,12 +50,16 @@ def setup_log():
for level, filename in zip(
(config.LOG_LEVEL, logging.ERROR), ("all.log", "error.log"),
):
handler = logging.FileHandler(os.path.join(config.LOG_PATH, filename))
handler = logging.handlers.RotatingFileHandler(
os.path.join(config.LOG_PATH, filename),
maxBytes=parse_size("32MB"),
backupCount=1,
)
handler.setLevel(level)
handlers.append(handler)
logging.basicConfig(
format="%(levelname)-8s [%(asctime)s] %(name)s %(message).1000s",
format="%(levelname)-8s [%(asctime)s] %(name)s %(message)s",
handlers=handlers,
)
logging.getLogger().setLevel(config.LOG_LEVEL)

View file

@ -4,6 +4,7 @@ from _curses import window # type: ignore
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.msg import MsgProxy
@ -126,12 +127,13 @@ class StatusView:
class ChatView:
def __init__(self, stdscr: window, p: float = 0.5) -> None:
def __init__(self, stdscr: window, users: UserModel, p: float = 0.5):
self.stdscr = stdscr
self.h = 0
self.w = 0
self.win = stdscr.subwin(self.h, self.w, 0, 0)
self._refresh = self.win.refresh
self.users = users
def resize(self, rows: int, cols: int, p: float = 0.25) -> None:
self.h = rows - 1
@ -193,33 +195,34 @@ class ChatView:
i, offset, last_msg, self._msg_color(is_selected)
)
if left_label := self._get_chat_label(
unread_count, is_pinned, chat
):
if flags := self._get_flags(unread_count, is_pinned, chat):
self.win.addstr(
i,
self.w - len(left_label) - 1,
left_label,
self.w - len(flags) - 1,
flags,
self._unread_color(is_selected),
)
self._refresh()
@staticmethod
def _get_chat_label(
unread_count: int, is_pinned: bool, chat: Dict[str, Any]
def _get_flags(
self, unread_count: int, is_pinned: bool, chat: Dict[str, Any]
) -> str:
labels = []
flags = []
if self.users.is_online(chat["id"]):
flags.append("online")
if is_pinned:
labels.append("pinned")
flags.append("pinned")
if chat["notification_settings"]["mute_for"]:
labels.append("muted")
flags.append("muted")
if unread_count:
labels.append(str(unread_count))
flags.append(str(unread_count))
label = " ".join(labels)
label = " ".join(config.CHAT_FLAGS.get(flag, flag) for flag in flags)
if label:
return f" {label}"
return label
@ -285,7 +288,7 @@ class MsgView:
if not flags:
return ""
return " ".join(flags)
return " ".join(config.MSG_FLAGS.get(flag, flag) for flag in flags)
def _format_reply_msg(
self, chat_id: int, msg: str, reply_to: int, width_limit: int