diff --git a/tg/config.py b/tg/config.py index e212b37..f8792d4 100644 --- a/tg/config.py +++ b/tg/config.py @@ -1,36 +1,46 @@ -import configparser -import mailcap -import mimetypes +""" +Every parameter (except for DEFAULT_CONFIG) can be +overwritten by external config file +""" import os +import platform +import runpy -DEFAULT_CONFIG = os.path.expanduser("~/.config/tg/tg.conf") +_os_name = platform.system() +_darwin = 'Darwin' +_linux = 'Linux' + + +DEFAULT_CONFIG = os.path.expanduser("~/.config/tg/conf.py") DEFAULT_FILES = os.path.expanduser("~/.cache/tg/") -max_download_size = "10MB" -record_cmd = "ffmpeg -f avfoundation -i ':0' -ar 22050 -b:a 32k '{file_path}'" -long_msg_cmd = "vim -c 'startinsert' {file_path}" -editor = os.environ.get("EDITOR", "vi") +LOG_LEVEL = "INFO" + +API_ID = '559815' +API_HASH = 'fd121358f59d764c57c55871aa0807ca' + +PHONE = None +ENC_KEY = None + +TDLIB_PATH = None +TDLIB_VERBOSITY = 0 + +MAX_DOWNLOAD_SIZE = "10MB" + +# TODO: check if darwin +NOTIFY_CMD = "/usr/local/bin/terminal-notifier -title '{title}' -subtitle '{subtitle}' -message '{msg}' -appIcon '{icon_path}'" +# TODO: check if darwin +RECORD_CMD = "ffmpeg -f avfoundation -i ':0' -ar 22050 -b:a 32k '{file_path}'" + +# TODO: use mailcap instead of editor +LONG_MSG_CMD = "vim -c 'startinsert' {file_path}" +EDITOR = os.environ.get("EDITOR", "vi") + +# TODO: if os == darwin else open-xdb? +DEFAULT_OPEN = "open '{file_path}'" if _os_name == _linux else "open '{file_path}'" -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 +if os.path.isfile(DEFAULT_CONFIG): + env_config_params = runpy.run_path(DEFAULT_CONFIG) + for param, value in env_config_params.items(): + if param.isupper(): + globals()[param] = value diff --git a/tg/controllers.py b/tg/controllers.py index c99b8e5..e366928 100644 --- a/tg/controllers.py +++ b/tg/controllers.py @@ -4,7 +4,6 @@ import os import threading from datetime import datetime from functools import partial -from signal import SIGWINCH, signal from tempfile import NamedTemporaryFile from typing import Any, Callable, Dict, Optional @@ -51,7 +50,6 @@ class Controller: self.lock = threading.Lock() self.tg = tg self.chat_size = 0.5 - signal(SIGWINCH, self.resize_handler) self.chat_bindings: Dict[str, key_bind_handler] = { "q": lambda _: "QUIT", @@ -62,18 +60,20 @@ class Controller: "^P": self.prev_chat, "J": lambda _: self.next_chat(10), "K": lambda _: self.prev_chat(10), - "gg": lambda _: self.first_chat, - "bp": lambda _: self.breakpoint, - "u": lambda _: self.toggle_unread, - "p": lambda _: self.toggle_pin, - "m": lambda _: self.toggle_mute, - "r": lambda _: self.read_msgs, + "gg": lambda _: self.first_chat(), + "bp": lambda _: self.breakpoint(), + "u": lambda _: self.toggle_unread(), + "p": lambda _: self.toggle_pin(), + "m": lambda _: self.toggle_mute(), + "r": lambda _: self.read_msgs(), } self.msg_bindings: Dict[str, key_bind_handler] = { "q": lambda _: "QUIT", "h": lambda _: "BACK", + "bp": lambda _: self.breakpoint(), "^D": lambda _: "BACK", + # navigate msgs "]": self.next_chat, "[": self.prev_chat, "J": lambda _: self.next_msg(10), @@ -82,18 +82,27 @@ class Controller: "^N": self.next_msg, "k": self.prev_msg, "^P": self.prev_msg, - "G": lambda _: self.jump_bottom, - "dd": lambda _: self.delete_msg, - "D": lambda _: self.download_current_file, - "l": lambda _: self.open_current_msg, + "G": lambda _: self.jump_bottom(), + # send files "sd": lambda _: self.send_file(self.tg.send_doc), "sp": lambda _: self.send_file(self.tg.send_photo), "sa": lambda _: self.send_file(self.tg.send_audio), - - " ": lambda _: self.toggle_select_msg, - "^[": lambda _: self.discard_selected_msgs, # esc - "y": lambda _: self.copy_msgs, - "p": lambda _: self.forward_msgs, + "sv": lambda _: self.send_video(), + "v": lambda _: self.send_voice(), + # manipulate msgs + "dd": lambda _: self.delete_msg(), + "D": lambda _: self.download_current_file(), + "l": lambda _: self.open_current_msg(), + "e": lambda _: self.edit_msg(), + "i": lambda _: self.write_short_msg(), + "a": lambda _: self.write_short_msg(), + "I": lambda _: self.write_long_msg(), + "A": lambda _: self.write_long_msg(), + "p": lambda _: self.forward_msgs(), + "y": lambda _: self.copy_msgs(), + # message selection + " ": lambda _: self.toggle_select_msg(), + "^[": lambda _: self.discard_selected_msgs(), # esc } def forward_msgs(self, _: int): diff --git a/tg/main.py b/tg/main.py index 385e4e4..304aea3 100644 --- a/tg/main.py +++ b/tg/main.py @@ -14,14 +14,34 @@ from tg.views import ChatView, MsgView, StatusView, View log = logging.getLogger(__name__) -def run(tg: Tdlib, stdscr: window) -> None: - # run this function in thread? +def main(stdscr: window) -> None: + utils.setup_log(config.LOG_LEVEL) + tg = Tdlib( + api_id=config.API_ID, + api_hash=config.API_HASH, + phone=config.PHONE, + database_encryption_key=config.ENC_KEY, + files_directory=config.DEFAULT_FILES, + tdlib_verbosity=config.TDLIB_VERBOSITY, + library_path=config.TDLIB_PATH, + ) + tg.login() + + # handle ctrl+c, to avoid interrupting tg when subprocess is called + def interrupt_signal_handler(sig, frame): + # TODO: draw on status pane: to quite press + log.info("Interrupt signal is handled and ignored on purpose") + signal.signal(signal.SIGINT, interrupt_signal_handler) + model = Model(tg) status_view = StatusView(stdscr) msg_view = MsgView(stdscr, model.msgs, model, model.users) chat_view = ChatView(stdscr) view = View(stdscr, chat_view, msg_view, status_view) controller = Controller(model, view, tg) + # hanlde resize of terminal correctly + signal.signal(signal.SIGWINCH, controller.resize_handler) + for msg_type, handler in update_handlers.handlers.items(): tg.add_update_handler(msg_type, partial(handler, controller)) @@ -30,33 +50,5 @@ def run(tg: Tdlib, stdscr: window) -> None: t.join() -def main(): - def signal_handler(sig, frame): - log.info("You pressed Ctrl+C!") - - signal.signal(signal.SIGINT, signal_handler) - - cfg = config.get_cfg()["DEFAULT"] - utils.setup_log(cfg.get("level", "DEBUG")) - log.debug("#" * 64) - tg = Tdlib( - api_id=cfg["api_id"], - api_hash=cfg["api_hash"], - phone=cfg["phone"], - 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"), - ) - config.max_download_size = utils.parse_size( - cfg.get("max_download_size", config.max_download_size) - ) - config.record_cmd = cfg.get("record_cmd", config.record_cmd) - config.long_msg_cmd = cfg.get("long_msg_cmd", config.long_msg_cmd) - tg.login() - - wrapper(partial(run, tg)) - - if __name__ == "__main__": - main() + wrapper(main()) diff --git a/tg/utils.py b/tg/utils.py index f360409..878645c 100644 --- a/tg/utils.py +++ b/tg/utils.py @@ -1,7 +1,9 @@ import base64 import curses import logging +import mailcap import math +import mimetypes import os import random import re @@ -30,6 +32,17 @@ emoji_pattern = re.compile( units = {"B": 1, "KB": 10 ** 3, "MB": 10 ** 6, "GB": 10 ** 9, "TB": 10 ** 12} +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 + + def parse_size(size): if size[-2].isalpha(): number, unit = size[:-2], size[-2:] @@ -110,7 +123,7 @@ def notify( msg, subtitle="", title="tg", - cmd=config.get_cfg()["DEFAULT"].get("notify_cmd"), + cmd=config.NOTIFY_CMD, ): if not cmd: return @@ -146,7 +159,7 @@ class suspend: subprocess.call(cmd, shell=True) def open_file(self, file_path): - cmd = config.get_file_handler(file_path) + cmd = get_file_handler(file_path) if not cmd: return self.call(cmd)