diff --git a/tg/config.py b/tg/config.py new file mode 100644 index 0000000..458ce26 --- /dev/null +++ b/tg/config.py @@ -0,0 +1,32 @@ +import os +import configparser +import mailcap +import mimetypes + +DEFAULT_CONFIG = os.path.expanduser("~/.config/tg/tg.conf") +DEFAULT_FILES = os.path.expanduser("~/.cache/tg/") + + +def get_cfg(config=DEFAULT_CONFIG): + cfg = configparser.ConfigParser() + cfg.read(config) + return cfg + + +def save_cfg(cfg, config=DEFAULT_CONFIG): + config_dir = os.path.dirname(config) + if not os.path.isdir(config_dir): + os.makedirs(config_dir) + with open(config, "w") as f: + cfg.write(f) + + +def get_file_handler(file_name, default=None): + mtype, _ = mimetypes.guess_type(file_name) + if not mtype: + return default + caps = mailcap.getcaps() + handler, view = mailcap.findmatch(caps, mtype, filename=file_name) + if not handler: + return None + return handler diff --git a/tg/controllers/__init__.py b/tg/controllers/__init__.py index c74a25a..b739889 100644 --- a/tg/controllers/__init__.py +++ b/tg/controllers/__init__.py @@ -54,20 +54,18 @@ class Controller: log.info("Open msg: %s", msg.msg) if msg.is_text: text = msg["content"]["text"]["text"] - with NamedTemporaryFile("w") as f: + with NamedTemporaryFile("w", suffix=".txt") as f: f.write(text) f.flush() with suspend(self.view) as s: - s.call(["less", f.name]) + s.run(f.name) return path = msg.local_path if path: - # handle with mimetype and mailcap - # if multiple entries in mailcap, open fzf to choose with suspend(self.view) as s: log.info("Opening file: %s", path) - s.call(["open", path]) + s.run(path) def handle_msgs(self) -> str: # set width to 0.25, move window to left @@ -200,12 +198,24 @@ class Controller: @handle_exception def update_new_msg(self, update): - chat_id = update["message"]["chat_id"] - self.model.msgs.add_message(chat_id, update["message"]) + msg = update["message"] + chat_id = msg["chat_id"] + self.model.msgs.add_message(chat_id, msg) self.refresh_msgs() - if not update.get("disable_notification"): - if update["message"]["content"] == "text": - notify(update["message"]["content"]["text"]["text"]) + + # notify + user_id = msg["sender_user_id"] + if msg["sender_user_id"] == self.model.get_me()["id"]: + return + user = self.model.users.get_user(user_id) + name = "{} {}".format(user["first_name"], user["last_name"]) + _type = msg["content"]["@type"] + + if _type == "messageText": + text = msg["content"]["text"]["text"] + else: + text = MsgProxy.types.get(_type, "") + notify(text, title=name) @handle_exception def update_chat_last_msg(self, update): @@ -218,7 +228,7 @@ class Controller: # though need to make sure that creatinng index is atomic operation # requires locks for read, until index and chats will be the same for i, chat in enumerate(self.model.chats.chats): - if chat['id'] == current_chat_id: + if chat["id"] == current_chat_id: self.model.current_chat = i break self.refresh_chats() diff --git a/tg/main.py b/tg/main.py index ab6c397..c131a29 100644 --- a/tg/main.py +++ b/tg/main.py @@ -1,6 +1,5 @@ import logging import logging.handlers -import os import threading from curses import wrapper, window from functools import partial @@ -10,23 +9,10 @@ from telegram.client import Telegram from tg.controllers import Controller from tg.models import Model from tg.views import View +from tg import config, utils -logging.basicConfig( - level=os.getenv("LOG_LEVEL", "DEBUG"), - format="%(asctime)s %(levelname)s %(message)s", - handlers=[ - logging.handlers.RotatingFileHandler( - "./tg.log", backupCount=1, maxBytes=1024 * 256 - ), - ], -) log = logging.getLogger(__name__) -API_ID = os.getenv("API_ID") -API_HASH = os.getenv("API_HASH") -PHONE = os.getenv("PHONE") -if PHONE is None: - raise Exception("Environment variables did not provided") def run(tg: Telegram, stdscr: window) -> None: @@ -61,16 +47,17 @@ class TelegramApi(Telegram): def main(): + cfg = config.get_cfg()["DEFAULT"] + utils.setup_log(cfg.get("level", "DEBUG")) log.debug("#" * 64) tg = TelegramApi( - api_id=API_ID, - api_hash=API_HASH, - phone=PHONE, - database_encryption_key="changeme1234", - files_directory=os.path.expanduser("~/.cache/tg/"), - tdlib_verbosity=0, - # TODO: add in config - library_path="/usr/local/Cellar/tdlib/1.6.0/lib/libtdjson.dylib", + api_id=cfg["api_id"], + api_hash=cfg["api_hash"], + phone=cfg["login"], + database_encryption_key=cfg["enc_key"], + files_directory=cfg.get("files", config.DEFAULT_FILES), + tdlib_verbosity=cfg.get("tdlib_verbosity", 0), + library_path=cfg.get("library_path"), ) tg.login() wrapper(partial(run, tg)) diff --git a/tg/models/__init__.py b/tg/models/__init__.py index 6d03357..cd01a6b 100644 --- a/tg/models/__init__.py +++ b/tg/models/__init__.py @@ -38,6 +38,7 @@ class Model: if chat_id is None: return {} current_msg = self.msgs.current_msgs[chat_id] + log.info("current-msg: %s", current_msg) return self.msgs.msgs[chat_id][current_msg] def jump_bottom(self): @@ -228,6 +229,11 @@ class MsgModel: log.info(f"adding {msg_id=} {message}") self.msgs[chat_id].append(message) msg_set.add(msg_id) + + self.msgs[chat_id] = sorted( + self.msgs[chat_id], key=lambda d: d["id"], reverse=True + ) + return True def add_messages(self, chat_id: int, messages: Any) -> bool: @@ -276,7 +282,7 @@ class MsgModel: messages = self._fetch_msgs_until_limit(chat_id, offset, limit) self.add_messages(chat_id, messages) - return sorted(self.msgs[chat_id], key=lambda d: d["id"])[::-1][ + return sorted(self.msgs[chat_id], key=lambda d: d["id"], reverse=True)[ offset:limit ] diff --git a/tg/utils.py b/tg/utils.py index ee78306..2adbcfb 100644 --- a/tg/utils.py +++ b/tg/utils.py @@ -4,6 +4,7 @@ from typing import Optional import subprocess from functools import wraps import curses +from tg import config log = logging.getLogger(__name__) @@ -15,17 +16,32 @@ def num(value: str, default: Optional[int] = None) -> Optional[int]: return default -def notify(msg, subtitle="New message", title="Telegram"): - msg = "-message {!r}".format(msg) - subtitle = "-subtitle {!r}".format(subtitle) - title = "-title {!r}".format(title) - sound = "-sound default" - icon_path = os.path.join(os.path.dirname(__file__), "tg.png") - icon = f"-appIcon {icon_path}" - cmd = "/usr/local/bin/terminal-notifier" +def setup_log(level="DEBUG"): + logging.basicConfig( + level=level, + format="%(asctime)s %(levelname)s %(message)s", + handlers=[ + logging.handlers.RotatingFileHandler( + "./tg.log", backupCount=1, maxBytes=1024 * 1024 + ), + ], + ) - log.debug("####: %s", f"{cmd} {icon} {sound} {title} {subtitle} {msg}") - os.system(f"{cmd} {icon} {sound} {title} {subtitle} {msg}") + +def notify( + msg, + subtitle="", + title="tg", + cmd=config.get_cfg()["DEFAULT"].get("notify_cmd"), +): + if not cmd: + return + icon_path = os.path.join(os.path.dirname(__file__), "tg.png") + notify_cmd = cmd.format( + icon_path=icon_path, title=title, subtitle=subtitle, msg=msg + ) + log.info("notify-cmd: %s", notify_cmd) + os.system(notify_cmd) def handle_exception(fun): @@ -46,6 +62,12 @@ class suspend: def call(self, *args, **kwargs): subprocess.call(*args, **kwargs) + def run(self, file_path): + cmd = config.get_file_handler(file_path) + if not cmd: + return + subprocess.call(cmd, shell=True) + def __enter__(self): curses.endwin() return self