示例#1
0
文件: models.py 项目: chux0519/tg
 def update_msg_content_opened(self, chat_id: int, msg_id: int) -> None:
     msg = self.msgs[chat_id].get(msg_id)
     if not msg:
         return
     msg_proxy = MsgProxy(msg)
     if msg_proxy.content_type == "voice":
         msg_proxy.is_listened = True
     elif msg_proxy.content_type == "recording":
         msg_proxy.is_viewed = True
示例#2
0
文件: models.py 项目: ArthurBais/tg
 def update_msg_content_opened(self, chat_id: int, msg_id: int) -> None:
     index = self.msg_idx[chat_id].get(msg_id)
     if not index:
         return
     msg = MsgProxy(self.msgs[chat_id][index])
     if msg.content_type == "voice":
         msg.is_listened = True
     elif msg.content_type == "recording":
         msg.is_viewed = True
示例#3
0
文件: models.py 项目: moneytech/tg-1
 def update_msg_content_opened(self, chat_id: int, msg_id: int):
     for message in self.msgs[chat_id]:
         if message["id"] != msg_id:
             continue
         msg = MsgProxy(message)
         if msg.content_type == "voice":
             msg.is_listened = True
         elif msg.content_type == "recording":
             msg.is_viewed = True
         # TODO: start the TTL timer for self-destructing messages
         # that is the last case to implement
         # https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1update_message_content_opened.html
         return
示例#4
0
 def open_url(self) -> None:
     msg = MsgProxy(self.model.current_msg)
     if not msg.is_text:
         return self.present_error("Does not contain urls")
     text = msg["content"]["text"]["text"]
     urls = []
     for entity in msg["content"]["text"]["entities"]:
         _type = entity["type"]["@type"]
         if _type == "textEntityTypeUrl":
             offset = entity["offset"]
             length = entity["length"]
             url = text[offset:offset + length]
         elif _type == "textEntityTypeTextUrl":
             url = entity["type"]["url"]
         else:
             continue
         urls.append(url)
     if not urls:
         return self.present_error("No url to open")
     if len(urls) == 1:
         with suspend(self.view) as s:
             s.call(
                 config.DEFAULT_OPEN.format(file_path=shlex.quote(urls[0])))
         return
     with suspend(self.view) as s:
         s.run_with_input(config.URL_VIEW, "\n".join(urls))
示例#5
0
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 or msg_id is None:
        log.warning("Can't find information about file with file_id=%s",
                    file_id)
        return
    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)
示例#6
0
文件: views.py 项目: chux0519/tg
def get_last_msg(chat: Dict[str, Any],
                 users: UserModel) -> Tuple[Optional[int], str]:
    last_msg = chat.get("last_message")
    if not last_msg:
        return None, "<No messages yet>"
    return (
        last_msg["sender"].get("user_id"),
        parse_content(MsgProxy(last_msg), users),
    )
示例#7
0
def update_file(controller: Controller, update):
    log.info("update_file: %s", update)
    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:
        log.warning("Can't find information about file with file_id=%s",
                    file_id)
        return
    msgs = controller.model.msgs.msgs[chat_id]
    for msg in msgs:
        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
示例#8
0
 def download_current_file(self) -> None:
     msg = MsgProxy(self.model.current_msg)
     log.debug("Downloading msg: %s", msg.msg)
     file_id = msg.file_id
     if not file_id:
         self.present_info("File can't be downloaded")
         return
     self.download(file_id, msg["chat_id"], msg["id"])
     self.present_info("File started downloading")
示例#9
0
    def _toggle_select_msg(self) -> None:
        chat_id = self.model.chats.id_by_index(self.model.current_chat)
        if not chat_id:
            return
        msg = MsgProxy(self.model.current_msg)

        if msg.msg_id in self.model.selected[chat_id]:
            self.model.selected[chat_id].remove(msg.msg_id)
        else:
            self.model.selected[chat_id].append(msg.msg_id)
