mirror of
https://github.com/paul-nameless/tg
synced 2025-02-16 10:38:23 +00:00
* Create new chat * Implement creating new group and secret chat, close secret chat when deleted to prevent showing again after restart * Update readme * Add doc strings, show error if fzf does not exist * Fix imports ordering
This commit is contained in:
parent
8ea4873780
commit
50b93eb308
7 changed files with 107 additions and 53 deletions
|
@ -39,8 +39,8 @@ To use tg, you'll need to have the following installed:
|
|||
|
||||
## Optional dependencies
|
||||
|
||||
- [terminal-notifier](https://github.com/julienXX/terminal-notifier) for Mac (used by default). You can change it to [dunst](https://github.com/dunst-project/dunst) for Linux or any other notifications program (see `NOTIFY_CMD` in configuration)
|
||||
- [ffmpeg](https://ffmpeg.org/) to record voice msgs and upload videos.
|
||||
- [terminal-notifier](https://github.com/julienXX/terminal-notifier) - for Mac (used by default). You can change it to [dunst](https://github.com/dunst-project/dunst) for Linux or any other notifications program (see `NOTIFY_CMD` in configuration)
|
||||
- [ffmpeg](https://ffmpeg.org/) - to record voice msgs and upload videos.
|
||||
- [tdlib](https://tdlib.github.io/td/build.html?language=Python) - in case of incompatibility with built in package.
|
||||
For example, macOS:
|
||||
```sh
|
||||
|
@ -52,9 +52,8 @@ To use tg, you'll need to have the following installed:
|
|||
```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`
|
||||
|
||||
- [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`
|
||||
- [fzf](https://github.com/junegunn/fzf) - to create groups and secret chats (used for single and multiple user selection)
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ MAX_DOWNLOAD_SIZE = "10MB"
|
|||
NOTIFY_CMD = "/usr/local/bin/terminal-notifier -title {title} -subtitle {subtitle} -message {msg} -appIcon {icon_path}"
|
||||
|
||||
VIEW_TEXT_CMD = "less"
|
||||
FZF = "fzf"
|
||||
|
||||
if _os_name == _linux:
|
||||
# for more info see https://trac.ffmpeg.org/wiki/Capture/ALSA
|
||||
|
|
|
@ -24,7 +24,7 @@ from tg.utils import (
|
|||
notify,
|
||||
suspend,
|
||||
)
|
||||
from tg.views import View, get_user_label
|
||||
from tg.views import View
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -505,6 +505,45 @@ class Controller:
|
|||
self.model.edit_message(text=text)
|
||||
self.present_info("Message edited")
|
||||
|
||||
def _get_user_ids(self, is_multiple: bool = False) -> List[int]:
|
||||
users = self.model.users.get_users()
|
||||
_, cols = self.view.stdscr.getmaxyx()
|
||||
limit = min(int(cols / 2), max(len(user.name) for user in users),)
|
||||
users_out = "\n".join(
|
||||
f"{user.id}\t{user.name:<{limit}} | {user.status}"
|
||||
for user in sorted(users, key=lambda user: user.order)
|
||||
)
|
||||
cmd = config.FZF + " -n 2"
|
||||
if is_multiple:
|
||||
cmd += " -m"
|
||||
|
||||
with NamedTemporaryFile("r+") as tmp, suspend(self.view) as s:
|
||||
s.run_with_input(f"{cmd} > {tmp.name}", users_out)
|
||||
with open(tmp.name) as f:
|
||||
return [int(line.split()[0]) for line in f.readlines()]
|
||||
|
||||
@bind(chat_handler, ["ns"])
|
||||
def new_secret(self) -> None:
|
||||
"""Create new secret chat"""
|
||||
user_ids = self._get_user_ids()
|
||||
if not user_ids:
|
||||
return
|
||||
self.tg.create_new_secret_chat(user_ids[0])
|
||||
|
||||
@bind(chat_handler, ["ng"])
|
||||
def new_group(self) -> None:
|
||||
"""Create new group"""
|
||||
user_ids = self._get_user_ids(is_multiple=True)
|
||||
if not user_ids:
|
||||
return
|
||||
title = self.view.status.get_input("Group name: ")
|
||||
if title is None:
|
||||
return self.present_info("Cancelling creating group")
|
||||
if not title:
|
||||
return self.present_error("Group name should not be empty")
|
||||
|
||||
self.tg.create_new_basic_group_chat(user_ids, title)
|
||||
|
||||
@bind(chat_handler, ["dd"])
|
||||
def delete_chat(self) -> None:
|
||||
"""Leave group/channel or delete private/secret chats"""
|
||||
|
@ -544,6 +583,9 @@ class Controller:
|
|||
self.tg.delete_chat_history(
|
||||
chat["id"], remove_from_chat_list=True, revoke=is_revoke
|
||||
)
|
||||
if chat_type == ChatType.chatTypeSecret:
|
||||
self.tg.close_secret_chat(chat["type"]["secret_chat_id"])
|
||||
|
||||
self.present_info("Chat was deleted")
|
||||
|
||||
@bind(chat_handler, ["n"])
|
||||
|
@ -586,30 +628,7 @@ class Controller:
|
|||
|
||||
@bind(chat_handler, ["c"])
|
||||
def view_contacts(self) -> None:
|
||||
contacts = self.model.users.get_contacts()
|
||||
if contacts is None:
|
||||
return self.present_error("Can't get contacts")
|
||||
|
||||
total = contacts["total_count"]
|
||||
users = []
|
||||
for user_id in contacts["user_ids"]:
|
||||
user_name = get_user_label(self.model.users, user_id)
|
||||
status = self.model.users.get_status(user_id)
|
||||
order = self.model.users.get_user_status_order(user_id)
|
||||
users.append((user_name, status, order))
|
||||
|
||||
_, cols = self.view.stdscr.getmaxyx()
|
||||
limit = min(
|
||||
int(cols / 2), max(len(user_name) for user_name, *_ in users)
|
||||
)
|
||||
users_out = "\n".join(
|
||||
f"{user_name:<{limit}} | {status}"
|
||||
for user_name, status, _ in sorted(users, key=lambda it: it[2])
|
||||
)
|
||||
with suspend(self.view) as s:
|
||||
s.run_with_input(
|
||||
config.VIEW_TEXT_CMD, f"{total} users:\n" + users_out
|
||||
)
|
||||
self._get_user_ids()
|
||||
|
||||
@bind(chat_handler, ["l", "^J", "^E"])
|
||||
def handle_msgs(self) -> Optional[str]:
|
||||
|
|
31
tg/models.py
31
tg/models.py
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, namedtuple
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from tg.msg import MsgProxy
|
||||
|
@ -499,6 +499,9 @@ class MsgModel:
|
|||
log.info(f"message has been sent: {result.update}")
|
||||
|
||||
|
||||
User = namedtuple("User", ["id", "name", "status", "order"])
|
||||
|
||||
|
||||
class UserModel:
|
||||
|
||||
types = {
|
||||
|
@ -645,3 +648,29 @@ class UserModel:
|
|||
return None
|
||||
self.contacts = result.update
|
||||
return self.contacts
|
||||
|
||||
def get_user_label(self, user_id: int) -> str:
|
||||
if user_id == 0:
|
||||
return ""
|
||||
user = self.get_user(user_id)
|
||||
if user["first_name"] and user["last_name"]:
|
||||
return f'{user["first_name"]} {user["last_name"]}'[:20]
|
||||
|
||||
if user["first_name"]:
|
||||
return f'{user["first_name"]}'[:20]
|
||||
|
||||
if user.get("username"):
|
||||
return "@" + user["username"]
|
||||
return "<Unknown>"
|
||||
|
||||
def get_users(self) -> List[User]:
|
||||
contacts = self.get_contacts()
|
||||
if contacts is None:
|
||||
return []
|
||||
users = []
|
||||
for user_id in contacts["user_ids"]:
|
||||
user_name = self.get_user_label(user_id)
|
||||
status = self.get_status(user_id)
|
||||
order = self.get_user_status_order(user_id)
|
||||
users.append(User(user_id, user_name, status, order))
|
||||
return users
|
||||
|
|
17
tg/tdlib.py
17
tg/tdlib.py
|
@ -335,6 +335,23 @@ class Tdlib(Telegram):
|
|||
}
|
||||
return self._send_data(data)
|
||||
|
||||
def create_new_secret_chat(self, user_id: int) -> AsyncResult:
|
||||
data = {
|
||||
"@type": "createNewSecretChat",
|
||||
"user_id": user_id,
|
||||
}
|
||||
return self._send_data(data)
|
||||
|
||||
def create_new_basic_group_chat(
|
||||
self, user_ids: List[int], title: str
|
||||
) -> AsyncResult:
|
||||
data = {
|
||||
"@type": "createNewBasicGroupChat",
|
||||
"user_ids": user_ids,
|
||||
"title": title,
|
||||
}
|
||||
return self._send_data(data)
|
||||
|
||||
def delete_chat_history(
|
||||
self, chat_id: int, remove_from_chat_list: bool, revoke: bool = False
|
||||
) -> AsyncResult:
|
||||
|
|
|
@ -198,7 +198,11 @@ class suspend:
|
|||
return subprocess.run(cmd, shell=True)
|
||||
|
||||
def run_with_input(self, cmd: str, text: str) -> None:
|
||||
subprocess.run(cmd, universal_newlines=True, input=text, shell=True)
|
||||
proc = subprocess.run(
|
||||
cmd, universal_newlines=True, input=text, shell=True
|
||||
)
|
||||
if proc.returncode:
|
||||
input(f"Command <{cmd}> failed: press <enter> to continue")
|
||||
|
||||
def open_file(self, file_path: str, cmd: str = None) -> None:
|
||||
if cmd:
|
||||
|
|
27
tg/views.py
27
tg/views.py
|
@ -22,6 +22,8 @@ MULTICHAR_KEYBINDINGS = (
|
|||
"sa",
|
||||
"sv",
|
||||
"sn",
|
||||
"ns",
|
||||
"ng",
|
||||
"bp",
|
||||
)
|
||||
|
||||
|
@ -216,7 +218,7 @@ class ChatView:
|
|||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
user, last_msg = get_last_msg(chat)
|
||||
if user:
|
||||
last_msg_sender = get_user_label(self.model.users, user)
|
||||
last_msg_sender = self.model.users.get_user_label(user)
|
||||
chat_type = get_chat_type(chat)
|
||||
if chat_type and is_group(chat_type):
|
||||
return last_msg_sender, last_msg
|
||||
|
@ -323,9 +325,7 @@ class MsgView:
|
|||
return msg
|
||||
reply_msg = MsgProxy(_msg)
|
||||
if reply_msg_content := self._parse_msg(reply_msg):
|
||||
reply_sender = get_user_label(
|
||||
self.model.users, reply_msg.sender_id
|
||||
)
|
||||
reply_sender = self.model.users.get_user_label(reply_msg.sender_id)
|
||||
sender_name = f" {reply_sender}:" if reply_sender else ""
|
||||
reply_line = f">{sender_name} {reply_msg_content}"
|
||||
if len(reply_line) >= width_limit:
|
||||
|
@ -385,7 +385,7 @@ class MsgView:
|
|||
dt = msg_proxy.date.strftime("%H:%M:%S")
|
||||
user_id_item = msg_proxy.sender_id
|
||||
|
||||
user_id = get_user_label(self.model.users, user_id_item)
|
||||
user_id = self.model.users.get_user_label(user_id_item)
|
||||
flags = self._get_flags(msg_proxy)
|
||||
if user_id and flags:
|
||||
# if not channel add space between name and flags
|
||||
|
@ -612,28 +612,13 @@ def get_download(
|
|||
return "no"
|
||||
|
||||
|
||||
def get_user_label(users: UserModel, user_id: int) -> str:
|
||||
if user_id == 0:
|
||||
return ""
|
||||
user = users.get_user(user_id)
|
||||
if user["first_name"] and user["last_name"]:
|
||||
return f'{user["first_name"]} {user["last_name"]}'[:20]
|
||||
|
||||
if user["first_name"]:
|
||||
return f'{user["first_name"]}'[:20]
|
||||
|
||||
if user.get("username"):
|
||||
return "@" + user["username"]
|
||||
return "<Unknown>"
|
||||
|
||||
|
||||
def _get_action_label(users: UserModel, chat: Dict[str, Any]) -> Optional[str]:
|
||||
actioner, action = users.get_user_action(chat["id"])
|
||||
if actioner and action:
|
||||
label = f"{action}..."
|
||||
chat_type = get_chat_type(chat)
|
||||
if chat_type and is_group(chat_type):
|
||||
user_label = get_user_label(users, actioner)
|
||||
user_label = users.get_user_label(actioner)
|
||||
label = f"{user_label} {label}"
|
||||
|
||||
return label
|
||||
|
|
Loading…
Add table
Reference in a new issue