Implement creating new group and secret chat ( #150) (#156)

* 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:
Nameless 2020-07-22 19:10:41 +08:00 committed by GitHub
parent 8ea4873780
commit 50b93eb308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 53 deletions

View file

@ -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

View file

@ -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

View file

@ -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]:

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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