示例#10
0
def update_new_message(controller: Controller, update: Dict[str, Any]) -> None:
    msg = MsgProxy(update["message"])
    controller.model.msgs.add_message(msg.chat_id, msg.msg)
    current_chat_id = controller.model.current_chat_id
    if current_chat_id == msg.chat_id:
        controller.render_msgs()
    if msg.file_id and msg.size and msg.size <= max_download_size:
        controller.download(msg.file_id, msg.chat_id, msg["id"])

    controller.notify_for_message(msg.chat_id, msg)
示例#11
0
    def show_user_info(self) -> None:
        """Show user profile"""
        msg = MsgProxy(self.model.current_msg)
        user_id = msg.sender_id
        info = self.model.get_user_info(user_id)

        with suspend(self.view) as s:
            s.run_with_input(
                config.VIEW_TEXT_CMD,
                "\n".join(f"{k}: {v}" for k, v in info.items() if v),
            )
示例#12
0
 def jump_to_reply_msg(self) -> None:
     chat_id = self.model.chats.id_by_index(self.model.current_chat)
     if not chat_id:
         return
     msg = MsgProxy(self.model.current_msg)
     if not msg.reply_msg_id:
         return self.present_error("This msg does not reply")
     if not self.model.msgs.jump_to_msg_by_id(chat_id, msg.reply_msg_id):
         return self.present_error(
             "Can't jump to reply msg: it's not preloaded or deleted")
     return self.render_msgs()
示例#13
0
 def open_msg_with_cmd(self) -> None:
     """Open msg or file with cmd: less %s"""
     msg = MsgProxy(self.model.current_msg)
     cmd = self.view.status.get_input()
     if not cmd:
         return
     if "%s" not in cmd:
         return self.present_error(
             "command should contain <%s> which will be replaced by file path"
         )
     return self._open_msg(msg, cmd)
