diff --git a/readme.md b/readme.md index ac313cd..6040e53 100644 --- a/readme.md +++ b/readme.md @@ -75,6 +75,7 @@ yay -S telegram-tg-git ```ini image/webp; mpv %s ``` +- [ranger](https://github.com/ranger/ranger), [nnn](https://github.com/jarun/nnn) - can be used to choose file when sending, customizable with `FILE_PICKER_CMD` ## Configuration @@ -165,6 +166,9 @@ USERS_COLORS = tuple(range(2, 16)) # cleanup cache # Values: N days, None (never) KEEP_MEDIA = 7 + +FILE_PICKER_CMD = "ranger --choosefile={file_path}" +# FILE_PICKER_CMD = "nnn -p {file_path}" ``` ### Mailcap file diff --git a/tg/config.py b/tg/config.py index 2ada323..5f60c0c 100644 --- a/tg/config.py +++ b/tg/config.py @@ -71,6 +71,8 @@ USERS_COLORS = tuple(range(2, 16)) KEEP_MEDIA = 7 +FILE_PICKER_CMD = "ranger --choosefile={file_path}" + if os.path.isfile(CONFIG_FILE): config_params = runpy.run_path(CONFIG_FILE) diff --git a/tg/controllers.py b/tg/controllers.py index 971b20f..7ca446b 100644 --- a/tg/controllers.py +++ b/tg/controllers.py @@ -15,6 +15,7 @@ from tg.msg import MsgProxy from tg.tdlib import ChatAction, Tdlib from tg.utils import ( get_duration, + get_mime, get_video_resolution, get_waveform, is_yes, @@ -299,18 +300,6 @@ class Controller: ) self.present_info("Message wasn't sent") - @bind(msg_handler, ["sv"]) - def send_video(self) -> None: - file_path = self.view.status.get_input() - if not file_path or not os.path.isfile(file_path): - return - chat_id = self.model.chats.id_by_index(self.model.current_chat) - if not chat_id: - return - width, height = get_video_resolution(file_path) - duration = get_duration(file_path) - self.tg.send_video(file_path, chat_id, width, height, duration) - @bind(msg_handler, ["dd"]) def delete_msgs(self) -> None: is_deleted = self.model.delete_msgs() @@ -320,18 +309,70 @@ class Controller: return self.present_info("Message deleted") + @bind(msg_handler, ["S"]) + def choose_and_send_file(self) -> None: + """Call file picker and send chosen file based on mimetype""" + chat_id = self.model.chats.id_by_index(self.model.current_chat) + file_path = None + if not chat_id: + return self.present_error("No chat selected") + try: + with NamedTemporaryFile("w") as f, suspend(self.view) as s: + s.call(config.FILE_PICKER_CMD.format(file_path=f.name)) + with open(f.name) as f: + file_path = f.read().strip() + except FileNotFoundError: + pass + if not file_path or not os.path.isfile(file_path): + return self.present_error("No file was selected") + mime_map = { + "image": self.tg.send_photo, + "audio": self.tg.send_audio, + "video": self._send_video, + } + mime = get_mime(file_path) + if mime in ("image", "video"): + resp = self.view.status.get_input( + f"Upload <{file_path}> compressed?[Y/n]" + ) + self.render_status() + if not is_yes(resp): + mime = "" + + fun = mime_map.get(mime, self.tg.send_doc) + fun(file_path, chat_id) + @bind(msg_handler, ["sd"]) def send_document(self) -> None: + """Enter file path and send uncompressed""" self.send_file(self.tg.send_doc) @bind(msg_handler, ["sp"]) def send_picture(self) -> None: + """Enter file path and send compressed image""" self.send_file(self.tg.send_photo) @bind(msg_handler, ["sa"]) def send_audio(self) -> None: + """Enter file path and send as audio""" self.send_file(self.tg.send_audio) + @bind(msg_handler, ["sv"]) + def send_video(self) -> None: + """Enter file path and send compressed video""" + file_path = self.view.status.get_input() + if not file_path or not os.path.isfile(file_path): + return + chat_id = self.model.chats.id_by_index(self.model.current_chat) + if not chat_id: + return + self._send_video(file_path, chat_id) + + def _send_video(self, file_path: str, chat_id: int) -> None: + width, height = get_video_resolution(file_path) + duration = get_duration(file_path) + self.tg.send_video(file_path, chat_id, width, height, duration) + def send_file( self, send_file_fun: Callable[[str, int], AsyncResult], ) -> None: @@ -627,8 +668,14 @@ class Controller: self.queue.put(self._render) def _render(self) -> None: - self.render_chats() - self.render_msgs() + self._render_chats() + self._render_msgs() + self._render_status() + + def render_status(self) -> None: + self.queue.put(self._render_status) + + def _render_status(self) -> None: self.view.status.draw() def render_chats(self) -> None: diff --git a/tg/models.py b/tg/models.py index ec7622b..5c575c1 100644 --- a/tg/models.py +++ b/tg/models.py @@ -362,7 +362,10 @@ class MsgModel: def remove_messages(self, chat_id: int, msg_ids: List[int]) -> None: log.info(f"removing msg {msg_ids=}") for msg_id in msg_ids: - self.msg_ids[chat_id].remove(msg_id) + try: + self.msg_ids[chat_id].remove(msg_id) + except ValueError: + pass self.msgs[chat_id].pop(msg_id, None) def add_message(self, chat_id: int, msg: Dict[str, Any]) -> None: diff --git a/tg/update_handlers.py b/tg/update_handlers.py index f8cc5e4..fa42c0a 100644 --- a/tg/update_handlers.py +++ b/tg/update_handlers.py @@ -229,20 +229,19 @@ def update_file(controller: Controller, update: Dict[str, Any]) -> None: file_id = update["file"]["id"] local = update["file"]["local"] chat_id, msg_id = controller.model.downloads.get(file_id, (None, None)) - if chat_id is None: + if chat_id is None or msg_id is None: log.warning( "Can't find information about file with file_id=%s", file_id ) return - msgs = controller.model.msgs.msgs[chat_id] - for msg_id, msg in msgs.items(): - if msg["id"] == msg_id: - proxy = MsgProxy(msg) - proxy.local = local - controller.render_msgs() - if proxy.is_downloaded: - controller.model.downloads.pop(file_id) - break + msg = controller.model.msgs.msgs[chat_id].get(msg_id) + if not msg: + return + proxy = MsgProxy(msg) + proxy.local = local + controller.render_msgs() + if proxy.is_downloaded: + controller.model.downloads.pop(file_id) @update_handler("updateMessageContentOpened") diff --git a/tg/utils.py b/tg/utils.py index e7fefdc..f58bec0 100644 --- a/tg/utils.py +++ b/tg/utils.py @@ -72,6 +72,13 @@ def setup_log() -> None: logging.captureWarnings(True) +def get_mime(file_path: str) -> str: + mtype, _ = mimetypes.guess_type(file_path) + if not mtype: + return "" + return mtype.split("/")[0] + + def get_file_handler(file_path: str) -> str: mtype, _ = mimetypes.guess_type(file_path) if not mtype: diff --git a/tg/views.py b/tg/views.py index da50386..600f7c7 100644 --- a/tg/views.py +++ b/tg/views.py @@ -5,16 +5,7 @@ from datetime import datetime from typing import Any, Dict, List, Optional, Tuple, Union, cast from tg import config -from tg.colors import ( - blue, - bold, - cyan, - get_color, - magenta, - reverse, - white, - yellow, -) +from tg.colors import bold, cyan, get_color, magenta, reverse, white, yellow from tg.models import Model, UserModel from tg.msg import MsgProxy from tg.tdlib import ChatType, get_chat_type @@ -98,10 +89,8 @@ class StatusView: self.win.resize(self.h, self.w) self.win.mvwin(self.y, self.x) - def draw(self, msg: Optional[str] = None) -> None: + def draw(self, msg: str = "") -> None: self.win.clear() - if not msg: - return self.win.addstr(0, 0, msg.replace("\n", " ")[: self.w]) self._refresh()