File picker (#136)

* File picker

* Add docstring to show in help

* Update doc

* Fix update_file: invalid get msg

* Catch ValueError when removing messages

* Ask to whether to upload file as document or compressed

* Clear status after sending file
This commit is contained in:
Nameless 2020-07-15 18:50:35 +08:00 committed by GitHub
parent 39a1e59f35
commit b37bba8fb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 89 additions and 38 deletions

View file

@ -75,6 +75,7 @@ yay -S telegram-tg-git
```ini ```ini
image/webp; mpv %s 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 ## Configuration
@ -165,6 +166,9 @@ USERS_COLORS = tuple(range(2, 16))
# cleanup cache # cleanup cache
# Values: N days, None (never) # Values: N days, None (never)
KEEP_MEDIA = 7 KEEP_MEDIA = 7
FILE_PICKER_CMD = "ranger --choosefile={file_path}"
# FILE_PICKER_CMD = "nnn -p {file_path}"
``` ```
### Mailcap file ### Mailcap file

View file

@ -71,6 +71,8 @@ USERS_COLORS = tuple(range(2, 16))
KEEP_MEDIA = 7 KEEP_MEDIA = 7
FILE_PICKER_CMD = "ranger --choosefile={file_path}"
if os.path.isfile(CONFIG_FILE): if os.path.isfile(CONFIG_FILE):
config_params = runpy.run_path(CONFIG_FILE) config_params = runpy.run_path(CONFIG_FILE)

View file

@ -15,6 +15,7 @@ from tg.msg import MsgProxy
from tg.tdlib import ChatAction, Tdlib from tg.tdlib import ChatAction, Tdlib
from tg.utils import ( from tg.utils import (
get_duration, get_duration,
get_mime,
get_video_resolution, get_video_resolution,
get_waveform, get_waveform,
is_yes, is_yes,
@ -299,18 +300,6 @@ class Controller:
) )
self.present_info("Message wasn't sent") 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"]) @bind(msg_handler, ["dd"])
def delete_msgs(self) -> None: def delete_msgs(self) -> None:
is_deleted = self.model.delete_msgs() is_deleted = self.model.delete_msgs()
@ -320,18 +309,70 @@ class Controller:
return return
self.present_info("Message deleted") 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"]) @bind(msg_handler, ["sd"])
def send_document(self) -> None: def send_document(self) -> None:
"""Enter file path and send uncompressed"""
self.send_file(self.tg.send_doc) self.send_file(self.tg.send_doc)
@bind(msg_handler, ["sp"]) @bind(msg_handler, ["sp"])
def send_picture(self) -> None: def send_picture(self) -> None:
"""Enter file path and send compressed image"""
self.send_file(self.tg.send_photo) self.send_file(self.tg.send_photo)
@bind(msg_handler, ["sa"]) @bind(msg_handler, ["sa"])
def send_audio(self) -> None: def send_audio(self) -> None:
"""Enter file path and send as audio"""
self.send_file(self.tg.send_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( def send_file(
self, send_file_fun: Callable[[str, int], AsyncResult], self, send_file_fun: Callable[[str, int], AsyncResult],
) -> None: ) -> None:
@ -627,8 +668,14 @@ class Controller:
self.queue.put(self._render) self.queue.put(self._render)
def _render(self) -> None: def _render(self) -> None:
self.render_chats() self._render_chats()
self.render_msgs() 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() self.view.status.draw()
def render_chats(self) -> None: def render_chats(self) -> None:

View file

@ -362,7 +362,10 @@ class MsgModel:
def remove_messages(self, chat_id: int, msg_ids: List[int]) -> None: def remove_messages(self, chat_id: int, msg_ids: List[int]) -> None:
log.info(f"removing msg {msg_ids=}") log.info(f"removing msg {msg_ids=}")
for msg_id in 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) self.msgs[chat_id].pop(msg_id, None)
def add_message(self, chat_id: int, msg: Dict[str, Any]) -> None: def add_message(self, chat_id: int, msg: Dict[str, Any]) -> None:

View file

@ -229,20 +229,19 @@ def update_file(controller: Controller, update: Dict[str, Any]) -> None:
file_id = update["file"]["id"] file_id = update["file"]["id"]
local = update["file"]["local"] local = update["file"]["local"]
chat_id, msg_id = controller.model.downloads.get(file_id, (None, None)) 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( log.warning(
"Can't find information about file with file_id=%s", file_id "Can't find information about file with file_id=%s", file_id
) )
return return
msgs = controller.model.msgs.msgs[chat_id] msg = controller.model.msgs.msgs[chat_id].get(msg_id)
for msg_id, msg in msgs.items(): if not msg:
if msg["id"] == msg_id: return
proxy = MsgProxy(msg) proxy = MsgProxy(msg)
proxy.local = local proxy.local = local
controller.render_msgs() controller.render_msgs()
if proxy.is_downloaded: if proxy.is_downloaded:
controller.model.downloads.pop(file_id) controller.model.downloads.pop(file_id)
break
@update_handler("updateMessageContentOpened") @update_handler("updateMessageContentOpened")

View file

@ -72,6 +72,13 @@ def setup_log() -> None:
logging.captureWarnings(True) 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: def get_file_handler(file_path: str) -> str:
mtype, _ = mimetypes.guess_type(file_path) mtype, _ = mimetypes.guess_type(file_path)
if not mtype: if not mtype:

View file

@ -5,16 +5,7 @@ from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple, Union, cast from typing import Any, Dict, List, Optional, Tuple, Union, cast
from tg import config from tg import config
from tg.colors import ( from tg.colors import bold, cyan, get_color, magenta, reverse, white, yellow
blue,
bold,
cyan,
get_color,
magenta,
reverse,
white,
yellow,
)
from tg.models import Model, UserModel from tg.models import Model, UserModel
from tg.msg import MsgProxy from tg.msg import MsgProxy
from tg.tdlib import ChatType, get_chat_type from tg.tdlib import ChatType, get_chat_type
@ -98,10 +89,8 @@ class StatusView:
self.win.resize(self.h, self.w) self.win.resize(self.h, self.w)
self.win.mvwin(self.y, self.x) self.win.mvwin(self.y, self.x)
def draw(self, msg: Optional[str] = None) -> None: def draw(self, msg: str = "") -> None:
self.win.clear() self.win.clear()
if not msg:
return
self.win.addstr(0, 0, msg.replace("\n", " ")[: self.w]) self.win.addstr(0, 0, msg.replace("\n", " ")[: self.w])
self._refresh() self._refresh()