示例#14
0
文件: views.py 项目: moneytech/tg-1
 def _format_reply_msg(self, chat_id: int, msg: str, reply_to: int,
                       width_limit: int) -> str:
     reply_msg = MsgProxy(self.msg_model.get_message(chat_id, reply_to))
     if reply_msg_content := self._parse_msg(reply_msg):
         reply_msg_content = reply_msg_content.replace("\n", " ")
         reply_sender = self._get_user_by_id(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:
             reply_line = f"{reply_line[:width_limit - 4]}..."
         msg = f"{reply_line}\n{msg}"
示例#15
0
文件: views.py 项目: chux0519/tg
 def _format_reply_msg(self, chat_id: int, msg: str, reply_to: int,
                       width_limit: int) -> str:
     _msg = self.model.msgs.get_message(chat_id, reply_to)
     if not _msg:
         return msg
     reply_msg = MsgProxy(_msg)
     if reply_msg_content := self._parse_msg(reply_msg):
         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:
             reply_line = f"{reply_line[:width_limit - 4]}..."
         msg = f"{reply_line}\n{msg}"
示例#16
0
文件: models.py 项目: chux0519/tg
 def copy_files(self, chat_id: int, msg_ids: List[int],
                dest_dir: str) -> bool:
     is_copied = False
     for msg_id in msg_ids:
         _msg = self.msgs.get_message(chat_id, msg_id)
         if not _msg:
             return False
         msg = MsgProxy(_msg)
         if msg.file_id and msg.local_path:
             file_path = msg.local_path
             shutil.copy2(file_path, dest_dir)
             is_copied = True
     return is_copied
示例#17
0
文件: models.py 项目: moneytech/tg-1
    def copy_msgs_text(self):
        """Copies current msg text or path to file if it's file"""
        buffer = []

        from_chat_id, msg_ids = self.copied_msgs
        if not msg_ids:
            return False
        for msg_id in msg_ids:
            msg = MsgProxy(self.msgs.get_message(from_chat_id, msg_id))
            if msg.file_id:
                buffer.append(msg.local_path)
            elif msg.is_text:
                buffer.append(msg.text_content)
        copy_to_clipboard("\n".join(buffer))
示例#18
0
文件: controllers.py 项目: Crax97/tg
 def reply_with_long_message(self):
     if not self.can_send_msg():
         self.present_info("Can't send msg in this chat")
         return
     chat_id = self.model.current_chat_id
     reply_to_msg = self.model.current_msg_id
     msg = MsgProxy(self.model.current_msg)
     with NamedTemporaryFile("w+", suffix=".txt") as f, suspend(
         self.view
     ) as s:
         f.write(insert_replied_msg(msg))
         f.seek(0)
         s.call(config.LONG_MSG_CMD.format(file_path=shlex.quote(f.name)))
         with open(f.name) as f:
             if msg := strip_replied_msg(f.read().strip()):
                 self.tg.reply_message(chat_id, reply_to_msg, msg)
                 self.present_info("Message sent")
             else:
示例#19
0
    def edit_msg(self) -> None:
        msg = MsgProxy(self.model.current_msg)
        log.info("Editing msg: %s", msg.msg)
        if not self.model.is_me(msg.sender_id):
            return self.present_error("You can edit only your messages!")
        if not msg.is_text:
            return self.present_error("You can edit text messages only!")
        if not msg.can_be_edited:
            return self.present_error("Meessage can't be edited!")

        with NamedTemporaryFile("r+",
                                suffix=".txt") as f, suspend(self.view) as s:
            f.write(msg.text_content)
            f.flush()
            s.call(f"{config.EDITOR} {f.name}")
            with open(f.name) as f:
                if text := f.read().strip():
                    self.model.edit_message(text=text)
                    self.present_info("Message edited")
示例#20
0
文件: controllers.py 项目: Crax97/tg
    def open_current_msg(self):
        msg = MsgProxy(self.model.current_msg)
        if msg.is_text:
            with NamedTemporaryFile("w", suffix=".txt") as f:
                f.write(msg.text_content)
                f.flush()
                with suspend(self.view) as s:
                    s.open_file(f.name)
            return

        path = msg.local_path
        if not path:
            self.present_info("File should be downloaded first")
            return
        chat_id = self.model.chats.id_by_index(self.model.current_chat)
        if not chat_id:
            return
        self.tg.open_message_content(chat_id, msg.msg_id)
        with suspend(self.view) as s:
            s.open_file(path)
示例#21
0
文件: views.py 项目: Crax97/tg
def parse_content(content: Dict[str, Any]) -> str:
    msg = MsgProxy({"content": content})
    if msg.is_text:
        return content["text"]["text"]

    if not msg.content_type:
        # not implemented
        _type = content["@type"]
        return f"[{_type}]"

    fields = dict(
        name=msg.file_name,
        download=get_download(msg.local, msg.size),
        size=msg.human_size,
        duration=msg.duration,
        listened=format_bool(msg.is_listened),
        viewed=format_bool(msg.is_viewed),
    )
    info = ", ".join(f"{k}={v}" for k, v in fields.items() if v)

    return f"[{msg.content_type}: {info}]"
示例#22
0
文件: views.py 项目: ArthurBais/tg
def parse_content(content: Dict[str, Any]) -> str:
    msg = MsgProxy({"content": content})
    if msg.is_text:
        return content["text"]["text"].replace("\n", " ")

    _type = content["@type"]

    if _type == "messageBasicGroupChatCreate":
        return "[created the group]"
    if _type == "messageChatAddMembers":
        return "[joined the group]"

    if not msg.content_type:
        # not implemented
        return f"[{_type}]"

    content_text = ""
    if msg.is_poll:
        content_text = f"\n {msg.poll_question}"
        for option in msg.poll_options:
            content_text += f"\n * {option['voter_count']} ({option['vote_percentage']}%) | {option['text']}"

    fields = dict(
        name=msg.file_name,
        download=get_download(msg.local, msg.size),
        size=msg.human_size,
        duration=msg.duration,
        listened=format_bool(msg.is_listened),
        viewed=format_bool(msg.is_viewed),
        animated=msg.is_animated,
        emoji=msg.sticker_emoji,
        closed=msg.is_closed_poll,
    )
    info = ", ".join(f"{k}={v}" for k, v in fields.items() if v is not None)

    return f"[{msg.content_type}: {info}]{content_text}"
示例#23
0
文件: views.py 项目: Crax97/tg
    def _collect_msgs_to_draw(
        self,
        current_msg_idx: int,
        msgs: List[Tuple[int, Dict[str, Any]]],
        min_msg_padding: int,
    ) -> List[Tuple[Tuple[str, ...], bool, int]]:
        """
        Tries to collect list of messages that will satisfy `min_msg_padding`
        theshold. Long messages could prevent other messages from displaying on
        the screen. In order to prevent scenario when *selected* message moved
        out from the visible area of the screen by some long messages, this
        function will remove message one by one from the start until selected
        message could be visible on the screen.
        """
        selected_item_idx: Optional[int] = None
        collected_items: List[Tuple[Tuple[str, ...], bool, int]] = []
        for ignore_before in range(len(msgs)):
            if selected_item_idx is not None:
                break
            collected_items = []
            line_num = self.h
            for msg_idx, msg_item in msgs[ignore_before:]:
                is_selected_msg = current_msg_idx == msg_idx
                msg_proxy = MsgProxy(msg_item)
                dt = msg_proxy.date.strftime("%H:%M:%S")
                user_id_item = msg_proxy.sender_id

                user_id = self._get_user_by_id(user_id_item)
                flags = self._get_flags(msg_proxy)
                if user_id and flags:
                    # if not channel add space between name and flags
                    flags = " " + flags
                label_elements = f" {dt} ", user_id, flags
                label_len = sum(len(e) for e in label_elements)

                msg = self._format_msg(
                    msg_proxy, user_id_item, width_limit=self.w - label_len - 1
                )
                elements = *label_elements, f" {msg}"
                needed_lines = 0
                for i, msg_line in enumerate(msg.split("\n")):
                    # count wide character utf-8 symbols that take > 1 bytes to
                    # print it causes invalid offset
                    emojies_count = sum(
                        map(len, emoji_pattern.findall(msg_line))
                    )
                    line_len = len(msg_line) + emojies_count
                    # first line cotains msg lable, e.g user name, date
                    if i == 0:
                        line_len += label_len

                    needed_lines += (line_len // self.w) + 1

                line_num -= needed_lines
                if line_num < 0:
                    tail_lines = needed_lines + line_num - 1
                    # try preview long message that did fit in the screen
                    if tail_lines > 0 and not is_selected_msg:
                        limit = self.w * tail_lines
                        tail_chatacters = len(msg) - limit - 3
                        elements = (
                            "",
                            "",
                            "",
                            f" ...{msg[tail_chatacters:]}",
                        )
                        collected_items.append((elements, is_selected_msg, 0))
                    break
                collected_items.append((elements, is_selected_msg, line_num))
                if is_selected_msg:
                    selected_item_idx = len(collected_items) - 1
            if (
                # ignore first and last msg
                selected_item_idx not in (0, len(msgs) - 1, None)
                and selected_item_idx is not None
                and len(collected_items) - 1 - selected_item_idx
                < min_msg_padding
            ):
                selected_item_idx = None

        return collected_items
示例#24
0
 def open_current_msg(self) -> None:
     """Open msg or file with cmd in mailcap"""
     msg = MsgProxy(self.model.current_msg)
     self._open_msg(msg)
示例#25
0
文件: models.py 项目: chux0519/tg
 def view_current_msg(self) -> None:
     msg = MsgProxy(self.current_msg)
     msg_id = msg["id"]
     if chat_id := self.chats.id_by_index(self.current_chat):
         self.tg.view_messages(chat_id, [msg_id])
示例#26
0
文件: models.py 项目: moneytech/tg-1
 def view_current_msg(self):
     chat_id = self.chats.id_by_index(self.current_chat)
     msg = MsgProxy(self.current_msg)
     msg_id = msg["id"]
     self.tg.view_messages(chat_id, [msg_id])