mirror of
https://github.com/paul-nameless/tg
synced 2024-11-22 03:43:19 +00:00
Add users status in chat flags
Rotate logs Add ability to configure flags representation
This commit is contained in:
parent
468950bc6a
commit
0151e8df6a
8 changed files with 99 additions and 26 deletions
26
README.md
26
README.md
|
@ -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": "💩",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
34
tg/models.py
34
tg/models.py
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
33
tg/views.py
33
tg/views.py
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue