(分享)telega 的一些自定义

该贴中的内容针对但不限于 telega v0.8.163 (TDLib v1.8.20-7a6d6cf) (telega-server v0.8.2) 版本的 telega。也许后面会因为适配 TDLib 部分内容失效。

首先是针对 telega 的 reply 以及 forward 样式的修改,修改的目的除了更加美观以外,也是为了可以显示更多的被回复内容。

(defmacro lucius/telega-ins--aux-inline-reply (&rest body)
  `(telega-ins--aux-inline
       "➦" 'telega-msg-inline-reply
     ,@body))

(defun lucius/telega-ins--msg-reply-inline (msg)
  "For message MSG insert reply header in case MSG is replying to some message."
  (when-let ((reply-to (plist-get msg :reply_to)))
    (cl-ecase (telega--tl-type reply-to)
      (messageReplyToMessage
       ;; NOTE: Do not show reply inline if replying to thread's root
       ;; message.  If replied message is not instantly available, it
       ;; will be fetched later by the
       ;; `telega-msg--replied-message-fetch'
       (unless (eq (plist-get telega-chatbuf--thread-msg :id)
                   (telega--tl-get msg :reply_to :message_id))
         (let ((replied-msg (telega-msg--replied-message msg)))
           (cond ((or (null replied-msg) (eq replied-msg 'loading))
                  ;; NOTE: replied message will be fetched by the
                  ;; `telega-msg--replied-message-fetch'
                  (lucius/telega-ins--aux-inline-reply
                   (telega-ins-i18n "lng_profile_loading")))
                 ((telega--tl-error-p replied-msg)
                  (lucius/telega-ins--aux-inline-reply
                   (telega-ins--with-face 'telega-shadow
                     (telega-ins (telega-i18n "lng_deleted_message")))))
                 ((telega-msg-match-p replied-msg 'ignored)
                  (lucius/telega-ins--aux-inline-reply
                   (telega-ins--message-ignored replied-msg)))
                 (t
                  (telega-ins--with-props
                      ;; When pressed, then jump to the REPLIED-MSG message
                      (list 'action
                            (lambda (_button)
                              (telega-msg-goto-highlight replied-msg)))
                    (lucius/telega-ins--aux-inline-reply
                     (telega-ins--aux-msg-one-line replied-msg
                       :with-username t
                       :username-face
                       (let* ((sender (telega-msg-sender replied-msg))
                              (faces (telega-msg-sender-title-faces sender)))
                         (if (and (telega-sender-match-p sender 'me)
                                  (plist-get msg :contains_unread_mention))
                             (append faces '(telega-entity-type-mention))
                           faces))))
                    ))))))

      (messageReplyToStory
       ;; NOTE: If replied story is not instantly available, it will
       ;; be fetched later by the `telega-msg--replied-story-fetch'
       (let ((replied-story (telega-msg--replied-story msg)))
         (cond ((or (null replied-story) (eq replied-story 'loading))
                ;; NOTE: replied story will be fetched by the
                ;; `telega-msg--replied-story-fetch'
                (lucius/telega-ins--aux-inline-reply
                 (telega-ins-i18n "lng_profile_loading")))
               ((or (telega--tl-error-p replied-story)
                    (telega-story-deleted-p replied-story))
                (lucius/telega-ins--aux-inline-reply
                 (telega-ins--with-face 'telega-shadow
                   (telega-ins (telega-i18n "lng_deleted_story")))))
               (t
                (telega-ins--with-props
                    ;; When pressed, open the replied story
                    (list 'action
                          (lambda (_button)
                            (telega-story-open replied-story msg)))
                  (lucius/telega-ins--aux-inline-reply
                   (telega-ins--my-story-one-line replied-story msg))
                  )))))
      )))

(defun lucius/telega-ins--fwd-info-inline (fwd-info)
  "Insert forward info FWD-INFO as one liner."
  (when fwd-info
    (telega-ins--with-props
        ;; When pressed, then jump to original message or show info
        ;; about original sender
        (list 'action
              (lambda (_button) (telega--fwd-info-action fwd-info))
              'help-echo "RET to goto original message")
      (telega-ins--with-attrs  (list :max (- telega-chat-fill-column
                                             (telega-current-column))
                                     :elide t
                                     :elide-trail 8
                                     :face 'telega-msg-inline-forward)
        ;; | Forwarded From:
        (telega-ins "| " "➥: ")
        (let* ((origin (plist-get fwd-info :origin))
               (sender nil)
               (from-chat-id (plist-get fwd-info :from_chat_id))
               (from-chat (when (and from-chat-id (not (zerop from-chat-id)))
                            (telega-chat-get from-chat-id))))
          ;; Insert forward origin first
          (cl-ecase (telega--tl-type origin)
            (messageForwardOriginChat
             (setq sender (telega-chat-get (plist-get origin :sender_chat_id)))
             (telega-ins--msg-sender sender
               :with-avatar-p t
               :with-username-p t
               :with-brackets-p t))

            (messageForwardOriginUser
             (setq sender (telega-user-get (plist-get origin :sender_user_id)))
             (telega-ins--msg-sender sender
               :with-avatar-p t
               :with-username-p t
               :with-brackets-p t))

            ((messageForwardOriginHiddenUser messageForwardOriginMessageImport)
             (telega-ins (telega-tl-str origin :sender_name)))

            (messageForwardOriginChannel
             (setq sender (telega-chat-get (plist-get origin :chat_id)))
             (telega-ins--msg-sender sender
               :with-avatar-p t
               :with-username-p t
               :with-brackets-p t)))

          (when-let ((signature (telega-tl-str origin :author_signature)))
            (telega-ins " --" signature))

          (when (and from-chat
                     (not (or (eq sender from-chat)
                              (and (telega-user-p sender)
                                   (eq sender (telega-chat-user from-chat))))))
            (telega-ins "→")
            (if telega-chat-show-avatars
                (telega-ins--image
                 (telega-msg-sender-avatar-image-one-line from-chat))
              (telega-ins--msg-sender from-chat
                :with-avatar-p t
                :with-username-p t
                :with-brackets-p t))))

        (let ((date (plist-get fwd-info :date)))
          (unless (zerop date)
            (telega-ins " " (telega-i18n "lng_schedule_at") " ")
            (telega-ins--date date)))
        (when telega-msg-heading-whole-line
          (telega-ins "\n")))
      (unless telega-msg-heading-whole-line
        (telega-ins "\n")))
    t))
    ;; ;; 修改 [| In reply to: ] 为 [| ➦: ]
    ;; ;; 因为这个 fwd-info 是个闭包,如果想在 elisp 里用闭包必须开词法作用域
    (advice-add 'telega-ins--msg-reply-inline :override #'lucius/telega-ins--msg-reply-inline)
    ;; 修改 [| Forward from: ] 为 [| ➥: ]
    (advice-add 'telega-ins--fwd-info-inline :override #'lucius/telega-ins--fwd-info-inline)

图中可以看到修改后的样子,另外为了显示更多的被回复内容,针对被回复人的用户名太长,我也用 psearch 替换做了缩略。

;; 用户名过长时,在 Reply 中省略部分。
    (psearch-patch telega-ins--aux-msg-one-line
      (psearch-replace
       '`(let ((sender (telega-msg-sender msg))) ,a)
       '`(let ((sender (telega-msg-sender msg)))
           (telega-ins--with-attrs
               (list :max (/ telega-chat-fill-column 3) :elide t)
             (telega-ins
              (or (telega-msg-sender-username sender 'with-Q)
                  (telega-msg-sender-title sender)))))))

另外,telega 的 msg-heading 是一整行,默认的背景灰色,是为了区别于 msg 本身,但是没有文字的区域也是灰色显得很难看。也用 psearch 做了修改。

(psearch-patch telega-ins--message-header
      (psearch-replace '`(if telega-msg-heading-whole-line ,a ,b)
                       '`(if telega-msg-heading-whole-line ,a)))

图中 msg-heading 有个很淡的背景,可以看出来背景长度与用户名长度保持一致。

BTW: psearch 可以查看这个帖子 psearch: 基于 pcase 的 elisp 代码搜索工具

9 个赞

另外对于一些 face 也做了自定义,针对的是 ef-spring 主题做了一些与之相配的修改。

(set-face-attribute 'telega-msg-heading nil
                        :inherit nil
                        :background "#EBF4EC"
                        :weight 'bold)
    (set-face-attribute 'telega-msg-inline-reply nil
                        :inherit nil
                        :foreground "#86C166") ;; 苗 NAE
    (set-face-attribute 'telega-msg-inline-forward nil
                        :inherit nil
                        :foreground "#FFB11B")
    (set-face-attribute 'telega-entity-type-mention nil
                        :underline '(:style line)
                        :weight 'bold)
    (set-face-attribute 'telega-msg-self-title nil
                        :foreground "#E2943B" ;; 朽葉 KUCHIBA
                        :italic t
                        :weight 'bold)
    (set-face-attribute 'telega-msg-user-title nil :italic t)
    (set-face-attribute 'telega-button nil
                        :foreground "#986DB2"
                        :box '(:line-width (-2 . -2)
                               :color "#986DB2"
                               :style nil))
    (set-face-attribute 'telega-button-active nil
                        :foreground "#ffffff"
                        :background "#986DB2")
    ;; 未读提示
    (set-face-attribute 'telega-unmuted-count nil
                        :foreground "#FFB11B"
                        :weight 'bold)
    (set-face-attribute 'telega-mention-count nil
                        :foreground "#FE6DB3")
    (set-face-attribute 'telega-muted-count nil
                        :foreground "#86C166"
                        :weight 'bold)

后续应该会把这部份设置挪到自己 fork 的主题当中去。

1 个赞

接下来是别人的一些努力成功,拿来用了。 比如 @stanley_1ab 贡献的 telega 插件 telega-bridge-bot 可以在 telega 中同步 Matrix 的头像,重新排布 username。下面是前后效果图。

应用前:

应用后: image

下面是我的配置

(setq telega-bridge-bot-matrix-user "@lucius_chen:matrix.org"
             telega-bridge-bot-bridge-info-plist
             ;; @emacs_china
             ;; telega 中在 Telega Root 对应的群组上 i 键查看
             '(-1001773572820
               ;; @matrix_t2bot
               ;; 同样的方式查看 bot 信息获得
               (420415423
                ;; Room Settings -> Advanced -> Internal room ID
                (:chat-id "!EGzPXoyqkJdTByDCjD:mozilla.org" :type :matrix))
               -1001478915941                ; @vimzh_real
               (5296957089                   ; @nichi_matrix_bot
                (:chat-id "!2KhbxzkrlqGS6zMD:nichi.co" :type :matrix))
               -1001480067069                ; @keyboard_cn
               (420415423                    ; @matrix_t2bot
                (:chat-id "!EGzPXoyqkJdTByDCjD:mozilla.org" :type :matrix))
               -1001154313178                ; @coder_ot
               (6332621450                   ; @yamatrix_bridge_bot
                (:chat-id "!hYCtHBRcjEMzEgnBOE:matrix.org" :type :matrix))
               -1001873425044                ; @Emacs_CN Lite
               (420415423
                (:chat-id "!rWYkGlkTdVlOsniLSh:matrix.org" :type :matrix))))

接下来还是 @stanley_1ab 开发的防止头像裂开的 patch。 如果你用的是 elpa 的话,执行下面这个 elisp,然后重启 emacs 就行了

     (let ((telega-path (package-desc-dir (package-get-descriptor
     'telega))))
       (shell-command (concat "cd " telega-path " && " "curl
       https://github.com/zevlg/telega.el/commit/e1c9de039c1cf20048fb22b740175917d17f9110.patch
       | patch -p1"))
       (byte-recompile-directory telega-path 0))

非 elpa 用户请自行前往代码中的 patch 连接 副作用是 @replies 里面的头像会裂开。

嫌麻烦的人也可以直接复制以下的同名函数进行覆盖就可以了

;; avatar
(defcustom telega-avatar-slice-2-raise 0.5
  "Raise of the second slice of the avatar.
Setting this value to a higher number will avoid gaps between avatar,
but increase the distance between username and text body."
  :package-version '(telega . "0.8.163")
  :type '(choice 'center integer)
  :type 'integer
  :group 'telega)

(defun telega-ins--ascent-percent (string)
  "Find the max of the fonts descent in STRING and convert it to ascent percent.
If STRING is empty or can't find the telega current buffer frame,
then return \\='center."
  (if-let* ((buffer (or telega--current-buffer (current-buffer)))
            (window (get-buffer-window buffer))
            (frame (window-frame window))
            (default-font (face-font 'default frame))
            (img-xheight (aref (font-info default-font frame) 3))
            (max-descent (aref (font-info default-font frame) 9)))
      (progn
        (dotimes (i (length string))
          (when-let* ((font (font-at i window string))
                      (descent (aref (font-info font frame) 9))
                      (max? (> descent max-descent)))
            (setq max-descent descent)))
        (if (= max-descent -1)
            'center
          (round (* 100 (- 1 (/ (float max-descent) img-xheight))))))
    'center))

(defun telega-ins--image (img &optional slice-num &rest props)
  "Insert image IMG generated by telega.
Uses internal `:telega-text' to keep correct column.
If SLICE-NUM is specified, then insert single slice.
SLICE-NUM can be a list in form (SLICE-NUM SLICE-Y SLICE-H).

Special property `:no-display-if' is supported in PROPS to
ommit image display if value is for this property is non-nil.

Property `:image-ascent' is used to specify image ascent.
It is useful to adjust the position of the sliced avatar.

Property `:image-raise' is used to specify display raise.
It is useful to adjust the position of the sliced avatar."
  ;; NOTE: IMG might be nil if `telega-use-images' is nil
  ;; See https://github.com/zevlg/telega.el/issues/274
  (if (not img)
      (telega-ins "<IMAGE>")

    ;; NOTE: do not check SLICE-NUM
    (let ((slice (cond ((numberp slice-num)
                        (list 0 (telega-chars-xheight slice-num)
                              1.0 (telega-chars-xheight 1)))
                       ((listp slice-num)
                        (prog1
                            (list 0 (nth 1 slice-num)
                                  1.0 (nth 2 slice-num))
                          (setq slice-num (nth 0 slice-num))))
                       (slice-num
                        (error "Invalid slice-num: %S" slice-num)))))
      (telega-ins--with-props
          (nconc (list 'rear-nonsticky '(display))
                 (unless (plist-get props :no-display-if)
                   (when-let ((ascent (plist-get props :image-ascent)))
                     (setf (image-property img :ascent) ascent))
                   (let ((display-specs (list img))
                         (image-raise (plist-get props :image-raise)))
                     (when slice
                       (push (cons 'slice slice) display-specs))
                     (when image-raise
                       (push `(raise ,image-raise) display-specs))
                     (list 'display display-specs)))
                 props)
        (telega-ins
         (or (plist-get props :telega-text)
             (telega-image--telega-text img slice-num)
             ;; Otherwise use slow `image-size' to get correct
             ;; `:telega-text'
             (make-string (telega-chars-in-width
                           (or (plist-get (cdr img) :width)
                               (progn
                                 (telega-debug "WARN: `image-size' used for %S" img)
                                 (cl-assert img)
                                 (car (image-size img t (telega-x-frame))))))
                          ?X)))))))
(defun telega-ins--user (user &optional member show-phone-p)
  "Insert USER, aligning multiple lines at current column.
MEMBER specifies corresponding \"ChatMember\" object.
If SHOW-PHONE-P is non-nil, then show USER's phone number."
  (let ((avatar (telega-msg-sender-avatar-image user))
        (username (if (telega-user-p user)
                      (telega-user-title user 'full-name)
                    (cl-assert (telega-chat-p user))
                    (telega-chat-title user)))
        (off-column (telega-current-column)))
    (telega-ins--image avatar 0
                       :image-ascent (telega-ins--ascent-percent username)
                       :no-display-if (not telega-user-show-avatars))
    (telega-ins--msg-sender user :with-username-p 'telega-username)
    (telega-ins--with-face 'telega-shadow
      (when (and member
                 (telega-ins-prefix " ("
                   (telega-ins--chat-member-status
                    (plist-get member :status))))
        (telega-ins ")")))

    (when show-phone-p
      (when-let ((phone-number (telega-tl-str user :phone_number)))
        (telega-ins--with-face 'telega-shadow
          (telega-ins " • "))
        (telega-ins "+" phone-number)))

    ;; Insert (him)in<-->out(me) relationship
    (when (and telega-user-show-relationship
               (not (telega-me-p user)))
      (telega-ins " ")
      (telega-ins--user-relationship user))
    ;; Block/scam mark, without requesting
    (when (telega-user-match-p user 'is-blocked)
      (telega-ins " " (telega-symbol 'blocked)))

    (telega-ins "\n")
    (telega-ins (make-string off-column ?\s))
    (telega-ins--image avatar 1
                       :no-display-if (not telega-user-show-avatars))
    ;; Setup `off-column' for "invited by" string
    (setq off-column (telega-current-column))
    (telega-ins--user-status user)

    (when-let ((join-date (plist-get member :joined_chat_date)))
      (unless (zerop join-date)
        (telega-ins ", joined ")
        (telega-ins--date join-date)))

    (telega-ins-prefix ", "
      (telega-ins--user-nearby-distance user))

    (when-let* ((inviter-id (plist-get member :inviter_user_id))
                (inviter-user (unless (zerop inviter-id)
                                (telega-user-get inviter-id 'local))))
      (telega-ins "\n")
      (telega-ins (make-string off-column ?\s))
      (telega-ins "invited by ")
      (telega-ins--raw-button (telega-link-props 'user inviter-id 'type 'telega)
        (telega-ins--msg-sender inviter-user :with-avatar-p t)))
    t))

(defun telega-ins--message0 (msg &optional no-header
                                   addon-header-inserter no-footer)
  "Insert message MSG.
If NO-HEADER is non-nil, then do not display message header
unless message is edited.
ADDON-HEADER-INSERTER is passed directly to `telega-ins--message-header'."
  (declare (indent 2))
  (if (telega-msg-special-p msg)
      (telega-ins--with-attrs (list :min (- telega-chat-fill-column
                                            (telega-current-column))
                                    :align 'center
                                    :align-symbol 'horizontal-bar)
        (telega-ins--content msg))

    ;; Message header needed
    (let* ((chat (telega-msg-chat msg))
           ;; Is formatting done for "Replies" chat?
           ;; Workaround for case when `:forward_info' is unset (for
           ;; outgoing messages [what?] for example)
           (msg-for-replies-p (and (telega-replies-p chat)
                                   (plist-get msg :forward_info)))
           (sender (if msg-for-replies-p
                       (telega-replies-msg-sender msg)
                     (telega-msg-sender msg)))
           (sender-name (if (telega-user-p sender)
                            (telega-user-title sender 'full-name)
                          (cl-assert (telega-chat-p sender))
                          (telega-chat-title sender)))
           (avatar (if msg-for-replies-p
                       (telega-msg-sender-avatar-image-three-lines sender)
                     (telega-msg-sender-avatar-image sender)))
           (awidth (length (telega-image--telega-text avatar 0)))
           ;; NOTE: `telega-msg-contains-unread-mention' is used
           ;; inside `telega--entity-to-properties'
           (telega-msg-contains-unread-mention
            (plist-get msg :contains_unread_mention))
           ccol)
      (if (and no-header
               (zerop (plist-get msg :edit_date))
               (zerop (plist-get msg :via_bot_user_id)))
          (telega-ins (make-string awidth ?\s))

        ;; Show user profile when clicked on avatar, header
        (telega-ins--with-props
            (list 'action (lambda (button)
                            ;; NOTE: check for custom message :action first
                            ;; - [RESEND] button uses :action
                            ;; - via @bot link uses :action
                            (or (telega-button--action button)
                                (telega-describe-msg-sender sender))))
          (telega-ins--image avatar 0
                             :image-ascent (telega-ins--ascent-percent sender-name)
                             :no-display-if (not telega-chat-show-avatars))
          (telega-ins--message-header msg chat sender addon-header-inserter)
          (telega-ins--image avatar 1
                             :image-raise telega-avatar-slice-2-raise
                             :no-display-if (not telega-chat-show-avatars))))

      (setq ccol (telega-current-column))
      (telega-ins--fwd-info-inline (plist-get msg :forward_info))
      ;; NOTE: Three lines avatars in "Replies" chat
      (when msg-for-replies-p
        (telega-ins--image avatar 2
                           :no-display-if (not telega-chat-show-avatars)))
      (when (< (telega-current-column) ccol)
        (telega-ins--move-to-column ccol))
      (when (< (telega-current-column) ccol)
        (telega-ins--move-to-column ccol))
      (telega-ins--msg-reply-inline msg)

      (telega-ins--column ccol telega-chat-fill-column
        (telega-ins--content msg)

        (telega-ins-prefix "\n"
          (telega-ins--msg-sending-state-failed msg))
        (when (telega-msg-match-p msg telega-msg-temex-show-reactions)
          (telega-ins-prefix "\n"
            (telega-ins--msg-reaction-list msg)))
        (telega-ins-prefix "\n"
          (telega-ins--reply-markup msg))
        (telega-ins-prefix "\n"
          (telega-ins--msg-comments msg chat))
        )))

  (unless no-footer
    ;; Footer: Date/status starts at `telega-chat-fill-column' column
    (telega-ins--move-to-column telega-chat-fill-column)
    (telega-ins--with-attrs (list :align 'right :min 10)
      ;; NOTE: telegaInternal messages has no `:date' property
      (when-let ((date (or (telega--tl-get msg :scheduling_state :send_date)
                           (plist-get msg :date))))
        (telega-ins--date date)))
    (telega-ins--outgoing-status msg))
  t)

telega 中的长链接经常会超出 column 的长度这行,导致排版混乱。可以用 telega-url-shorten 插件来缩短,官方提供了一些适配,但是需要根据不同的 url 做适配,这里我是用正则统一缩短了 url。这里也要感谢 @stanley_1ab 提供的正则。

(setq telega-url-shorten-regexps
             ;; telega-url-shorten
             (list `(too-long-link
                     :regexp "^\\(https?://\\)\\(.\\{55\\}\\).*?$"
                     :symbol ""
                     :replace "\\1\\2...")))

如图,超出部分会省略,telega 的 chat buffer 的宽度默认70,由 telega-root-fill-column 控制,可以根据这个调整正则。

1 个赞

telega 中可以通过 emacs 快捷键来复制一些文本,但是图片需要分享时,没法一键操作,需要先下载再复制。下面来自 @LawxenceX 的函数可以解决这个问题。

(defun z/telega-save-file-to-clipboard (msg)
  "Save file at point to clipboard.
NOTE: macOS only."
  (interactive (list (telega-msg-for-interactive)))
  (let ((file (telega-msg--content-file msg)))
    (unless file
      (user-error "No file associated with message"))
    (telega-file--download file 32
      (lambda (dfile)
        (telega-msg-redisplay msg)
        (message "Wait for downloading to finish…")
        (when (telega-file--downloaded-p dfile)
          (let* ((fpath (telega--tl-get dfile :local :path)))
            (shell-command (format "osascript -e 'set the clipboard to POSIX file \"%s\"'" fpath))
            (message (format "File saved to clipboard: %s" fpath))))))))

我是绑定到 telega-msg-button-map 中的 C,小写 c 是复制文本的。效果是光标在图片时,按下 C 就会保存在 .telega 中并粘贴到剪贴板,方便分享。

另外一个算是和其他插件组合的用法,可以在 tabbar 上显示 telega 的未读、@消息以及 reaction,代码来自于 @roife,这里 telega 的未读、@消息以及 reaction会和原本 telega 中的 face 保持一致。原则上来讲,可以也可以放在 header line 上,有消息的时候展示,无消息的时候隐藏 header line。

light theme:

image

dark theme:

image

主要的函数在这里,这里我另外加了一个 emacs 的 icon 在前面,不喜欢的可以去掉。

(defun tab-bar-format-menu-bar ()
  "Produce the Menu button for the tab bar that shows the menu bar."
  `((menu-bar menu-item
              (format " %s "
                      (nerd-icons-sucicon "nf-custom-emacs"
                                          :face '(:inherit nerd-icons-purple)))
              tab-bar-menu-bar :help "Menu Bar")))

(defun lucius/tab-bar-tab-name-function ()
  (let* ((raw-tab-name (buffer-name (window-buffer (minibuffer-selected-window))))
         (count (length (window-list-1 nil 'nomini)))
         (truncated-tab-name (if (< (length raw-tab-name)
                                    tab-bar-tab-name-truncated-max)
                                 raw-tab-name
                               (truncate-string-to-width raw-tab-name
                                                         tab-bar-tab-name-truncated-max
                                                         nil nil tab-bar-tab-name-ellipsis))))
    (if (> count 1)
        (concat truncated-tab-name "(" (number-to-string count) ")")
      truncated-tab-name)))

(defun lucius/tab-bar-tab-name-format-function (tab i)
  (let ((face (funcall tab-bar-tab-face-function tab)))
    (concat
     (propertize " " 'face face)
     (propertize (number-to-string i) 'face `(:inherit ,face :weight ultra-bold :underline t))
     (propertize (concat " " (alist-get 'name tab) " ") 'face face))))
;; telega notification
(defvar lucius/tab-bar-telega-indicator-cache nil)

(defun lucius/tab-bar-telega-icon-update (&rest rest)
  (setq lucius/tab-bar-telega-indicator-cache
        (when (and (fboundp 'telega-server-live-p)
                   (telega-server-live-p)
                   (buffer-live-p telega-server--buffer))
          (let* ((me-user (telega-user-me 'locally))
                 (online-p (and me-user (telega-user-online-p me-user)))
                 (unread-count (or (plist-get telega--unread-chat-count :unread_unmuted_count) 0))
                 (mentioned-count (apply '+ (mapcar (telega--tl-prop :unread_mention_count)
                                                    (telega-filter-chats telega--ordered-chats '(mention)))))
                 ;; 最好使用 (and is-known unread-reactions) temex 来切断一般列表中不可见的聊天
                 ;; 此类聊天,例如对频道中的帖子发表评论,或者您进入、写下一些内容然后离开,然后有人做出反应的聊天
                 (reaction-count (apply '+ (mapcar (telega--tl-prop :unread_reaction_count)
                                                   (telega-filter-chats telega--ordered-chats '(and is-known unread-reactions)))))
                 (notification-count (+ mentioned-count unread-count reaction-count)))
            (when (> notification-count 0)
              (concat (nerd-icons-faicon "nf-fae-telegram" :face '(:inherit nerd-icons-purple))
                      " "
                      (when (> unread-count 0)
                        (propertize (concat "●" (number-to-string unread-count) " ")
                                    'face 'telega-unmuted-count))
                      (when (> mentioned-count 0)
                        (propertize (concat "@" (number-to-string mentioned-count) " ")
                                    'face 'telega-mention-count))
                      (when (> reaction-count 0)
                        (propertize (concat "❤" (number-to-string reaction-count) " ")
                                    'face 'telega-mention-count))))))))

(defun lucius/tab-bar-telega-icon ()
  (or lucius/tab-bar-telega-indicator-cache
      (lucius/tab-bar-telega-icon-update)))

在 tabbar 配置中再加上下面代码就可以生效了

(setq tab-bar-tab-name-function 'lucius/tab-bar-tab-name-function
         tab-bar-tab-name-format-function 'lucius/tab-bar-tab-name-format-function
         tab-bar-format '(tab-bar-format-menu-bar
                              lucius/tab-bar-telega-icon
                              tab-bar-format-tabs
                              tab-bar-format-add-tab))
(advice-add 'telega--on-updateUnreadChatCount :after #'lucius/tab-bar-telega-icon-update)
(advice-add 'telega--on-updateChatUnreadMentionCount :after #'lucius/tab-bar-telega-icon-update)
(advice-add 'telega--on-updateChatUnreadReactionCount :after #'lucius/tab-bar-telega-icon-update)
2 个赞

感谢分享! :smiling_face_with_three_hearts:

:+1: :+1: :+1: :+1: :+1: :+1:

漂亮,谢谢分享~

其中的 tab-bar-format 可以添加 tab-bar-format-align-right 来将内容放在 tabbar 的最右侧。

(setq tab-bar-format '(tab-bar-format-menu-bar
                              tab-bar-format-tabs
                              tab-bar-format-add-tab
                              tab-bar-format-align-right
                              lucius/tab-bar-telega-icon))

回复和转发的样式在最新的 telega 中作者修改了类似的设置。可以不用覆盖了。 telegram-cloud-photo-size-5-6199436953204668332-x

最新版的 telega 中删除了 telega–current-buffer 变量,只要把 telega-ins–ascent-percent 中的 (buffer (or telega--current-buffer (current-buffer))) 改为 (buffer (current-buffer)) 就可以继续使用了。

新版本增加了 specific quote 的支持,前面的竖线是用的 svg,所以和上方的不对齐。

image

可以通过修改 telega-symbols-emojify 列表,去除 vertical-bar 的 svg 生成,就可达到对齐。

或者修改 telega-ins--msg-reply-to-message-inline 函数最后的 when 语句即可。

(when reply-quote
      (telega-ins--line-wrap-prefix
          (concat (propertize "| " 'face 'telega-entity-type-blockquote)
                  (nerd-icons-mdicon "nf-md-format_quote_open"
                                     :face
                                     '(:inherit telega-entity-type-blockquote))
                  (propertize " " 'face 'telega-entity-type-blockquote))
        (telega-ins--with-face 'telega-entity-type-blockquote
          (telega-ins--fmt-text reply-quote replied-msg))
        (telega-ins "\n")))

这是最终的效果,换了 nerd-icons 作为 specific quote 标识。

2 个赞

我注意到 nerd-icons 还是太大了一些,用符号代替了。

(when reply-quote
      (telega-ins--line-wrap-prefix
          (propertize "| ❝ " 'face 'telega-entity-type-blockquote)
        (telega-ins--with-face 'telega-entity-type-blockquote
          (telega-ins--fmt-text reply-quote replied-msg))
        (telega-ins "\n")))

另外现在很多符号作者都是通过 svg 实现的,个人还是更喜欢 symbol 多一些(specific quote 的 svg 有点丑),完全可以通过删减 telega-symbols-emojify 列表,同时单独设置删除项需要显示的 symbol 就可以了。

telega-symbol-reply "➦"
telega-symbol-reply-quote "➦⑆"
telega-symbol-forward "➲"

这里是我用的几个。效果如下。

另外「未读」和「已读」也是在 telega-symbols-emojify 中判断用了 SVG,导致「未读」和「已读」的区分度并不是很明显,这里也是从列表中去掉了 checkmarkheavy-checkmark,更加清晰一些。

image image

我用的是

(setq telega-symbols-emojify (assq-delete-all 'checkmark telega-symbols-emojify))
(setq telega-symbols-emojify (assq-delete-all 'heavy-checkmark telega-symbols-emojify))
(setq telega-symbol-checkmark (nerd-icons-codicon "nf-cod-check"))
(setq telega-symbol-heavy-checkmark (nerd-icons-codicon "nf-cod-check_all"))

2 个赞

双勾好看,抄走!感谢~

用 nerd-icons 又换了几个

telega-symbol-reply (nerd-icons-faicon "nf-fa-reply")
telega-symbol-reply-quote (nerd-icons-faicon "nf-fa-reply_all")
telega-symbol-forward (nerd-icons-mdicon "nf-md-comment_arrow_right_outline")
telega-symbol-heavy-checkmark (nerd-icons-codicon "nf-cod-check_all")
telega-symbol-right-arrow (nerd-icons-codicon "nf-cod-arrow_right")
telega-symbol-reaction (nerd-icons-mdicon "nf-md-heart_circle")
1 个赞

原来修改的 quote 有些问题,添加到 nerd-icons 会在 quote 的文本的每一行前面都存在,当文本内容较长时或者在另一个聊天中回复时,会全文引用,会非常难看。因此做了修改。(quote 过长不作省略,作者的解释是原文可能被删除,无法跳转到原文。)

这一段时修改的在另一个聊天内回复某条消息以及 Specific quote 时,修改的是 telega-ins--msg-reply-to-message-inline 的最后一段。

(when reply-quote
      (telega-ins
       (concat (nerd-icons-mdicon "nf-md-format_quote_open" :face '(:inherit telega-msg-inline-reply))
               " "))
      (telega-ins--with-face 'telega-entity-type-blockquote
        (telega-ins--line-wrap-prefix "   "
          (telega-ins--fmt-text reply-quote replied-msg)))
      (telega-ins "\n"))

这一段时修改的普通消息中包含 quote 时,修改的是 telega--entity-type-to-text-props 中的 textEntityTypeBlockQuote 一段。

(textEntityTypeBlockQuote
      (let* ((lwprefix (concat
                        (nerd-icons-mdicon "nf-md-format_quote_open" :face '(:inherit telega-msg-inline-reply))
                        " "))
             (lwprops (list 'line-prefix lwprefix
                            'wrap-prefix "   "
                            'face 'telega-entity-type-blockquote))
             (repr (concat "\n" (apply #'propertize text lwprops) "\n")))
        (list 'telega-display repr)))

原理就是只有第一行添加了 nerd-icons,其他行用同等宽度的空格填充。

主题很好看, 这个配色是自己调的吗?