From 02c68bcea96edbb30d559885d28ca429a1e73d72 Mon Sep 17 00:00:00 2001 From: Zajcev Evgeny Date: Fri, 31 Jan 2025 20:39:35 +0300 Subject: [PATCH] Initial support for TDLib 1.8.42 - [new] `telega-msg-delimiter` to delimit messages in the chatbuf - [upd] Support for updated TDLib API for folders - [enh] Better logic to choose best photo in `telega-photo--best` with lesser garbagea - [upd] "updateOwnedStarCount" event to support starAmount TL struct - [enh] Use `telega-msg-heading-trail' instead of `telega-msg-heading-with-date-and-status'. Making header configuration more flexible - [fix] Use `custom-face-get-current-spec-unfiltered' instead of `face-default-spec', so user changes to face takes effect. Fixes https://t.me/emacs_china/262239 - [enh] Hide text/media spoilers when point moves out of message - [fix] Make Show/Hide spoiler work if multiple text entities used for spoiler text - [fix] Use window width in chars in the chatbuf auto fill mode. Fixes https://t.me/emacs_telega/46751 - [enh] Support for :show_media_above_description property of linkPreview And more Version -> 0.8.420 --- .github/workflows/docker.yml | 3 +- docs/index.html | 94 ++++-- docs/telega-ellit.org | 4 +- docs/telega-manual.org | 35 ++- etc/langs/en.plist | 32 ++ telega-chat.el | 50 ++-- telega-core.el | 120 ++++++-- telega-customize.el | 60 +++- telega-emoji.el | 2 +- telega-filter.el | 4 +- telega-folders.el | 74 +++-- telega-i18n.el | 2 + telega-info.el | 2 +- telega-ins.el | 553 +++++++++++++++++++---------------- telega-match.el | 33 ++- telega-media.el | 157 +++++++--- telega-modes.el | 171 +++++------ telega-msg.el | 320 ++++++++++---------- telega-obsolete.el | 4 + telega-root.el | 66 ++++- telega-sort.el | 39 ++- telega-sticker.el | 12 +- telega-tdlib-events.el | 13 +- telega-tdlib.el | 87 +++++- telega-user.el | 14 +- telega-util.el | 171 ++++++----- telega-webpage.el | 19 +- telega.el | 8 +- test.el | 6 +- 29 files changed, 1366 insertions(+), 789 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9c72761..21cb76a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,8 @@ jobs: docker-arm64: # enabled due to https://t.me/emacs_telega/47280 - if: true + # Disabled due to build failure, see https://t.me/emacs_telega/47396 + if: false runs-on: ubuntu-latest steps: - diff --git a/docs/index.html b/docs/index.html index d37fc3c..7abf9d5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,10 +3,10 @@ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + -Telega Manual (v0.8.393) +Telega Manual (v0.8.420) @@ -32,11 +32,43 @@ + + +
-

 Telega Manual (v0.8.393)

+

 Telega Manual (v0.8.420)

@@ -2277,6 +2316,8 @@

last-seen
Sort by last seen activity. For private chats user's last seen date is taken. For other chats date of the last message is taken.
+ +
important
Makes chats matching telega-important-chat-temex on top.

@@ -2370,7 +2411,7 @@

+
 MSG1                              <--- msg sent on 27dec
 -------(28 December 2020)------   <--- date break
 MSG2                              <--- msg sent on 28dec
@@ -2379,6 +2420,19 @@ 

+ +

+Default value: "\n" +

@@ -2563,7 +2617,7 @@

-
+
 ```<language-name>
 first line of multiline preformatted code
 second line
@@ -2595,7 +2649,7 @@ 

+
 #+begin_src <language-name>
 code line
 next code line
@@ -2858,12 +2912,12 @@ 

To forward a message, put cursor under the message which you want to forward and press - (telega-msg-forward-marked-or-at-point) +f, <down-mouse-3> <forward> (telega-msg-forward-dwim) and then select a Chat to forward a message to. To forward multiple messages at once, mark messages with the -m, <down-mouse-3> <telega-msg-mark-toggle> (telega-msg-mark-toggle) and then +m, <down-mouse-3> <mark> (telega-msg-mark-toggle) and then press - (telega-msg-forward-marked-or-at-point) +f, <down-mouse-3> <forward> (telega-msg-forward-dwim) on one of the messages.

@@ -2890,7 +2944,7 @@

As with forwarding messages, you can mark multiple messages to delete -with m, <down-mouse-3> <telega-msg-mark-toggle> (telega-msg-mark-toggle). +with m, <down-mouse-3> <mark> (telega-msg-mark-toggle).

@@ -3493,7 +3547,7 @@

To toggle message at point being favorite, press -*, <down-mouse-3> <rm-favorite>, <down-mouse-3> <add-favorite> (telega-msg-favorite-toggle). +*, <down-mouse-3> <favorite> (telega-msg-favorite-toggle).

@@ -3519,7 +3573,7 @@

-
+
 [⏪] [⏩] [2×] [Stop] 
 
@@ -4717,7 +4771,7 @@

+
 https://github.com/zevlg/telega.el/issues/105
 https://gitlab.com/jessieh/mood-line/issues/6
 https://www.youtube.com/watch?v=0m2jR6_eMkU
diff --git a/docs/telega-ellit.org b/docs/telega-ellit.org
index cd0c17e..c258d02 100644
--- a/docs/telega-ellit.org
+++ b/docs/telega-ellit.org
@@ -760,12 +760,12 @@ It is possible to edit a message using markup syntax.
 
 To forward a message, put cursor under the message which you want to
 forward and press
-{{{where-is(telega-msg-forward-marked-or-at-point,telega-msg-button-map)}}}
+{{{where-is(telega-msg-forward-dwim,telega-msg-button-map)}}}
 and then select a Chat to forward a message to.  To forward multiple
 messages at once, mark messages with the
 {{{where-is(telega-msg-mark-toggle,telega-msg-button-map)}}} and then
 press
-{{{where-is(telega-msg-forward-marked-or-at-point,telega-msg-button-map)}}}
+{{{where-is(telega-msg-forward-dwim,telega-msg-button-map)}}}
 on one of the messages.
 
 There are few options how you can affect the way a message is forwarded:
diff --git a/docs/telega-manual.org b/docs/telega-manual.org
index 8bb3586..0244411 100644
--- a/docs/telega-manual.org
+++ b/docs/telega-manual.org
@@ -1,5 +1,5 @@
 #+options: timestamp:nil \n:t num:nil ellit-cid:t
-#+title: Telega Manual (v0.8.393)
+#+title: Telega Manual (v0.8.420)
 #+author: Zajcev Evgeny
 #+startup: showall
 
@@ -939,9 +939,15 @@ view.
 - @@html:@@v S@@html:@@ (~telega-view-settings~) :: 
      View and edit your Telegram settings.
 
-- @@html:@@v c@@html:@@ (~telega-view-contacts~) :: 
+- @@html:@@v c s@@html:@@ (~telega-view-contacts-search~) :: 
      View contacts searched by ~QUERY~.
      If ~QUERY~ is empty string, then show all contacts.
+- @@html:@@v c a@@html:@@ (~telega-view-contacts-all~) :: 
+     View all contacts.
+- @@html:@@v c f@@html:@@ (~telega-view-close-friends~) :: 
+     View close friends.
+- @@html:@@v c o@@html:@@ (~telega-view-owned-bots~) :: 
+     View owned bots.
 
 - @@html:@@v C@@html:@@, @@html:@@c l@@html:@@ (~telega-view-calls~) :: 
      View calls.
@@ -1157,6 +1163,7 @@ Use ~telega-chat-match-p~ to match a chat.
 
 - verified, @@html:@@/ v@@html:@@ (~telega-filter-by-verified~) :: 
      Matches if chat is verified.
+     Return verification status if ~CHAT~ is verified.
 
 - (restriction ~SUFFIX-LIST~...), @@html:@@/ r@@html:@@ (~telega-filter-by-restriction~) :: 
      Matches restricted chats.
@@ -1233,6 +1240,7 @@ Use ~telega-chat-match-p~ to match a chat.
 
 - fake-or-scam :: 
      Matches if chat is fake or scam user or group.
+     Return verification status if chat is fake or scam.
 
 - (has-video-chat [ ~NON-EMPTY~ ]) :: 
      Matches if chat contains a live video chat.
@@ -1309,6 +1317,9 @@ Use ~telega-chat-match-p~ to match a chat.
      Matches if chat's boost level is greater or equal to ~N~.
      By default ~N~ is 1.
 
+- is-pinned :: 
+     Matches if chat is pinned.
+
 ** List of Message Temexes
 :PROPERTIES:
 :CUSTOM_ID: list-of-message-temexes
@@ -1816,6 +1827,9 @@ For other sorting keybindings see below.
      For private chats user's last seen date is taken.
      For other chats date of the last message is taken.
 
+- ~important~ :: 
+     Makes chats matching ~telega-important-chat-temex~ on top.
+
 ** Customizable options making use of sorting criteria
 :PROPERTIES:
 :CUSTOM_ID: customizable-options-making-use-of-sorting-criteria
@@ -1878,6 +1892,13 @@ Important customizable options:
   #+end_example
 
   Default value: ~t~
+- User Option: ~telega-msg-delimiter~ 
+
+  Delimiter for the messages in a chatbuf.
+  Use ~(propertize "\n" \~line-spacing 0.25)' to add extra line space
+  between messages. 
+
+  Default value: ~"\n"~
 
 ** Chatbuf fast navigation
 :PROPERTIES:
@@ -2198,12 +2219,12 @@ It is possible to edit a message using markup syntax.
 
 To forward a message, put cursor under the message which you want to
 forward and press
- (~telega-msg-forward-marked-or-at-point~)
+@@html:@@f@@html:@@, @@html:@@ @@html:@@ (~telega-msg-forward-dwim~)
 and then select a Chat to forward a message to.  To forward multiple
 messages at once, mark messages with the
-@@html:@@m@@html:@@, @@html:@@ @@html:@@ (~telega-msg-mark-toggle~) and then
+@@html:@@m@@html:@@, @@html:@@ @@html:@@ (~telega-msg-mark-toggle~) and then
 press
- (~telega-msg-forward-marked-or-at-point~)
+@@html:@@f@@html:@@, @@html:@@ @@html:@@ (~telega-msg-forward-dwim~)
 on one of the messages.
 
 There are few options how you can affect the way a message is forwarded:
@@ -2222,7 +2243,7 @@ To delete a message, put cursor under the message you want to delete and press
  (~telega-msg-delete-marked-or-at-point~).
 
 As with [[#forwarding-messages][forwarding messages]], you can mark multiple messages to delete
-with @@html:@@m@@html:@@, @@html:@@ @@html:@@ (~telega-msg-mark-toggle~).
+with @@html:@@m@@html:@@, @@html:@@ @@html:@@ (~telega-msg-mark-toggle~).
 
 Also, you can ban/report message sender (and delete all messages from
 this sender in the chat) with
@@ -2618,7 +2639,7 @@ messages are labeled with:
   Default value: ~"🔖"~
 
 To toggle message at point being favorite, press
-@@html:@@*@@html:@@, @@html:@@ @@html:@@, @@html:@@ @@html:@@ (~telega-msg-favorite-toggle~).
+@@html:@@*@@html:@@, @@html:@@ @@html:@@ (~telega-msg-favorite-toggle~).
 
 To jump to a favorite message before message at point in the chat
 buffer, press
diff --git a/etc/langs/en.plist b/etc/langs/en.plist
index 735ec30..75ecd83 100644
--- a/etc/langs/en.plist
+++ b/etc/langs/en.plist
@@ -764,6 +764,8 @@ Example: 23 y.o. designer from San Francisco")
   :value "Please change at least one rule for this folder.")
  ("lng_filters_remove_sure"
   :value "This will remove the folder, chats won't be deleted.")
+ ("lng_filters_add_chats"
+  :value "Add Chats")
 
  ("lng_filters_all"
   :value "All chats")
@@ -790,6 +792,8 @@ Example: 23 y.o. designer from San Francisco")
   :other_value "{count} chats")
  ("lng_filters_shareable_status"
   :value "shareable folder")
+ ("lng_filters_tag_color_no"
+  :value "No Tag")
 
  ;; Comments
  ("lng_comments_open_none"
@@ -862,6 +866,8 @@ Example: 23 y.o. designer from San Francisco")
   :value "Select up to this message")
  ("lng_context_disable_spoiler"
   :value "Remove Spoiler")
+ ("lng_context_spoiler_effect"
+  :value "Hide with Spoiler")
  ("lng_context_cancel_upload"
   :value "Cancel Upload")
  ("lng_context_cancel_download"
@@ -991,6 +997,11 @@ Example: 23 y.o. designer from San Francisco")
   :value "{from} renamed the {link} to «{title}»")
  ("lng_action_topic_icon_changed"
   :value "{from} changed the {link} icon to {emoji}")
+ ("lng_action_topic_unhidden"
+  :value "«{topic}» was unhidden")
+ ("lng_action_topic_hidden"
+  :value "«{topic}» was hidden")
+
  ("lng_action_boost_apply"
   :one_value "{from} boosted the group"
   :other_value "{from} boosted the group {count} times")
@@ -1008,6 +1019,10 @@ to start the topic.")
   :value "Topic Info")
  ("lng_topic_author_badge"
   :value "Topic Creator")
+ ("lng_admin_log_topics_hidden"
+  :value "{from} hid topic {topic}")
+ ("lng_admin_log_topics_unhidden"
+  :value "{from} unhid topic {topic}")
 
  ("lng_rights_gigagroup_about"
   :value "Broadcast groups can have over 200,000 members, but only admins can send messages in them.")
@@ -1662,6 +1677,12 @@ You can send them an invite link as message instead.")
   :value "Create link")
  ("lng_formatting_link_url"
   :value "URL")
+ ("lng_formatting_code_title"
+  :value "Code Language")
+ ("lng_formatting_code_language"
+  :value "Language for syntax highlighting.")
+ ("lng_formatting_code_auto"
+  :value "Auto-Detect")
 
  ;; Copying
  ("lng_code_copied"
@@ -1894,6 +1915,17 @@ You can send them an invite link as message instead.")
   :one_value "Do you want to unlock {media} from {user} for **{count} Star**?"
   :other_value "Do you want to unlock {media} from {user} for **{count} Stars**?")
 
+ ("lng_paid_react_title"
+  :value "Star Reaction")
+ ("lng_paid_react_undo"
+  :value "Undo")
+ ("lng_paid_react_toast"
+  :one_value "Star Sent!"
+  :other_value "Stars Sent!")
+ ("lng_paid_react_toast_text"
+  :one_value "You reacted with **{count} Star**."
+  :other_value "You reacted with **{count} Stars**.")
+
  ("lng_stats_overview_message_views"
   :value "Views")
  )
diff --git a/telega-chat.el b/telega-chat.el
index 0f1d399..8b2c82e 100644
--- a/telega-chat.el
+++ b/telega-chat.el
@@ -47,6 +47,7 @@
 ;; Important customizable options:
 ;; - {{{user-option(telega-chat-fill-column, 2)}}}
 ;; - {{{user-option(telega-chat-use-date-breaks, 2)}}}
+;; - {{{user-option(telega-msg-delimiter, 2)}}}
 
 ;;; Code:
 (require 'cl-lib)
@@ -378,18 +379,18 @@ end of the title."
           (if no-badges
               title0
             (concat title0
-                    ;; Badges
+                    ;; Verification Status badges
+                    (telega-msg-sender--verification-badges
+                     (plist-get info :verification_status))
+
+                    ;; Premium Badge
                     (cond ((plist-get chat :emoji_status)
                            (telega-ins--as-string
                             (telega-ins--chat-emoji-status chat)))
                           ((plist-get info :is_premium)
                            (telega-symbol 'premium)))
-                    (when (plist-get info :is_scam)
-                      (propertize (telega-i18n "lng_scam_badge") 'face 'error))
-                    (when (plist-get info :is_fake)
-                      (propertize (telega-i18n "lng_fake_badge") 'face 'error))
-                    (when (plist-get info :is_verified)
-                      (telega-symbol 'verified))
+
+                    ;; Blocking Status badge
                     (when (telega-chat-match-p chat 'is-blocked)
                       (telega-symbol 'blocked))
                     ))))
@@ -2633,6 +2634,15 @@ If NEW-FOCUS-STATE is specified, then focus state is forced."
               (telega-chatbuf--filter-msg-position-load msg)
               (run-hook-with-args 'telega-msg-hover-in-hook msg))
 
+          ;; NOTE: Hide spoilers when point moves out of message
+          (when (or (when (plist-get msg :telega-text-spoiler-removed)
+                      (plist-put msg :telega-text-spoiler-removed nil)
+                      t)
+                    (when (plist-get msg :telega-media-spoiler-removed)
+                      (plist-put msg :telega-media-spoiler-removed nil)
+                      t))
+            (telega-msg-redisplay msg))
+
           (run-hook-with-args 'telega-msg-hover-out-hook msg))
         ))))
 
@@ -2693,7 +2703,7 @@ If NEW-FOCUS-STATE is specified, then focus state is forced."
       ;; Without separator, sensor function won't be triggered
       (unless telega-chatbuf--messages-compact-view
         (telega-ins--with-props '(read-only t front-sticky t)
-          (telega-ins "\n"))))
+          (telega-ins telega-msg-delimiter))))
     ))
 
 (define-derived-mode telega-chat-mode nil '((:eval (telega-symbol 'mode)) "Chat")
@@ -4271,7 +4281,7 @@ Use `\\[universal-argument] to clear formatting from selected region."
     (user-error "telega: Can cancel formatting only inside chatbuf input"))
   (remove-text-properties
    begin end
-   '(face nil :tl-entity nil :tl-entity-type nil :tl-entity-explicit nil
+   '(face nil :tl-entities nil :tl-entity-type nil :tl-entity-explicit nil
           rear-nonsticky nil front-sticky nil)))
 
 (defun telega-chatbuf-cancel-aux (&optional arg)
@@ -4965,19 +4975,20 @@ from message at point."
             (message "telega: Can't find message with unread reaction")
           (telega-msg-goto (seq-first reaction-messages) 'highlight))))))
 
-(defun telega-chat-favorite-messages (chat)
+(defun telega-chat-favorite-messages-ids (chat)
   "Return entries from the `telega--favorite-messages' for the CHAT."
-  (cl-remove (plist-get chat :id) telega--favorite-messages
-             :test-not 'eq :key (telega--tl-prop :chat_id)))
+  (let ((chat-id (plist-get chat :id)))
+    (cl-loop for fav-msg in telega--favorite-messages
+             if (eq chat-id
+                    (plist-get fav-msg :chat_id))
+             collect (plist-get fav-msg :id))))
 
 (defun telega-chatbuf-prev-favorite ()
   "Goto previous favorite message."
   (interactive)
   (let* ((fav-ids
           ;; NOTE: Sort favorite messages ids by id in decreasing order
-          (sort (mapcar (telega--tl-prop :id)
-                        (telega-chat-favorite-messages telega-chatbuf--chat))
-                #'>))
+          (sort (telega-chat-favorite-messages-ids telega-chatbuf--chat) #'>))
          (next-fav-id
           (or (cl-find (or (plist-get (telega-msg-at (point)) :id) -1)
                        fav-ids :test #'>)
@@ -6219,10 +6230,12 @@ REVOKE forced to non-nil for supergroup, channel or a secret chat."
             (telega-help-message 'show-deleted
                 "JFYI see `telega-chat-show-deleted-messages-for'")))))))
 
-(defun telega-msg-report-dwim (reason)
+(defun telega-msg-report-dwim (msg)
   "Report messages in a DWIM manner."
-  (interactive)
+  (interactive (list (telega-msg-for-interactive)))
   (user-error "TODO")
+  ;; telega--reportMessageReaction
+  ;; telega--reportSupergroupSpam
   )
 
 (defun telega-chatbuf-complete ()
@@ -7106,8 +7119,7 @@ containing QUERY sent by specified sender."
   (interactive (list (get-buffer-window)))
 
   (when (or (null win) (eq (selected-window) win))
-    (let ((new-fill-column (- (/ (window-width win 'pixels)
-                                 (telega-chars-xwidth 1))
+    (let ((new-fill-column (- (window-width win)
                               (if win
                                   (with-selected-window win
                                     (line-number-display-width))
diff --git a/telega-core.el b/telega-core.el
index 2049ef7..4137953 100644
--- a/telega-core.el
+++ b/telega-core.el
@@ -485,28 +485,31 @@ Pallete is a plist with the following keys: `:outline', `:foreground',
     (setq background-mode (frame-parameter nil 'background-mode)))
   (cl-assert (memq background-mode '(light dark)))
 
-  (if (and color-id (< color-id 7))
-      (let ((palette (alist-get background-mode telega-builtin-palettes-alist)))
-        (cl-assert (= (length palette) 7))
-        (nth color-id palette))
-
-    (when-let* ((tl-color (alist-get color-id telega--accent-colors-alist))
-                (colors (mapcar (lambda (color-value)
-                                  (format "#%06x" color-value))
-                                (plist-get tl-color
-                                           (if (eq background-mode 'light)
-                                               :light_theme_colors
-                                             :dark_theme_colors))))
-                (fg-color (car colors))
-                (bg-color (telega-color-name-set-saturation-light
-                           fg-color 0.1 (cl-ecase background-mode
-                                          (light 0.8)
-                                          (dark  0.2))))
-                (ol-color (cl-ecase background-mode
-                            (light (color-darken-name fg-color 20))
-                            (dark  (color-lighten-name fg-color 20)))))
-      `((:outline ,ol-color) (:foreground ,fg-color)
-        (:background ,bg-color) (:colors ,colors)))))
+  (cond ((or (null color-id) (< color-id 0))
+         nil)
+        ((< color-id 7)
+         (let ((palette (alist-get background-mode
+                                   telega-builtin-palettes-alist)))
+           (cl-assert (= (length palette) 7))
+           (nth color-id palette)))
+        (t
+         (when-let* ((tl-color (alist-get color-id telega--accent-colors-alist))
+                     (colors (mapcar (lambda (color-value)
+                                       (format "#%06x" color-value))
+                                     (plist-get tl-color
+                                                (if (eq background-mode 'light)
+                                                    :light_theme_colors
+                                                  :dark_theme_colors))))
+                     (fg-color (car colors))
+                     (bg-color (telega-color-name-set-saturation-light
+                                fg-color 0.1 (cl-ecase background-mode
+                                               (light 0.8)
+                                               (dark  0.2))))
+                     (ol-color (cl-ecase background-mode
+                                 (light (color-darken-name fg-color 20))
+                                 (dark  (color-lighten-name fg-color 20)))))
+           `((:outline ,ol-color) (:foreground ,fg-color)
+             (:background ,bg-color) (:colors ,colors))))))
 
 (defmacro telega-palette-attr (palette attribute)
   "From PALETTE return ATTRIBUTE value.
@@ -535,7 +538,8 @@ to make `:outline' be a `:foreground'."
   "Merge PALETTE ATTRIBUTES into FACE, resulting in a new face."
   (declare (indent 2))
   (let ((new-face (cond ((facep face)
-                         (face-spec-choose (face-default-spec face)))
+                         (face-spec-choose
+                          (custom-face-get-current-spec-unfiltered face)))
                         (t
                          (cl-assert (listp face))
                          face)))
@@ -740,6 +744,7 @@ Done when telega server is ready to receive queries."
   (setq telega--animations-saved nil)
   (setq telega--chat-themes nil)
   (setq telega--dice-emojis nil)
+  (setq telega--suggested-actions nil)
 
   (setq telega-tdlib--chat-folders nil)
   (setq telega-tdlib--chat-folder-tags-p nil)
@@ -1029,6 +1034,20 @@ BIND is in form ((PROP VAL) PLIST)."
             by #'cddr
             do (progn ,@body)))
 
+(defun telega--tl-entity-get (tl-entities &rest tl-types)
+  "Return first TL text entity from TL-ENTITIES, with entity type from TL-TYPES.
+If TL-TYPES is nil, then return first TL entity from the TL-ENTITIES list."
+  (if tl-types
+      (cl-find-if (lambda (tl-ent)
+                    (memq (telega--tl-type (plist-get tl-ent :type)) tl-types))
+                  tl-entities)
+    (car tl-types)))
+
+(defun telega--tl-star-amount-as-float (tl-star-amount)
+  "Return starAmount TL-STAR-AMOUNT as float number."
+  (+ (plist-get tl-star-amount :star_count)
+     (/ (plist-get tl-star-amount :nanostar_count) 1000000000.0)))
+
 (defsubst telega-file--size (file)
   "Return FILE size."
   ;; NOTE: fsize is 0 if unknown, in this case esize is approximate
@@ -1625,10 +1644,10 @@ If NO-PROPS is non-nil, then remove properties from the resulting string."
     (cond ((string= "chatListMain" pos-list-type)
            'main)
           ((string= "chatListFolder" pos-list-type)
-           (telega-tl-str (cl-find (plist-get pos-list :chat_folder_id)
-                                   telega-tdlib--chat-folders
-                                   :key (telega--tl-prop :id))
-                          :title no-props))
+           (telega-folder-name (cl-find (plist-get pos-list :chat_folder_id)
+                                        telega-tdlib--chat-folders
+                                        :key (telega--tl-prop :id))
+                               no-props))
           ((string= "chatListArchive" pos-list-type)
            'archive))))
 
@@ -1942,6 +1961,53 @@ Return what BODY returns."
            (progn ,@body)
          (add-text-properties ,spnt-sym (point) ,props)))))
 
+(defun telega-fmt-eval-eliding (str elide-props)
+  "Apply eliding properties to STR."
+  (let ((str-width (string-width str))
+        (max (plist-get elide-props :max)))
+    (if (or (not max) (<= str-width max))
+        str
+
+      ;; Need to elide string
+      (let* ((elide-str (or (plist-get elide-props :elide-string)
+                            telega-symbol-eliding))
+             (elide-width (string-width elide-str))
+             (elide-pos (or (plist-get elide-props :elide-position) 1))
+             (max (plist-get elide-props :max))
+             (str-len (length str))
+             (elide-trail (progn
+                            (cl-assert (<= 0 elide-pos 1))
+                            (floor (* max (- 1 elide-pos)))))
+             (trail-width
+              (progn
+                ;; Correct `elide-trail' in case of multibyte chars
+                (while (and (> elide-trail 0)
+                            (> (string-width str (- str-len elide-trail))
+                               (floor (* max (- 1 elide-pos)))))
+                  (setq elide-trail (1- elide-trail)))
+                (string-width str (- str-len elide-trail))))
+             (elide-lead (- (min max str-len) elide-width trail-width)))
+
+        ;; Correct `elide-lead' in case of multibyte chars, by chopping
+        ;; char by char from the end of leading chars
+        (while (and (> elide-lead 0)
+                    (> (+ (string-width str 0 elide-lead) elide-width trail-width)
+                       max))
+          (setq elide-lead (1- elide-lead)))
+        (add-text-properties elide-lead (- str-len elide-trail)
+                             (list 'display elide-str
+                                   'rear-nonsticky '(display)
+                                   'face (plist-get elide-props :face))
+                             str)
+        str))))
+
+(defmacro telega-ins--with-eliding (elide-props &rest body)
+  (declare (indent 1))
+  `(telega-ins
+    (telega-fmt-eval-eliding
+     (telega-ins--as-string ,@body)
+     ,elide-props)))
+
 (defun telega--region-by-text-prop (beg prop &optional limit)
   "Return region after BEG point with text property PROP set."
   (unless (get-text-property beg prop)
diff --git a/telega-customize.el b/telega-customize.el
index 6acd898..10dc065 100644
--- a/telega-customize.el
+++ b/telega-customize.el
@@ -457,7 +457,8 @@ for the `telega-docker-run-arguments'."
 (defcustom telega-emoji-large-height 2
   "*Vertical size in characters for emoji only messages.
 Used only if `telega-emoji-use-images' is non-nil."
-  :type 'integer
+  :type '(choice (const :tag "Disabled" nil)
+                 (integer :tag "Height for emoji only message"))
   :group 'telega-emoji)
 
 
@@ -1719,6 +1720,17 @@ See `mode-line-buffer-identification'."
   :type 'sexp
   :group 'telega-chat)
 
+(defcustom telega-expandable-blockquote-limit '(50 . 150)
+  "*Non-nil to collapse expandable blockquote at this char.
+Can be a cons cell, meaning to show at least car chars, and then
+collapse at newline or at cdr char."
+  :package-version '(telega . "0.8.420")
+  :type '(choice (const :tag "No collapse" nil)
+                 (cons (integer :tag "Min chars before collapsing")
+                       (integer :tag "Collapse at newline before"))
+                 (integer :tag "Collapse at"))
+  :group 'telega-chat)
+
 
 ;; VoIP
 (defgroup telega-voip nil
@@ -1850,10 +1862,12 @@ Use this for Client Side Messages Filtering."
   :type '(repeat function)
   :group 'telega-msg)
 
-(defcustom telega-msg-heading-with-date-and-status nil
+(defcustom telega-msg-heading-trail nil
   "Non-nil to put message sent date and outgoing status into heading."
-  :package-version '(telega . "0.8.210")
-  :type 'boolean
+  :package-version '(telega . "0.8.393")
+  :type '(choice (const :tag "Message's date and status" date-and-status)
+                 (const :tag "Fill with `telega-msg-heading' face" fill)
+                 (const :tag "No trail" nil))
   :group 'telega-msg)
 
 (defcustom telega-msg-heading-aux-format-plist
@@ -1929,6 +1943,15 @@ fallback to cdr argument."
   :type '(cons integer integer)
   :group 'telega-msg)
 
+(defcustom telega-msg-delimiter "\n"
+  "Delimiter for the messages in a chatbuf.
+Use `(propertize \"\\n\" \\'line-spacing 0.25)' to add extra line space
+between messages."
+  :package-version '(telega . "0.8.420")
+  :type 'string
+  :options (list "\n\n" (propertize "\n" 'line-spacing 0.25))
+  :group 'telega-msg)
+
 
 (defgroup telega-story nil
   "Customization for Telegram stories."
@@ -2110,12 +2133,18 @@ cdr is used if custom order is greater then real chat's order."
   :group 'telega-symbol)
 
 (defcustom telega-symbol-checkmark "✓" ;\u2713
-  "Symbol for simple check mark."
+  "Symbol for single check mark."
   :type 'string
   :group 'telega-symbol)
 
 (defcustom telega-symbol-heavy-checkmark "✔" ;\u2714
-  "Symbol for heavy check mark."
+  "Symbol for double check mark."
+  :type 'string
+  :group 'telega-symbol)
+
+(defcustom telega-symbol-no-checkmark " "
+  "Status symbol for non-outgoing messages.
+Used for alignment with outgoing messages."
   :type 'string
   :group 'telega-symbol)
 
@@ -3017,6 +3046,18 @@ non-nil if symbol gets emojification."
   "Face to display strike through RichText."
   :group 'telega-faces)
 
+(defface telega-webpage-subtitle
+  '((((type tty pc) (class color)) :weight bold)
+    (t :inherit fixed-pitch-serif :weight bold :height 1.1))
+  "Face to display subtitle in webpage instant view."
+  :group 'telega-faces)
+
+(defface telega-webpage-title
+  '((((type tty pc) (class color)) :weight bold)
+    (t :inherit telega-webpage-subtitle :height 1.1))
+  "Face to display title in webpage instant view."
+  :group 'telega-faces)
+
 (defface telega-webpage-subheader
   '((((type tty pc) (class color)) :weight bold)
     (t :inherit variable-pitch :weight bold :height 1.1))
@@ -3034,8 +3075,13 @@ non-nil if symbol gets emojification."
   "Face to display fixed text in webpage instant view."
   :group 'telega-faces)
 
+(defface telega-webpage-outline
+  '((t :inherit telega-msg-heading :extend t))
+  "Face to display text with different background."
+  :group 'telega-faces)
+
 (defface telega-webpage-preformatted
-  '((t :inherit telega-webpage-fixed :background "gray85"))
+  '((t :inherit telega-webpage-fixed :inherit telega-webpage-outline))
   "Face to display preformatted text in webpage instant view."
   :group 'telega-faces)
 
diff --git a/telega-emoji.el b/telega-emoji.el
index 8fc926f..86fe836 100644
--- a/telega-emoji.el
+++ b/telega-emoji.el
@@ -531,7 +531,7 @@ If EMOJI is omitted, then use STICKER's emoji instead."
    :tl-entity-type (list :@type "textEntityTypeCustomEmoji"
                          :custom_emoji_id (telega-custom-emoji-id sticker))
    'display (when telega-use-images
-              (telega-sticker--image sticker))
+              (copy-sequence (telega-sticker--image sticker)))
    'rear-nonsticky t))
 
 (defun telega-custom-emoji-choose (&optional custom-action)
diff --git a/telega-filter.el b/telega-filter.el
index 08aa56b..6989369 100644
--- a/telega-filter.el
+++ b/telega-filter.el
@@ -305,7 +305,7 @@ If FILTER is nil, then active filter is used."
           ((and telega-filter-custom-one-liners
                 folder-p
                 (equal (nth 1 custom-filter)
-                       (telega-tl-str (car telega-tdlib--chat-folders) :title)))
+                       (telega-folder-name (car telega-tdlib--chat-folders))))
            (insert "\n"))
           ((zerop ccolumn)
            ;; no-op
@@ -384,7 +384,7 @@ Actually return active chat filter corresponding to CUSTOM filter."
 
 (defun telega-filter--custom-folder-spec (tdlib-chat-filter)
   "Return custom filter spec for the TDLIB-CHAT-FILTER folder."
-  (let ((fn (telega-tl-str tdlib-chat-filter :title)))
+  (let ((fn (telega-folder-name tdlib-chat-filter)))
     (cons (telega-folder-format "%i%f" fn tdlib-chat-filter)
           (list 'folder (substring-no-properties fn)))))
 
diff --git a/telega-folders.el b/telega-folders.el
index f55cc33..b73d04a 100644
--- a/telega-folders.el
+++ b/telega-folders.el
@@ -60,18 +60,42 @@ See `telega-folder-icons-alist'")
             (setq folders (cons list-name folders))))))
     (nreverse folders)))
 
-(defun telega-folder-names (&optional tdlib-filters)
+(defun telega-folder-name--fetch-custom-emojis (folder)
+  "For the FOLDER, ensure all custom emojis in its name are fetched."
+  (when-let* ((fmt-text (telega--tl-get folder :name :text))
+              (name-ce-ids
+               (seq-uniq
+                (delq nil
+                      (mapcar (lambda (entity)
+                                (let ((entity-type (plist-get entity :type)))
+                                  (when (eq 'textEntityTypeCustomEmoji
+                                            (telega--tl-type entity-type))
+                                    (plist-get entity-type :custom_emoji_id))))
+                              (plist-get fmt-text :entities)))))
+              (fetch-ce-ids
+               (seq-remove #'telega-custom-emoji-get name-ce-ids)))
+    ;; NOTE: Fetch only uncached custom emojis
+    (telega--getCustomEmojiStickers fetch-ce-ids
+      (lambda (stickers)
+        (seq-doseq (sticker stickers)
+          (telega-custom-emoji--ensure sticker))))))
+
+(defun telega-folder-name (folder &optional no-properties)
+  "Return FOLDER's name."
+  (telega-tl-str (plist-get folder :name) :text no-properties))
+
+(defun telega-folder-names (&optional tdlib-filters no-properties)
   "Return list of names for all Telegram folders.
 Specify TDLIB-FILTERS list to use alternative TDLib chat filters list."
   (mapcar (lambda (fi)
-            (telega-tl-str fi :title))
+            (telega-folder-name fi no-properties))
           (or tdlib-filters telega-tdlib--chat-folders)))
 
 (defun telega-folder--chat-folder-info (folder-name)
   "Return chatFolderInfo corresponding to FOLDER-NAME."
   (cl-find folder-name telega-tdlib--chat-folders
            :key (lambda (fi)
-                  (telega-tl-str fi :title 'no-props))
+                  (telega-folder-name fi 'no-props))
            :test #'equal))
 
 (defun telega-folder--tdlib-chat-list (folder-name)
@@ -107,7 +131,7 @@ property is used in `telega-folder--tdlib-chat-list' to
 correctly extract folder name."
   (unless folder-info
     (setq folder-info (telega-folder--chat-folder-info folder-name)))
-  (let* ((ftitle (telega-tl-str folder-info :title))
+  (let* ((ftitle (telega-folder-name folder-info))
          (ficon-name (telega-tl-str (plist-get folder-info :icon) :name))
          (ficon (cdr (assoc ficon-name telega-folder-icons-alist)))
          (ficon-emoji (when (and ficon telega-emoji-use-images)
@@ -145,9 +169,12 @@ If FORCE is specified, then `telega-tdlib--chat-folder-tags-p' is ignored."
   (when (and (or force telega-tdlib--chat-folder-tags-p) folders)
     (seq-doseq (folder folders)
       (let ((finfo (telega-folder--chat-folder-info folder)))
-        (telega-ins--with-face (telega-folder-tag-face folder finfo)
-          (telega-ins (telega-folder-format fmt-spec folder finfo)))
-        (telega-ins " ")))
+        ;; NOTE: `:color_id' == -1 means display as tag is disabled
+        ;; for this folder
+        (unless (eq (plist-get finfo :color_id) -1)
+          (telega-ins--with-face (telega-folder-tag-face folder finfo)
+            (telega-ins (telega-folder-format fmt-spec folder finfo)))
+          (telega-ins " "))))
     t))
 
 (defun telega-folders-insert-default (&optional fmt-spec)
@@ -165,17 +192,21 @@ If FORCE is specified, then `telega-tdlib--chat-folder-tags-p' is ignored."
 
 (defun telega-folder-create (folder-name icon-name chats)
   "Create new Telegram folder with name FOLDER-NAME."
-  (interactive (list (read-string "Create Folder with name: ")
-                     (when (y-or-n-p "Associate icon with the folder? ")
-                       (telega-completing-read-folder-icon-name
-                        "Folder icon name: "))
-                     (telega-completing-read-chat-list "Chats to add")))
+  (interactive
+   (list (read-string (concat (telega-i18n "lng_filters_create") ": "))
+         (when (y-or-n-p "Associate icon with the folder? ")
+           (telega-completing-read-folder-icon-name
+            "Folder icon name: "))
+         (telega-completing-read-chat-list
+          (concat (telega-i18n "lng_filters_add_chats") ": "))))
+
   ;; NOTE: Folder must contain at least 1 chat, otherwise error=400 is
   ;; returned
   (when chats
     (telega--createChatFolder
      (nconc (list :@type "chatFolder"
-                  :title folder-name
+                  :name (list :@type "chatFolderName"
+                              :text (telega-fmt-text folder-name))
                   :included_chat_ids (cl-map 'vector (telega--tl-prop :id) chats))
             (when icon-name
               (list :icon (list :@type "chatFolderIcon" :name icon-name)))))))
@@ -209,7 +240,9 @@ This won't delete any chat, just a folder."
                         "Folder icon name: "))))
   (let* ((folder-info (telega-folder--chat-folder-info folder-name))
          (tdlib-folder (telega--getChatFolder (plist-get folder-info :id))))
-    (plist-put tdlib-folder :title new-folder-name)
+    (plist-put tdlib-folder
+               :name (list :@type "chatFolderName"
+                           :text (telega-fmt-text new-folder-name)))
     (when new-icon-name
       (plist-put tdlib-folder
                  :icon (list :@type "chatFolderIcon" :name new-icon-name)))
@@ -262,9 +295,9 @@ You can add chat to multiple folders."
          (exc-cids (append (plist-get tdlib-folder :excluded_chat_ids) nil)))
     (cl-assert tdlib-folder)
 
-    ;; NOTE: Fix `tdlib-folder's `:title' in case it contains
+    ;; NOTE: Fix `tdlib-folder's `:name' in case it contains
     ;; surropagated pairs, to avoid error with code=400
-    (plist-put tdlib-folder :title folder-name)
+    (telega-fmt-text-desurrogate (telega--tl-get tdlib-folder :name :text))
 
     (if (memq chat-id inc-cids)
         (plist-put tdlib-folder :included_chat_ids
@@ -327,7 +360,7 @@ migrate your custom labels %S to Telegram Folders." custom-labels))))
 
 (defun telega-folders-settings--ins-folder (folder-info)
   "Inserter for the FOLDER-INFO in the Folders settings buffer."
-  (let ((folder-name (telega-tl-str folder-info :title)))
+  (let ((folder-name (telega-folder-name folder-info)))
     (telega-ins (telega-folder-format "%i %f" folder-name folder-info))
     (telega-ins--with-face 'telega-shadow
       (telega-ins " (")
@@ -339,8 +372,11 @@ migrate your custom labels %S to Telegram Folders." custom-labels))))
         (telega-ins-i18n "lng_filters_shareable_status"))
       (telega-ins ")"))
     (telega-ins--move-to-column 35)
-    (telega-ins--with-face (telega-folder-tag-face folder-name folder-info)
-      (telega-ins "■"))))
+    (if (eq (plist-get folder-info :color_id) -1)
+        (telega-ins--with-face 'telega-shadow
+          (telega-ins-i18n "lng_filters_tag_color_no"))
+      (telega-ins--with-face (telega-folder-tag-face folder-name folder-info)
+        (telega-ins "■")))))
 
 (defun telega-folders-settings--inserter (&rest _ignored)
   "Inserter for the folder settings."
diff --git a/telega-i18n.el b/telega-i18n.el
index 8972719..ccba6b8 100644
--- a/telega-i18n.el
+++ b/telega-i18n.el
@@ -26,6 +26,8 @@
 ;;; Code:
 (require 'telega-tdlib)
 
+(defvar telega-filters--dirty)
+(declare-function telega-filters--redisplay "telega-filter")
 (declare-function telega-root-aux-redisplay "telega-root" (&optional inserter))
 
 (defvar telega-i18n-month-names
diff --git a/telega-info.el b/telega-info.el
index d0a7689..a187f24 100644
--- a/telega-info.el
+++ b/telega-info.el
@@ -26,7 +26,7 @@
 ;;; Code:
 (require 'telega-core)
 (require 'telega-tdlib)
-(require 'telega-util)
+;(require 'telega-util)
 (require 'telega-filter)
 (require 'telega-chat)
 
diff --git a/telega-ins.el b/telega-ins.el
index a0ba32e..ed53335 100644
--- a/telega-ins.el
+++ b/telega-ins.el
@@ -130,7 +130,9 @@ 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."
+ommit image display if value is for this property is non-nil.
+If `:right-margin' property is specified, then display image at right
+margin.  In this case SLICE-NUM is ignored."
   ;; NOTE: IMG might be nil if `telega-use-images' is nil
   ;; See https://github.com/zevlg/telega.el/issues/274
   (if (or (not img) (not telega-use-images)
@@ -153,10 +155,12 @@ ommit image display if value is for this property is non-nil."
       (telega-ins--with-props
           (nconc (list 'rear-nonsticky '(display))
                  (unless (plist-get props :no-display-if)
-                   (list 'display
-                         (if slice
-                             (list (cons 'slice slice) img)
-                           img)))
+                   (let ((spec img))
+                     (when (plist-get props :right-margin)
+                       (setq spec (list '(margin right-margin) spec)))
+                     (when slice
+                       (setq spec (list (cons 'slice slice) spec)))
+                     (list 'display spec)))
                  props)
         (telega-ins
          (or (plist-get props :telega-text)
@@ -717,7 +721,7 @@ SHOW-DETAILS - non-nil to show photo details."
                      (if (plist-get msg :self_destruct_type) 'flames 'lock))))))
 
             ((and (plist-get msg-content :has_spoiler)
-                  (not (plist-get msg-content :telega-spoiler-removed)))
+                  (not (plist-get msg :telega-media-spoiler-removed)))
              (telega-ins--image-slices
                  (telega-spoiler-create-svg
                   (plist-get photo :minithumbnail)
@@ -726,13 +730,20 @@ SHOW-DETAILS - non-nil to show photo details."
                   telega-thumbnail-size-limits))
              (telega-ins "\n")
              (telega-ins--box-button (telega-i18n "lng_context_disable_spoiler")
-               :action #'telega-msg-remove-media-spoiler)
+               :action #'telega-msg-media-spoiler-toggle)
              (telega-ins " "))
 
             (t
              (telega-ins--image-slices
                  (telega-photo--image
-                  photo (or limits telega-photo-size-limits))))))
+                  photo (or limits telega-photo-size-limits)))
+             (when (plist-get msg-content :has_spoiler)
+               (cl-assert (plist-get msg :telega-media-spoiler-removed))
+               (telega-ins "\n")
+               (telega-ins--box-button (telega-i18n "lng_context_spoiler_effect")
+                 :action #'telega-msg-media-spoiler-toggle)
+               (telega-ins " "))
+             )))
     t))
 
 (defun telega-ins--audio (msg &optional audio how music-symbol)
@@ -857,7 +868,7 @@ and thumbnail are shown."
                 (setq ret t)))
 
              ((and (plist-get content :has_spoiler)
-                   (not (plist-get content :telega-spoiler-removed)))
+                   (not (plist-get msg :telega-media-spoiler-removed)))
               (telega-ins--image-slices
                   (telega-spoiler-create-svg
                    (plist-get video :minithumbnail)
@@ -866,7 +877,7 @@ and thumbnail are shown."
                    telega-thumbnail-size-limits))
               (telega-ins "\n")
               (telega-ins--box-button (telega-i18n "lng_context_disable_spoiler")
-                :action #'telega-msg-remove-media-spoiler)
+                :action #'telega-msg-media-spoiler-toggle)
               (telega-ins " ")
               (setq ret t))
 
@@ -878,6 +889,13 @@ and thumbnail are shown."
                       (telega-media--image
                        (cons video #'telega-video--create-image)
                        (cons thumb :file)))
+                  (when (plist-get content :has_spoiler)
+                    (cl-assert (plist-get msg :telega-media-spoiler-removed))
+                    (telega-ins "\n")
+                    (telega-ins--box-button
+                        (telega-i18n "lng_context_spoiler_effect")
+                      :action #'telega-msg-media-spoiler-toggle)
+                    (telega-ins " "))
                   (setq ret t)))))))
     ret))
 
@@ -1112,6 +1130,229 @@ and thumbnail are shown."
         (telega-ins (telega-tl-str game :description)))
       t)))
 
+(defun telega-ins--link-preview-description (msg link-preview palette)
+  "Insert LINK-PREVIEW description part."
+  (when-let ((sitename (telega-tl-str link-preview :site_name)))
+    (telega-ins--with-face (list (assq :foreground palette)
+                                 'telega-link-preview-sitename)
+      (telega-ins sitename))
+    (when telega-link-preview-show-author
+      (when-let ((author (telega-tl-str link-preview :author)))
+        (unless (equal sitename author)
+          (telega-ins--with-face 'telega-shadow
+            (telega-ins " --" author)))))
+    ;; NOTE: `(message-property :can_be_edited)' matcher makes TDLib
+    ;; request, so we put button only for outgoing messages,
+    ;; considering outgoing message can be edited
+    (when (telega-msg-match-p msg 'is-outgoing)
+      (telega-ins " ")
+      (telega-ins--text-button (telega-symbol 'button-close)
+        'face 'telega-link
+        :action #'telega-msg-disable-link-preview
+        'help-echo "telega: Press to disable link preview"))
+    (telega-ins "\n"))
+
+  (when-let ((title (telega-tl-str link-preview :title)))
+    (telega-ins--with-face 'telega-link-preview-title
+      (telega-ins title))
+    (telega-ins "\n"))
+  (when-let ((desc (telega-tl-str link-preview :description)))
+    (when (and telega-link-preview-description-limit
+               (> (length desc) telega-link-preview-description-limit))
+      (setq desc (truncate-string-to-width
+                  desc telega-link-preview-description-limit nil nil
+                  (when (> telega-link-preview-description-limit 0)
+                    telega-symbol-eliding))))
+    (when (telega-ins desc)
+      (telega-ins "\n")))
+  t)
+
+(defun telega-ins--link-preview-media (msg link-preview)
+  "Insert LINK-PREVIEW media part."
+  (let* ((lp-type (plist-get link-preview :type))
+         (lp-tl-type (telega--tl-type lp-type)))
+    (cl-ecase lp-tl-type
+      (linkPreviewTypeAlbum
+       (telega-ins "TODO: ALBUM")
+       (telega-ins "\n"))
+      (linkPreviewTypeAnimation
+       (when-let ((animation (plist-get lp-type :animation)))
+         (telega-ins--animation-msg msg animation)
+         (telega-ins "\n")))
+      ((linkPreviewTypeApp
+        linkPreviewTypeArticle
+        linkPreviewTypeChat
+        linkPreviewTypePhoto
+        linkPreviewTypeWebApp)
+       (when telega-link-preview-preview-size-limits
+         (when-let ((photo (plist-get lp-type :photo)))
+           (telega-ins--photo
+            photo msg telega-link-preview-preview-size-limits)
+           (telega-ins "\n"))))
+      (linkPreviewTypeAudio
+       (when-let ((audio (plist-get lp-type :audio)))
+         (when (telega-ins--audio msg audio 'thumbnail)
+           (telega-ins "\n"))
+         (telega-ins--with-face 'telega-shadow
+           (telega-ins--audio msg audio 'metainfo))
+         (telega-ins "\n")))
+      (linkPreviewTypeBackground
+       (when-let ((doc (plist-get lp-type :document)))
+         (telega-ins--document msg doc 'thumbnail)
+         (telega-ins "\n")))
+      ((linkPreviewTypeChannelBoost
+        linkPreviewTypeSupergroupBoost
+        linkPreviewTypeUser
+        linkPreviewTypeVideoChat)
+       (when-let ((chat-photo (plist-get lp-type :photo)))
+         (telega-ins--chat-photo chat-photo 'with-slices)
+         (telega-ins "\n")))
+      (linkPreviewTypeDocument
+       (when-let ((doc (plist-get lp-type :document)))
+         (telega-ins--document msg doc 'thumbnail)
+         (telega-ins "\n")
+         (telega-ins--with-face 'telega-shadow
+           (telega-ins--document msg doc 'metainfo))
+         (telega-ins "\n")))
+      ((linkPreviewTypeEmbeddedAnimationPlayer
+        linkPreviewTypeEmbeddedAudioPlayer
+        linkPreviewTypeEmbeddedVideoPlayer)
+       (when-let ((photo (plist-get lp-type :thumbnail)))
+         (when telega-link-preview-preview-size-limits
+           (telega-ins--photo
+            photo msg telega-link-preview-preview-size-limits)
+           (telega-ins "\n"))))
+      ((linkPreviewTypeSticker linkPreviewTypeStickerSet)
+       (when-let ((stickers
+                   (if (eq lp-tl-type 'linkPreviewTypeSticker)
+                       (when-let ((sticker (plist-get lp-type :sticker)))
+                         (list sticker))
+                     (cl-assert (eq lp-tl-type 'linkPreviewTypeStickerSet))
+                     (append (plist-get lp-type :stickers) nil)))
+                  (nslices 1))
+         (seq-doseq (sticker stickers)
+           (when (telega-custom-emoji-sticker-p sticker)
+             (telega-custom-emoji--ensure sticker))
+           (when-let ((sslices (car (telega-sticker-size sticker))))
+             (when (> sslices nslices)
+               (setq nslices sslices))))
+
+         (dotimes (slice-num nslices)
+           (seq-doseq (sticker stickers)
+             (telega-ins--image (telega-sticker--image sticker) slice-num)
+             (telega-ins " "))
+           (telega-ins "\n"))))
+      (linkPreviewTypeStory
+       (telega-ins--story-content
+        (telega-story-get (plist-get lp-type :story_sender_chat_id)
+                          (plist-get lp-type :story_id) 'offline)
+        msg)
+       (telega-ins "\n"))
+      (linkPreviewTypeVideo
+       (when-let ((video (plist-get lp-type :video)))
+         (telega-ins--video msg video 'thumbnail)
+         (telega-ins "\n")
+         (telega-ins--with-face 'telega-shadow
+           (telega-ins--video msg video 'metainfo))
+         (telega-ins "\n")))
+      (linkPreviewTypeVideoNote
+       (when-let ((video-note (plist-get lp-type :video_note)))
+         (telega-ins--video-note msg video-note)
+         (telega-ins "\n")))
+      (linkPreviewTypeVoiceNote
+       (when-let ((voice-note (plist-get lp-type :voice_note)))
+         (telega-ins--voice-note msg voice-note)
+         (telega-ins "\n")))
+      ((linkPreviewTypeMessage
+        linkPreviewTypeInvoice
+        linkPreviewTypePremiumGiftCode
+        linkPreviewTypeShareableChatFolder
+        linkPreviewTypeTheme
+        linkPreviewTypeExternalAudio ; TODO
+        linkPreviewTypeExternalVideo ; TODO
+        linkPreviewTypeUnsupported)
+       ;; no-op
+       )))
+  t)
+
+(defun telega-ins--link-preview-button (_msg link-preview)
+  "Insert button for LINK-PREVIEW."
+  ;; [View Button]
+  ;; ("telegram_channel"
+  ;;  (telega-i18n "lng_view_button_channel"))
+  ;; ("telegram_channel_boost"
+  ;;  (telega-i18n "lng_view_button_boost"))
+  ;; ((or "telegram_chat" "telegram_megagroup")
+  ;;  (telega-i18n "lng_view_button_group"))
+  ;; ("telegram_bot"
+  ;;  (telega-i18n "lng_view_button_bot"))
+  ;; ("telegram_bot_app"
+  ;;  (telega-i18n "lng_view_button_bot_app"))
+  ;; (linkPreviewTypeMessage
+  ;;  (telega-i18n "lng_view_button_message"))
+  ;; ("telegram_background"
+  ;;  (telega-i18n "lng_view_button_background"))
+  ;; ("telegram_theme"
+  ;;  (telega-i18n "lng_view_button_theme"))
+  ;; ("telegram_user"
+  ;;  (telega-i18n "lng_view_button_user"))
+  ;; ("telegram_channel_request"
+  ;;  (telega-i18n "lng_view_button_request_join"))
+  ;; ("telegram_livestream"
+  ;;  (telega-i18n "lng_view_button_voice_chat_channel"))
+  ;; ("telegram_voicechat"
+  ;;  "JOIN AS LISTENER")
+  ;; ("telegram_chatlist"
+  ;;  "VIEW CHAT LIST")
+  ;; ("telegram_story"
+  ;;  (telega-i18n "lng_view_button_story"))
+  ;; ("telegram_giftcode"
+  ;;  (telega-i18n "lng_view_button_giftcode"))
+  (let* ((lp-type (plist-get link-preview :type))
+         (iv-button-p
+          (not (telega-zerop
+                (plist-get link-preview :instant_view_version))))
+         (button-label
+          (if iv-button-p
+              (telega-i18n "lng_view_button_iv")
+            (cl-case (telega--tl-type lp-type)
+              (linkPreviewTypeBackground
+               (telega-i18n "lng_view_button_background"))
+              (linkPreviewTypeWebApp
+               (telega-i18n "lng_view_button_bot_app"))
+              (linkPreviewTypeMessage
+               (telega-i18n "lng_view_button_message"))
+              (linkPreviewTypeChat
+               (if (plist-get lp-type :creates_join_request)
+                   (telega-i18n "lng_view_button_request_join")
+                 (cl-case (telega--tl-type (plist-get lp-type :type))
+                   (inviteLinkChatTypeChannel
+                    (telega-i18n "lng_view_button_channel"))
+                   ((inviteLinkChatTypeBasicGroup
+                     inviteLinkChatTypeSupergroup)
+                    (telega-i18n "lng_view_button_group")))))
+              (linkPreviewTypeChannelBoost
+               (telega-i18n "lng_view_button_boost"))
+              (linkPreviewTypeStickerSet
+               (telega-i18n "lng_view_button_emojipack"))
+              (linkPreviewTypeStory
+               (telega-i18n "lng_view_button_story"))
+              (linkPreviewTypeTheme
+               (telega-i18n "lng_view_button_theme"))
+              (linkPreviewTypeUser
+               (telega-i18n (if (plist-get lp-type :is_bot)
+                                "lng_view_button_bot"
+                              "lng_view_button_user")))))))
+    (when button-label
+      (telega-ins--box-button
+          (concat "   " (when iv-button-p
+                          (telega-symbol 'lightning))
+                  (upcase button-label)
+                  "   ")
+        'action 'telega-msg-button--action)
+      (telega-ins "\n")))
+  t)
+
 (defun telega-ins--link-preview (msg &optional link-preview)
   "Insert LINK-PREVIEW preview.
 Return `non-nil' if LINK-PREVIEW has been inserted."
@@ -1125,217 +1366,17 @@ Return `non-nil' if LINK-PREVIEW has been inserted."
                        origin-sender
                      (telega-msg-sender msg)))
            (telega-palette-context 'link-preview)
-           (palette (telega-msg-sender-palette sender)))
+           (palette (telega-msg-sender-palette sender))
+           (media-above-p
+            (plist-get link-preview :show_media_above_description)))
       (telega-ins--with-outline-palette palette
-        (when-let ((sitename (telega-tl-str link-preview :site_name)))
-          (telega-ins--with-face (list (assq :foreground palette)
-                                       'telega-link-preview-sitename)
-            (telega-ins sitename))
-          (when telega-link-preview-show-author
-            (when-let ((author (telega-tl-str link-preview :author)))
-              (unless (equal sitename author)
-                (telega-ins--with-face 'telega-shadow
-                  (telega-ins " --" author)))))
-          (when (telega-msg-match-p msg '(prop :can_be_edited))
-            (telega-ins " ")
-            (telega-ins--text-button (telega-symbol 'button-close)
-              'face 'telega-link
-              :action #'telega-msg-disable-link-preview
-              'help-echo "telega: Press to disable link preview"))
-          (telega-ins "\n"))
-
-        (when-let ((title (telega-tl-str link-preview :title)))
-          (telega-ins--with-face 'telega-link-preview-title
-            (telega-ins title))
-          (telega-ins "\n"))
-        (when-let ((desc (telega-tl-str link-preview :description)))
-          (when (and telega-link-preview-description-limit
-                     (> (length desc) telega-link-preview-description-limit))
-            (setq desc (truncate-string-to-width
-                        desc telega-link-preview-description-limit nil nil
-                        (when (> telega-link-preview-description-limit 0)
-                          telega-symbol-eliding))))
-          (when (telega-ins desc)
-            (telega-ins "\n")))
-
-        ;; Link Preview specific content
-        (let* ((lp-type (plist-get link-preview :type))
-               (lp-tl-type (telega--tl-type lp-type)))
-          (cl-ecase lp-tl-type
-            (linkPreviewTypeAlbum
-             (telega-ins "TODO: ALBUM"))
-            (linkPreviewTypeAnimation
-             (when-let ((animation (plist-get lp-type :animation)))
-               (telega-ins--animation-msg msg animation)
-               (telega-ins "\n")))
-            ((linkPreviewTypeApp
-              linkPreviewTypeArticle
-              linkPreviewTypeChat
-              linkPreviewTypePhoto
-              linkPreviewTypeWebApp)
-             (when telega-link-preview-preview-size-limits
-               (when-let ((photo (plist-get lp-type :photo)))
-                 (telega-ins--photo
-                  photo msg telega-link-preview-preview-size-limits)
-                 (telega-ins "\n"))))
-            (linkPreviewTypeAudio
-             (when-let ((audio (plist-get lp-type :audio)))
-               (when (telega-ins--audio msg audio 'thumbnail)
-                 (telega-ins "\n"))
-               (telega-ins--with-face 'telega-shadow
-                 (telega-ins--audio msg audio 'metainfo))
-               (telega-ins "\n")))
-            (linkPreviewTypeBackground
-             (when-let ((doc (plist-get lp-type :document)))
-               (telega-ins--document msg doc 'thumbnail)
-               (telega-ins "\n")))
-            ((linkPreviewTypeChannelBoost
-              linkPreviewTypeSupergroupBoost
-              linkPreviewTypeUser
-              linkPreviewTypeVideoChat)
-             (when-let ((chat-photo (plist-get lp-type :photo)))
-               (telega-ins--chat-photo chat-photo 'with-slices)
-               (telega-ins "\n")))
-            (linkPreviewTypeDocument
-             (when-let ((doc (plist-get lp-type :document)))
-               (telega-ins--document msg doc 'thumbnail)
-               (telega-ins "\n")
-               (telega-ins--with-face 'telega-shadow
-                 (telega-ins--document msg doc 'metainfo))
-               (telega-ins "\n")))
-            ((linkPreviewTypeEmbeddedAnimationPlayer
-              linkPreviewTypeEmbeddedAudioPlayer
-              linkPreviewTypeEmbeddedVideoPlayer)
-             (when-let ((photo (plist-get lp-type :thumbnail)))
-               (when telega-link-preview-preview-size-limits
-                 (telega-ins--photo
-                  photo msg telega-link-preview-preview-size-limits))))
-            ((linkPreviewTypeSticker linkPreviewTypeStickerSet)
-             (when-let ((stickers
-                         (if (eq lp-tl-type 'linkPreviewTypeSticker)
-                             (when-let ((sticker (plist-get lp-type :sticker)))
-                               (list sticker))
-                           (cl-assert (eq lp-tl-type 'linkPreviewTypeStickerSet))
-                           (append (plist-get lp-type :stickers) nil)))
-                        (nslices 1))
-               (seq-doseq (sticker stickers)
-                 (when (telega-custom-emoji-sticker-p sticker)
-                   (telega-custom-emoji--ensure sticker))
-                 (when-let ((sslices (car (telega-sticker-size sticker))))
-                   (when (> sslices nslices)
-                     (setq nslices sslices))))
-
-               (dotimes (slice-num nslices)
-                 (seq-doseq (sticker stickers)
-                   (telega-ins--image (telega-sticker--image sticker) slice-num)
-                   (telega-ins " "))
-                 (telega-ins "\n"))))
-            (linkPreviewTypeStory
-             (telega-ins--story-content
-              (telega-story-get (plist-get lp-type :story_sender_chat_id)
-                                (plist-get lp-type :story_id) 'offline)
-              msg)
-             (telega-ins "\n"))
-            (linkPreviewTypeVideo
-             (when-let ((video (plist-get lp-type :video)))
-               (telega-ins--video msg video 'thumbnail)
-               (telega-ins "\n")
-               (telega-ins--with-face 'telega-shadow
-                 (telega-ins--video msg video 'metainfo))
-               (telega-ins "\n")))
-            (linkPreviewTypeVideoNote
-             (when-let ((video-note (plist-get lp-type :video_note)))
-               (telega-ins--video-note msg video-note)
-               (telega-ins "\n")))
-            (linkPreviewTypeVoiceNote
-             (when-let ((voice-note (plist-get lp-type :voice_note)))
-               (telega-ins--voice-note msg voice-note)
-               (telega-ins "\n")))
-            ((linkPreviewTypeMessage
-              linkPreviewTypeInvoice
-              linkPreviewTypePremiumGiftCode
-              linkPreviewTypeShareableChatFolder
-              linkPreviewTypeTheme
-              linkPreviewTypeExternalAudio ; TODO
-              linkPreviewTypeExternalVideo ; TODO
-              linkPreviewTypeUnsupported)
-             ;; no-op
-             ))
-
-          ;; [View Button]
-    ;; ("telegram_channel"
-    ;;  (telega-i18n "lng_view_button_channel"))
-    ;; ("telegram_channel_boost"
-    ;;  (telega-i18n "lng_view_button_boost"))
-    ;; ((or "telegram_chat" "telegram_megagroup")
-    ;;  (telega-i18n "lng_view_button_group"))
-    ;; ("telegram_bot"
-    ;;  (telega-i18n "lng_view_button_bot"))
-    ;; ("telegram_bot_app"
-    ;;  (telega-i18n "lng_view_button_bot_app"))
-    ;; (linkPreviewTypeMessage
-    ;;  (telega-i18n "lng_view_button_message"))
-    ;; ("telegram_background"
-    ;;  (telega-i18n "lng_view_button_background"))
-    ;; ("telegram_theme"
-    ;;  (telega-i18n "lng_view_button_theme"))
-    ;; ("telegram_user"
-    ;;  (telega-i18n "lng_view_button_user"))
-    ;; ("telegram_channel_request"
-    ;;  (telega-i18n "lng_view_button_request_join"))
-    ;; ("telegram_livestream"
-    ;;  (telega-i18n "lng_view_button_voice_chat_channel"))
-    ;; ("telegram_voicechat"
-    ;;  "JOIN AS LISTENER")
-    ;; ("telegram_chatlist"
-    ;;  "VIEW CHAT LIST")
-    ;; ("telegram_story"
-    ;;  (telega-i18n "lng_view_button_story"))
-    ;; ("telegram_giftcode"
-    ;;  (telega-i18n "lng_view_button_giftcode"))
-          (let* ((iv-button-p
-                  (not (telega-zerop
-                        (plist-get link-preview :instant_view_version))))
-                 (button-label
-                  (if iv-button-p
-                      (telega-i18n "lng_view_button_iv")
-                    (cl-case lp-tl-type
-                      (linkPreviewTypeBackground
-                       (telega-i18n "lng_view_button_background"))
-                      (linkPreviewTypeWebApp
-                       (telega-i18n "lng_view_button_bot_app"))
-                      (linkPreviewTypeMessage
-                       (telega-i18n "lng_view_button_message"))
-                      (linkPreviewTypeChat
-                       (if (plist-get lp-type :creates_join_request)
-                           (telega-i18n "lng_view_button_request_join")
-                         (cl-case (telega--tl-type (plist-get lp-type :type))
-                           (inviteLinkChatTypeChannel
-                            (telega-i18n "lng_view_button_channel"))
-                           ((inviteLinkChatTypeBasicGroup
-                             inviteLinkChatTypeSupergroup)
-                            (telega-i18n "lng_view_button_group")))))
-                      (linkPreviewTypeChannelBoost
-                       (telega-i18n "lng_view_button_boost"))
-                      (linkPreviewTypeStickerSet
-                       (telega-i18n "lng_view_button_emojipack"))
-                      (linkPreviewTypeStory
-                       (telega-i18n "lng_view_button_story"))
-                      (linkPreviewTypeTheme
-                       (telega-i18n "lng_view_button_theme"))
-                      (linkPreviewTypeUser
-                       (telega-i18n (if (plist-get lp-type :is_bot)
-                                        "lng_view_button_bot"
-                                      "lng_view_button_user")))))))
-            (when button-label
-              (telega-ins--box-button
-                  (concat "   " (when iv-button-p
-                                  (telega-symbol 'lightning))
-                          (upcase button-label)
-                          "   ")
-                'action 'telega-msg-button--action)
-              (telega-ins "\n"))))))
-    t))
+        (when media-above-p
+          (telega-ins--link-preview-media msg link-preview))
+        (telega-ins--link-preview-description msg link-preview palette)
+        (unless media-above-p
+          (telega-ins--link-preview-media msg link-preview))
+        (telega-ins--link-preview-button msg link-preview))
+      t)))
 
 (defun telega-ins--location (location)
   "Inserter for the LOCATION."
@@ -1609,15 +1650,17 @@ Return `non-nil' if LINK-PREVIEW has been inserted."
       (telega-ins ", ")
       (telega-ins--with-face 'error
         (telega-ins-i18n "lng_polls_closed")))
-    (when (and (not closed-p) (plist-get msg :can_be_edited))
+    (when (and (not closed-p)
+               ;; NOTE: `(message-property :can_be_edited)' makes
+               ;; TDLib request, so we put Stop/Close button only for
+               ;; outgoing poll messages
+               (telega-msg-match-p msg 'is-outgoing))
       (telega-ins " ")
       (telega-ins--box-button
           (if quiz-p
               "Stop Quiz"
             (telega-i18n "lng_polls_stop"))
-        'action (lambda (_ignored)
-                  (when (yes-or-no-p (telega-i18n "lng_polls_stop_warning"))
-                    (telega--stopPoll msg)))))
+        :action #'telega-msg-stop-poll))
     (telega-ins "\n")
 
     ;; Question and options
@@ -2040,6 +2083,7 @@ If NO-THUMBNAIL-P is non-nil, then do not insert thumbnail."
           messageForumTopicCreated
           messageForumTopicEdited
           messageForumTopicIsClosedToggled
+          messageForumTopicIsHiddenToggled
           messageGiveawayCreated
           messageGiveawayCompleted
           messageGiftedPremium
@@ -2290,6 +2334,12 @@ Special messages are determined with `telega-msg-special-p'."
                           "lng_action_topic_reopened")
          :topic (telega-ins--as-string
                  (telega-ins--special-replied-msg msg))))
+      (messageForumTopicIsHiddenToggled
+       (telega-ins sender-name (telega-symbol 'right-arrow))
+       (telega-ins-i18n (if (plist-get content :is_hidden)
+                            "lng_action_topic_hidden"
+                          "lng_action_topic_unhidden")
+         :topic (concat (telega-symbol 'topic) "General")))
       (messageForumTopicEdited
        (let* ((edit-icon-p (plist-get content :edit_icon_custom_emoji_id))
               (new-icon-sticker (when edit-icon-p
@@ -2731,12 +2781,14 @@ performance."
 
 (defun telega-ins--message-date-and-status (msg)
   "Insert message's date and outgoing status."
-  ;; NOTE: telegaInternal messages has no `:date' property
+  ;; NOTE:
+  ;; - telegaInternal messages has no `:date' property
+  ;; - outgoing messages always has `:date' property
   (when-let ((date (or (telega--tl-get msg :scheduling_state :send_date)
                        (plist-get msg :date))))
     (telega-ins--date date)
-    ;; NOTE: outgoing messages always has `:date' property
-    (telega-ins--outgoing-status msg)
+    (or (telega-ins--outgoing-status msg)
+        (telega-ins (telega-symbol 'no-checkmark)))
     t))
 
 (defun telega-ins--message-header (msg &optional msg-chat msg-sender
@@ -2746,11 +2798,14 @@ MSG-CHAT - Chat for which to insert message header.
 MSG-SENDER - Sender of the message.
 If ADDON-INSERTER function is specified, it is called with one
 argument - MSG to insert additional information after header."
-  (let* ((date-and-status (telega-ins--as-string
-                           (when telega-msg-heading-with-date-and-status
-                             (telega-ins--message-date-and-status msg))))
+  (let* ((date-and-status
+          (when (eq telega-msg-heading-trail 'date-and-status)
+            (telega-ins--as-string
+             (telega-ins--message-date-and-status msg))))
          (dwidth (- telega-chat-fill-column
-                    (string-width date-and-status)))
+                    (if (stringp date-and-status)
+                        (string-width date-and-status)
+                      0)))
          (chat (or msg-chat (telega-msg-chat msg)))
          (sender (or msg-sender (telega-msg-sender msg)))
          (telega-palette-context 'msg-header)
@@ -2883,9 +2938,9 @@ argument - MSG to insert additional information after header."
                       :help-echo "Show topic info")
               (telega-ins topic-title))))
 
-        (when date-and-status
-          (telega-ins--move-to-column dwidth)
-          (telega-ins date-and-status))
+        (when telega-msg-heading-trail
+          (telega-ins--move-to-column dwidth))
+        (telega-ins date-and-status)
 
         (telega-ins "\n")))))
 
@@ -3344,7 +3399,7 @@ ADDON-HEADER-INSERTER is passed directly to `telega-ins--message-header'."
          (telega-ins--msg-comments msg))
         ))
 
-    (unless telega-msg-heading-with-date-and-status
+    (unless (eq telega-msg-heading-trail 'date-and-status)
       (let* ((date-and-status (telega-ins--as-string
                                (telega-ins--message-date-and-status msg)))
              (dswidth (string-width date-and-status))
@@ -3616,7 +3671,8 @@ If SHORT-P is non-nil then use short version."
 If REMOVE-CAPTION is specified, then do not insert caption."
   (declare (indent 1))
   (telega-ins--one-lined
-   (let ((telega-msg--current msg)
+   (let ((telega-inhibit-telega-display-by t)
+         (telega-msg--current msg)
          (content (or content (plist-get msg :content))))
      (cl-case (telega--tl-type content)
        (messageText
@@ -3780,9 +3836,11 @@ If REMOVE-CAPTION is specified, then do not insert caption."
   (let* ((date-and-status (telega-ins--as-string
                            (telega-ins--message-date-and-status msg)))
          (dwidth (- telega-root-fill-column (string-width date-and-status))))
-    (telega-ins--with-attrs (list :align 'left
-                                  :max (- dwidth (telega-current-column))
-                                  :elide t)
+    ;; (telega-ins--with-attrs (list :max (- dwidth (telega-current-column))
+    ;;                               :align 'left
+    ;;                               :elide t)
+    (telega-ins--with-eliding (list :max (- dwidth (telega-current-column))
+                                    :face 'telega-shadow)
       ;; NOTE: Do not show username for:
       ;;;  - Saved Messages
       ;;;  - Channel posts
@@ -3874,7 +3932,7 @@ If REMOVE-CAPTION is specified, then do not insert caption."
 (defun telega-ins--chopic-pinned-trail (chopic)
   "Trail inserter for pin status of the chat or topic CHOPIC."
   (when (if (telega-chat-p chopic)
-            (plist-get (telega-chat-position chopic) :is_pinned)
+            (telega-chat-match-p chopic 'is-pinned)
           (plist-get chopic :is_pinned))
     (telega-ins (telega-symbol 'pin))))
 
@@ -3939,9 +3997,11 @@ Return t."
       (telega-ins (car brackets)))
 
     ;; Title
-    (telega-ins--with-attrs (list :max title-width
-                                  :align 'left
-                                  :elide t)
+    ;; (telega-ins--with-attrs (list :max title-width
+    ;;                               :align 'left
+    ;;                               :elide t)
+    (telega-ins--with-eliding (list :max title-width
+                                    :face 'telega-shadow)
       (when-let* ((folders-insexp (plist-get fmt-plist :with-folders-insexp))
                   (telega-chat-folders
                    (seq-difference (telega-chat-folders chat)
@@ -4147,7 +4207,7 @@ Return non-nil if restrictions has been inserted."
 (defun telega-ins--root-msg-call (msg)
   "Inserter for call message MSG in rootbuf."
   (let ((telega-chat-fill-column telega-root-fill-column)
-        (telega-msg-heading-with-date-and-status t))
+        (telega-msg-heading-trail 'date-and-status))
     (telega-ins--message msg
       :sender (when (telega-msg-match-p msg 'outgoing)
                 (telega-chat-user (telega-msg-chat msg))))))
@@ -4457,9 +4517,8 @@ If REMOVE-CAPTION is specified, then do not insert caption."
       (telega-ins (telega-symbol 'topic))
       (telega-ins--topic-icon topic)
       (telega-ins (car telega-symbol-topic-brackets))
-      (telega-ins--with-attrs (list :max title-width
-                                    :align 'left
-                                    :elide t)
+      (telega-ins--with-eliding (list :max title-width
+                                      :face 'telega-shadow)
         (telega-ins--topic-title topic))
 
       (cond ((and curr-column title-width)
diff --git a/telega-match.el b/telega-match.el
index 289f6f2..efd95d0 100644
--- a/telega-match.el
+++ b/telega-match.el
@@ -55,9 +55,9 @@
 ;;     Matches channel's text messages containing "hello" word.
 
 ;;; Code:
-(require 'telega-chat)
-(require 'telega-msg)
-(require 'telega-user)
+;; (require 'telega-chat)
+;; (require 'telega-msg)
+;; (require 'telega-user)
 (require 'telega-info)
 
 (defvar telega-temex-remap-list nil
@@ -105,6 +105,7 @@ Use `define-telega-matcher' to define new matchers."
 (defun telega-match-p (object temex)
   "Return non-nil if TEMEX matches OBJECT.
 Takes `telega-temex-remap-alist' into account."
+  (declare (indent 1))
   (when temex
     ;; Try whole TEMEX remapping
     (setq temex (or (telega-match--temex-remap telega-temex-match-prefix temex)
@@ -401,8 +402,12 @@ PERM could be one of in `telega-chat--chat-permissions' list or in
 ;; - verified, {{{where-is(telega-filter-by-verified,telega-root-mode-map)}}} ::
 ;;   {{{temexdoc(chat, verified, 2)}}}
 (define-telega-matcher chat verified (chat)
-  "Matches if chat is verified."
-  (plist-get (telega-chat--info chat 'locally) :is_verified))
+  "Matches if chat is verified.
+Return verification status if CHAT is verified."
+  (when-let ((verification-status
+              (plist-get (telega-chat--info chat t) :verification_status)))
+    (when (plist-get verification-status :is_verified)
+      verification-status)))
 
 ;;; ellit-org: chat-temex
 ;; - (restriction ~SUFFIX-LIST~...), {{{where-is(telega-filter-by-restriction,telega-root-mode-map)}}} ::
@@ -589,10 +594,13 @@ LIST-NAME is `main' or `archive' symbol, or string naming Chat Folder."
 ;; - fake-or-scam ::
 ;;   {{{temexdoc(chat, fake-or-scam, 2)}}}
 (define-telega-matcher chat fake-or-scam (chat)
-  "Matches if chat is fake or scam user or group."
-  (let ((info (telega-chat--info chat)))
-    (or (plist-get info :is_scam)
-        (plist-get info :is_fake))))
+  "Matches if chat is fake or scam user or group.
+Return verification status if chat is fake or scam."
+  (when-let ((verification-status
+              (plist-get (telega-chat--info chat t) :verification_status)))
+    (when (or (plist-get verification-status :is_scam)
+              (plist-get verification-status :is_fake))
+      verification-status)))
 
 ;;; ellit-org: chat-temex
 ;; - (has-video-chat [ ~NON-EMPTY~ ]) ::
@@ -787,6 +795,13 @@ By default N is 1."
     (let ((info (telega-chat--info chat 'local)))
       (>= (or (plist-get info :boost_level) 0) (or n 1)))))
 
+;;; ellit-org: chat-temex
+;; - is-pinned ::
+;;   {{{temexdoc(chat, is-pinned, 2)}}}
+(define-telega-matcher chat is-pinned (chat)
+  "Matches if chat is pinned."
+  (plist-get (telega-chat-position chat) :is_pinned))
+
 
 ;;; User Temexes
 ;;; ellit-org: user-temex
diff --git a/telega-media.el b/telega-media.el
index a7c7b52..2bb418d 100644
--- a/telega-media.el
+++ b/telega-media.el
@@ -273,11 +273,10 @@ By default LIMITS is `telega-photo-size-limits'."
   (unless limits
     (setq limits telega-photo-size-limits))
 
-  (let ((lim-xwidth (telega-chars-xwidth (nth 2 limits)))
-        (lim-xheight (telega-chars-xheight (nth 3 limits)))
+  (let ((lim-tw (telega-chars-xwidth (nth 2 limits)))
+        (lim-th (telega-chars-xheight (nth 3 limits)))
         ret)
-    ;; NOTE: `reverse' is used to start from highes sizes
-    (seq-doseq (thumb (reverse (plist-get photo :sizes)))
+    (seq-doseq (thumb (plist-get photo :sizes))
       (let* ((thumb-file (telega-file--renew thumb :photo))
              (tw (plist-get thumb :width))
              (th (plist-get thumb :height)))
@@ -285,22 +284,23 @@ By default LIMITS is `telega-photo-size-limits'."
         ;; if size does not fits
         ;; Select sizes larger then limits, because downscaling works
         ;; betten then upscaling
-
         (when (and (or (telega-file--downloaded-p thumb-file)
                        (and (telega-file--can-download-p thumb-file)
                             (not (telega-file--downloaded-p
                                   (plist-get ret :photo)))))
-                   (or (not ret)
-                       (and (>= tw lim-xwidth)
-                            (>= th lim-xheight)))
 
-                   ;; NOTE: prefer thumbs with `:progressive_sizes' set
                    (or (not ret)
-                       (and (telega-file--can-download-p (plist-get ret :photo))
-                            (not (plist-get ret :progressive_sizes))
-                            (plist-get thumb :progressive_sizes)))
-                   )
-          (setq ret thumb))))
+                       (and (> tw lim-tw)
+                            (> th lim-th))
+                       ;; NOTE: prefer thumbs with `:progressive_sizes'
+                       (and (= tw lim-tw)
+                            (= tw lim-tw)
+                            (not (seq-empty-p
+                                  (plist-get thumb :progressive_sizes)))
+                            (seq-empty-p (plist-get ret :progressive_sizes)))))
+          (setq ret thumb
+                lim-tw tw
+                lim-th th))))
 
     (or ret
         ;; Fallback to the very first thumbnail
@@ -395,36 +395,43 @@ CHEIGHT is the height in chars to use (default=1).
 PROGRESSIVE-SIZES specifies list of jpeg's progressive file sizes."
   (unless cheight
     (setq cheight 1))
-  (if (or (telega-file--downloaded-p file)
-          (and progressive-sizes
-               (>= (telega-file--downloaded-size file)
-                   (car progressive-sizes))))
-      (let ((image-filename (telega--tl-get file :local :path)))
-        ;; NOTE: Handle case when file is partially downloaded and
-        ;; some progressive size is reached. In this case create
-        ;; temporary image file writing corresponding progress bytes
-        ;; into it and displaying it
-        (unless (telega-file--downloaded-p file)
-          (let* ((tmp-size (cl-find (telega-file--downloaded-size file)
-                                    (reverse progressive-sizes) :test #'>=))
-                 (tmp-fname (expand-file-name
-                             (format "%s-%d.%s"
-                                     (file-name-base image-filename)
-                                     tmp-size
-                                     (file-name-extension image-filename))
-                             telega-temp-dir))
-                 (coding-system-for-write 'binary))
-            (unless (file-exists-p tmp-fname)
-              (telega-debug "Creating progressive img: %d / %S -> %s"
-                            (telega-file--downloaded-size file)
-                            progressive-sizes
-                            tmp-fname)
-              (with-temp-buffer
-                (set-buffer-multibyte nil)
-                (insert-file-contents-literally image-filename)
-                (write-region 1 (+ 1 tmp-size) tmp-fname nil 'quiet)))
-            (setq image-filename tmp-fname)))
-
+  (let* ((local-file (plist-get file :local))
+         (partial-size
+          (when (and (not (seq-empty-p progressive-sizes))
+                     (telega-file--downloading-p file)
+                     (zerop (plist-get local-file :download_offset))
+                     (>= (plist-get local-file :downloaded_prefix_size)
+                         (seq-first progressive-sizes)))
+            (cl-find (plist-get local-file :downloaded_prefix_size)
+                     (seq-reverse progressive-sizes) :test #'>=)))
+         (image-filename
+          (cond ((telega-file--downloaded-p file)
+                 (plist-get local-file :path))
+                (partial-size
+                 ;; NOTE: Handle case when file is partially
+                 ;; downloaded and some progressive size is
+                 ;; reached. In this case create temporary image file
+                 ;; writing corresponding progress bytes into it and
+                 ;; displaying it
+                 (let* ((tl-filepath (plist-get local-file :path))
+                        (tmp-fname (expand-file-name
+                                    (format "%s-%d.%s"
+                                            (file-name-base tl-filepath)
+                                            partial-size
+                                            (file-name-extension tl-filepath))
+                                    telega-temp-dir))
+                       (coding-system-for-write 'binary))
+                   (unless (file-exists-p tmp-fname)
+                     (telega-debug "Creating progressive img: %d / %S -> %s"
+                                   (telega-file--downloaded-size file)
+                                   progressive-sizes
+                                   tmp-fname)
+                     (with-temp-buffer
+                       (set-buffer-multibyte nil)
+                       (insert-file-contents-literally tl-filepath)
+                       (write-region 1 (+ 1 partial-size) tmp-fname nil 'quiet)))
+                   tmp-fname)))))
+    (if image-filename
         (telega-create-image
             (if (string-empty-p image-filename)
                 (telega-etc-file "non-existing.jpg")
@@ -433,9 +440,8 @@ PROGRESSIVE-SIZES specifies list of jpeg's progressive file sizes."
           :height (telega-ch-height cheight)
           :telega-nslices cheight
           :scale 1.0
-          :ascent 'center))
-
-    (telega-media--progress-svg file width height cheight)))
+          :ascent 'center)
+      (telega-media--progress-svg file width height cheight))))
 
 (defun telega-minithumb--create-image (minithumb cheight)
   "Create image and use MINITHUMB minithumbnail as data."
@@ -661,6 +667,7 @@ Default is `:telega-image'."
       ;; and https://t.me/emacs_telega/33101
       (when telega-use-images
         (ignore-errors (image-flush cached-image)))
+
       (plist-put (car obj-spec) (or cache-prop :telega-image) cached-image))
     cached-image))
 
@@ -688,6 +695,62 @@ Default is `:telega-image'."
                 (force-window-update)))))))
     cached-image))
 
+(cl-defun telega-media--image-updateNEW (obj)
+  "Update media image for the OBJ."
+  (let* ((media-spec (plist-get obj :telega-media-spec))
+         (image-create-func (plist-get media-spec :image-create-func))
+         (cache-prop (plist-get media-spec :cache-prop))
+         (cached-image (plist-get obj cache-prop))
+         (simage (funcall image-create-func obj)))
+    ;; NOTE: Sometimes `create' function returns nil results
+    ;; Probably, because Emacs has no access to the image file while
+    ;; trying to convert sticker from webp to png
+    (when (and telega-use-images (not simage))
+      (error "telega: [BUG] Image create (%S %S) -> nil"
+             image-create-func obj))
+
+    (unless (equal cached-image simage)
+      ;; NOTE: We call `image-flush' because only filename in
+      ;; the image spec can be changed (during animation for
+      ;; example), and image caching won't notice this because
+      ;; `(sxhash cached-image)' and `(sxhash simage)' might
+      ;; return the same!
+      ;;
+      ;; We do it under `ignore-errors' to avoid any image related errors
+      ;; see https://github.com/zevlg/telega.el/issues/349
+      ;; and https://t.me/emacs_telega/33101
+      (when telega-use-images
+        (ignore-errors (image-flush cached-image)))
+
+      ;; Update the image
+      (if cached-image
+          (setcdr cached-image (cdr simage))
+        (setq cached-image simage))
+
+      (plist-put obj cache-prop cached-image))
+    cached-image))
+
+(cl-defun telega-media--imageNEW (obj &key create-image-function cheight
+                                      cache-prop)
+  (let ((cached-image (plist-get (car obj-spec) (or cache-prop :telega-image))))
+    (when (or force-update (not cached-image))
+      (let ((media-file (telega-file--renew (car file-spec) (cdr file-spec))))
+        ;; First time image is created or update is forced
+        (setq cached-image
+              (telega-media--image-update obj-spec media-file cache-prop))
+
+        ;; Possibly initiate file downloading
+        (when (and telega-use-images
+                   (or (telega-file--need-download-p media-file)
+                       (telega-file--downloading-p media-file)))
+          (telega-file--download media-file
+            :update-callback
+            (lambda (dfile)
+              (when (telega-file--downloaded-p dfile)
+                (telega-media--image-update obj-spec dfile cache-prop)
+                (force-window-update)))))))
+    cached-image))
+
 (defun telega-photo--image (photo limits)
   "Return best suitable image for the PHOTO."
   (let* ((best (telega-photo--best photo limits))
diff --git a/telega-modes.el b/telega-modes.el
index 4248306..e3c4cca 100644
--- a/telega-modes.el
+++ b/telega-modes.el
@@ -190,14 +190,6 @@ If MESSAGES-P is non-nil then use number of messages with mentions."
                    'mouse-face 'mode-line-highlight
                    'help-echo "Click to filter chats with mentions")))))
 
-(defun telega-mode-line-update (&rest _ignored)
-  "Update value for `telega-mode-line-string'."
-  (when telega-mode-line-mode
-    (setq telega-mode-line-string
-          (when (telega-server-live-p)
-            (telega-format-mode-line telega-mode-line-string-format)))
-    (force-mode-line-update 'all)))
-
 ;;;###autoload
 (define-minor-mode telega-mode-line-mode
   "Toggle display of the unread chats/mentions in the modeline."
@@ -246,6 +238,14 @@ If MESSAGES-P is non-nil then use number of messages with mentions."
     (advice-remove 'tracking-remove-buffer 'telega-mode-line-update)
     ))
 
+(defun telega-mode-line-update (&rest _ignored)
+  "Update value for `telega-mode-line-string'."
+  (when telega-mode-line-mode
+    (setq telega-mode-line-string
+          (when (telega-server-live-p)
+            (telega-format-mode-line telega-mode-line-string-format)))
+    (force-mode-line-update 'all)))
+
 ;;; ellit-org: minor-modes
 ;; ** telega-appindicator-mode
 ;;
@@ -500,7 +500,10 @@ Return filename of the generated icon."
 (defcustom telega-autoplay-custom-emojis 10
   "Non-nil to automatically play this number of custom emojis in the message."
   :type '(choice (const :tag "Autoplay Disabled" nil)
-                 integer)
+                 ;; TODO: add support for maximum number of frames to play
+                 (cons (integer :tag "Maximum number of custom emojis")
+                       (integer :tag "Maximum number of frames to be played"))
+                 (integer :tag "Maximum number of custom emojis to play"))
   :group 'telega-modes)
 
 (defun telega-autoplay-custom-emojis (msg &optional force)
@@ -958,17 +961,6 @@ get message associated with the file."
       '("Turn off minor mode" . telega-edit-file-mode))
     map))
 
-(defun telega-edit-file-message ()
-  "Return message for the currently edited file with `telega-edit-file-mode'."
-  (cl-assert telega-edit-file-mode)
-  telega--help-win-param)
-
-(defun telega-edit-file-buffer-name ()
-  "Return buffer name for a file edited with `telega-edit-file-mode'."
-  (concat (buffer-name) (telega-symbol 'mode)
-          (telega-chatbuf--name
-           (telega-msg-chat (telega-edit-file-message)))))
-
 (defvar telega-edit-file-mode-lighter
   (concat " " (telega-symbol 'mode) "Edit")
   "Lighter for the `telega-edit-file-mode'.")
@@ -1016,6 +1008,17 @@ Can be enabled only for content from editable messages."
     (setq mode-line-buffer-identification
           (propertized-buffer-identification "%12b"))))
 
+(defun telega-edit-file-message ()
+  "Return message for the currently edited file with `telega-edit-file-mode'."
+  (cl-assert telega-edit-file-mode)
+  telega--help-win-param)
+
+(defun telega-edit-file-buffer-name ()
+  "Return buffer name for a file edited with `telega-edit-file-mode'."
+  (concat (buffer-name) (telega-symbol 'mode)
+          (telega-chatbuf--name
+           (telega-msg-chat (telega-edit-file-message)))))
+
 (defun telega-edit-file-goto-message ()
   "Goto corresponding message."
   (interactive)
@@ -1739,6 +1742,51 @@ Chat description is used to probe chat's language."
   :type '(repeat (string :tag "Language Code"))
   :group 'telega-modes)
 
+(defvar telega-auto-translate-mode-lighter
+  (concat " " (telega-symbol 'mode) "Translate")
+  "Lighter for the `telega-auto-translate-mode'.")
+
+(define-minor-mode telega-auto-translate-mode
+  "Minor mode to automatically translate chat messages."
+  :lighter telega-auto-translate-mode-lighter
+  (if telega-auto-translate-mode
+      (progn
+        (unless telega-translate-to-language-by-default
+          (setq telega-translate-to-language-by-default
+                (telega-completing-read-language-code
+                 "Translate chat messages/input to: "))
+          (telega-help-message 'translate-to-language
+              "Consider customizing `telega-translate-to-language-by-default'"))
+
+        (advice-add 'telega-chatbuf-input-send
+                    :around 'telega-auto-translate--chatbuf-input-send)
+        (add-hook 'telega-msg-hover-in-hook
+                  #'telega-auto-translate--msg nil 'local)
+        (add-hook 'telega-chatbuf-post-msg-insert-hook
+                  #'telega-auto-translate--on-msg-insert nil 'local)
+        (add-hook 'telega-chatbuf-pre-msg-update-hook
+                  #'telega-auto-translate--on-msg-update nil 'local)
+
+        ;; Try to detect chat's language code
+        (if (and (not telega-chatbuf-language-code)
+                 (telega-chatbuf-match-p 'can-send-or-post))
+            (telega-auto-translate--chatbuf-detect-language
+             telega-auto-translate-probe-language-codes)
+          (telega-chatbuf--prompt-update)
+          (telega-auto-translate--chatbuf-translate-visible-messages))
+        )
+
+    (remove-hook 'telega-chatbuf-pre-msg-update-hook
+                 #'telega-auto-translate--on-msg-update 'local)
+    (remove-hook 'telega-chatbuf-post-msg-insert-hook
+                 #'telega-auto-translate--on-msg-insert 'local)
+    (remove-hook 'telega-msg-hover-in-hook
+                 #'telega-auto-translate--msg 'local)
+    (advice-remove 'telega-chatbuf-input-send
+                   'telega-auto-translate--chatbuf-input-send)
+    (telega-chatbuf--prompt-update)
+    ))
+
 (defun telega-auto-translate--chatbuf-translate-visible-messages ()
   "Translate all visible messages in the chatbuf."
   (cl-assert telega-translate-to-language-by-default)
@@ -1878,51 +1926,6 @@ Or nil if translation is not needed."
                   telega-chatbuf-language-code
                   "]"))))
 
-(defvar telega-auto-translate-mode-lighter
-  (concat " " (telega-symbol 'mode) "Translate")
-  "Lighter for the `telega-auto-translate-mode'.")
-
-(define-minor-mode telega-auto-translate-mode
-  "Minor mode to automatically translate chat messages."
-  :lighter telega-auto-translate-mode-lighter
-  (if telega-auto-translate-mode
-      (progn
-        (unless telega-translate-to-language-by-default
-          (setq telega-translate-to-language-by-default
-                (telega-completing-read-language-code
-                 "Translate chat messages/input to: "))
-          (telega-help-message 'translate-to-language
-              "Consider customizing `telega-translate-to-language-by-default'"))
-
-        (advice-add 'telega-chatbuf-input-send
-                    :around 'telega-auto-translate--chatbuf-input-send)
-        (add-hook 'telega-msg-hover-in-hook
-                  #'telega-auto-translate--msg nil 'local)
-        (add-hook 'telega-chatbuf-post-msg-insert-hook
-                  #'telega-auto-translate--on-msg-insert nil 'local)
-        (add-hook 'telega-chatbuf-pre-msg-update-hook
-                  #'telega-auto-translate--on-msg-update nil 'local)
-
-        ;; Try to detect chat's language code
-        (if (and (not telega-chatbuf-language-code)
-                 (telega-chatbuf-match-p 'can-send-or-post))
-            (telega-auto-translate--chatbuf-detect-language
-             telega-auto-translate-probe-language-codes)
-          (telega-chatbuf--prompt-update)
-          (telega-auto-translate--chatbuf-translate-visible-messages))
-        )
-
-    (remove-hook 'telega-chatbuf-pre-msg-update-hook
-                 #'telega-auto-translate--on-msg-update 'local)
-    (remove-hook 'telega-chatbuf-post-msg-insert-hook
-                 #'telega-auto-translate--on-msg-insert 'local)
-    (remove-hook 'telega-msg-hover-in-hook
-                 #'telega-auto-translate--msg 'local)
-    (advice-remove 'telega-chatbuf-input-send
-                   'telega-auto-translate--chatbuf-input-send)
-    (telega-chatbuf--prompt-update)
-    ))
-
 
 ;;; ellit-org: minor-modes
 ;;
@@ -1968,6 +1971,25 @@ TDLib's autoDownloadSettings structure."
   :type 'alist
   :group 'telega-modes)
 
+;;;###autoload
+(define-minor-mode telega-auto-download-mode
+  "Global mode to automatically download media files."
+  :init-value nil :global t :group 'telega-modes
+  (if telega-auto-download-mode
+      (progn
+        (add-hook 'telega-ready-hook
+                  #'telega-auto-download--start)
+        (when (telega-server-live-p)
+          (telega-auto-download--start))
+        )
+
+    (when (telega-server-live-p)
+      (telega-auto-download--apply-settings
+       telega-auto-download--disabled-preset))
+    (remove-hook 'telega-ready-hook
+                 #'telega-auto-download--start)
+    ))
+
 (defun telega-auto-download--apply-settings (settings &optional tl-network-type)
   "Apply auto-downloading SETTINGS for the TL-NETWORK-TYPE."
   (let ((tl-settings
@@ -1998,25 +2020,6 @@ TDLib's autoDownloadSettings structure."
        (setq telega-auto-download--tdlib-presets reply)
        (telega-auto-download--start)))))
 
-;;;###autoload
-(define-minor-mode telega-auto-download-mode
-  "Global mode to automatically download media files."
-  :init-value nil :global t :group 'telega-modes
-  (if telega-auto-download-mode
-      (progn
-        (add-hook 'telega-ready-hook
-                  #'telega-auto-download--start)
-        (when (telega-server-live-p)
-          (telega-auto-download--start))
-        )
-
-    (when (telega-server-live-p)
-      (telega-auto-download--apply-settings
-       telega-auto-download--disabled-preset))
-    (remove-hook 'telega-ready-hook
-                 #'telega-auto-download--start)
-    ))
-
 
 ;;; ellit-org: minor-modes
 ;; ** telega-contact-birthdays-mode
diff --git a/telega-msg.el b/telega-msg.el
index c8df7dd..cf03c99 100644
--- a/telega-msg.el
+++ b/telega-msg.el
@@ -25,6 +25,7 @@
 
 ;;; Code:
 (require 'format-spec)
+(require 'easymenu)
 
 (require 'telega-core)
 (require 'telega-tdlib)
@@ -56,129 +57,95 @@
 
 
 ;; Menu for right-mouse on message
-(defvar telega-msg-button-menu-map
-  (let ((menu-map (make-sparse-keymap "Telega Message")))
-    (bindings--define-key menu-map [telega-msg-mark-toggle]
-      '(menu-item (telega-i18n "lng_context_select_msg") telega-msg-mark-toggle
-                  :help "Mark the message"
-                  :button (:toggle . (telega-msg-marked-p
-                                      (telega-msg-at-down-mouse-3)))))
-                  ;; :visible (not (telega-msg-marked-p
-                  ;;                (telega-msg-at-down-mouse-3)))))
-    ;; (bindings--define-key menu-map [unmark]
-    ;;   '(menu-item "Unmark" telega-msg-mark-toggle
-    ;;               :help "Unmark the message"
-    ;;               :visible (telega-msg-marked-p (telega-msg-at-down-mouse-3))))
-    (bindings--define-key menu-map [s0] menu-bar-separator)
-    (bindings--define-key menu-map [add-tags]
-      '(menu-item (telega-i18n "lng_add_tag_button") telega-msg-add-reaction
-                  :help "Add tag to the message"
-                  :visible (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                             '(chat saved-messages))))
-    (bindings--define-key menu-map [add-favorite]
-      '(menu-item "Add to Favorites" telega-msg-favorite-toggle
-                  :help "Add message to the list of favorite messages"
-                  :visible (not (telega-msg-favorite-p
-                                 (telega-msg-at-down-mouse-3)))))
-    (bindings--define-key menu-map [rm-favorite]
-      '(menu-item "Remove from Favorites" telega-msg-favorite-toggle
-                  :help "Remove message from the list of favorite messages"
-                  :visible (telega-msg-favorite-p (telega-msg-at-down-mouse-3))))
-
-    (bindings--define-key menu-map [save]
-      '(menu-item (telega-i18n "lng_context_save_file") telega-msg-save
-                  :help "Save message's media to a file"))
-    (bindings--define-key menu-map [copy-link]
-      '(menu-item (telega-i18n "lng_context_copy_link") telega-msg-copy-link
-                  :help "Copy link to the message to the kill ring"))
-    (bindings--define-key menu-map [copy-text]
-      '(menu-item (telega-i18n "lng_context_copy_text") telega-msg-copy-text
-                  :visible (let ((msg (telega-msg-at-down-mouse-3)))
-                             (telega-msg-content-text msg 'with-voice-note))
-                  :help "Copy message text to the kill ring"))
-    (bindings--define-key menu-map [unpin]
-      '(menu-item (telega-i18n "lng_context_unpin_msg") telega-msg-pin-toggle
-                  :help "Unpin message"
-                  :visible (let ((msg (telega-msg-at-down-mouse-3)))
-                             (and (telega-chat-match-p (telega-msg-chat msg)
-                                    '(my-permission :can_pin_messages))
-                                  (plist-get msg :is_pinned)))))
-    (bindings--define-key menu-map [pin]
-      '(menu-item (telega-i18n "lng_context_pin_msg") telega-msg-pin-toggle
-                  :help "Pin message"
-                  :visible (let ((msg (telega-msg-at-down-mouse-3)))
-                             (and (telega-chat-match-p (telega-msg-chat msg)
-                                    '(my-permission :can_pin_messages))
-                                  (not (plist-get msg :is_pinned))))))
-    (bindings--define-key menu-map [s1] menu-bar-separator)
-    (bindings--define-key menu-map [ban-sender]
-      '(menu-item (propertize "Ban Sender" 'face 'error)
-                  telega-msg-ban-sender
-                  :help "Ban/report message sender"
-                  :enable (let ((msg (telega-msg-at-down-mouse-3)))
-                             (telega-chat-match-p (telega-msg-chat msg)
-                               '(my-permission :can_restrict_members)))
-                  ))
-    (bindings--define-key menu-map [delete]
-      '(menu-item (propertize (telega-i18n "lng_context_delete_msg") 'face 'error)
-                  telega-msg-delete-dwim
-                  :enable (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                            '(or (message-property :can_be_deleted_for_all_users)
-                                 (message-property :can_be_deleted_only_for_self)))
-                  ))
-    ;; TODO: create submenu for reporting a message/chat/reactions/etc
-    ;; (bindings--define-key menu-map [report]
-    ;;   '(menu-item (propertize (telega-i18n "lng_context_report_msg") 'face 'error)
-    ;;               telega-msg-report-dwim
-    ;;               :enable (telega-msg-match-p (telega-msg-at-down-mouse-3)
-    ;;                         '(or (message-property :can_be_deleted_for_all_users)
-    ;;                              (message-property :can_be_deleted_only_for_self)))
-    ;;               ))
-    (bindings--define-key menu-map [forward]
-      '(menu-item (telega-i18n "lng_context_forward_msg")
-                  telega-msg-forward-dwim
-                  :help "Forward messages"
-                  :enable (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                            '(message-property :can_be_forwarded))
-                  ))
-    (bindings--define-key menu-map [translate]
-      '(menu-item (telega-i18n "lng_context_translate") telega-msg-translate
-                  :help "Translate message's text"
-                  :visible (telega-msg-content-text
-                            (telega-msg-at-down-mouse-3))))
-    (bindings--define-key menu-map [s2] menu-bar-separator)
-    (bindings--define-key menu-map [topic]
-      '(menu-item (telega-i18n "lng_replies_view_topic")
-                  telega-msg-open-thread-or-topic
-                  :help "Show message's topic"
-                  :visible (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                             'is-topic)))
-    (bindings--define-key menu-map [thread]
-      '(menu-item (telega-i18n "lng_replies_view_thread")
-                  telega-msg-open-thread-or-topic
-                  :help "Show message's thread"
-                  :visible (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                             'is-thread)))
-    (bindings--define-key menu-map [edit]
-      '(menu-item (telega-i18n "lng_context_edit_msg") telega-msg-edit
-                  :help "Edit the message"
-                  :enable (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                            '(message-property :can_be_edited))))
-    (bindings--define-key menu-map [reply-another-char]
-      '(menu-item (telega-i18n "lng_reply_in_another_chat")
-                  telega-msg-reply-in-another-chat
-                  :help (telega-i18n "lng_reply_in_another_chat")
-                  :enable (telega-msg-match-p (telega-msg-at-down-mouse-3)
-                            '(message-property :can_be_replied_in_another_chat))
-                  ))
-    (bindings--define-key menu-map [reply]
-      '(menu-item (telega-i18n "lng_context_reply_msg") telega-msg-reply
-                  :help "Reply to the message"))
-    (bindings--define-key menu-map [s3] menu-bar-separator)
-    (bindings--define-key menu-map [describe]
-      '(menu-item (telega-i18n "lng_info_about_label") telega-describe-message
-                  :help "Describe the message"))
-    menu-map))
+(easy-menu-define telega-msg-button-menu nil
+  "Menu for the message."
+  '("Telega Message"
+    ["describe" telega-describe-message
+     :key (kbd "i")
+     :label (telega-i18n "lng_info_about_label")]
+    "---"
+    ["reply" telega-msg-reply
+     :label (telega-i18n "lng_context_reply_msg")]
+    ["reply-in-another-chat" telega-msg-reply-in-another-chat
+      :label (telega-i18n "lng_reply_in_another_chat")
+      :visible (telega-msg-match-p (telega-msg-for-interactive)
+                 '(message-property :can_be_replied_in_another_chat))]
+    ["edit" telega-msg-edit
+     :label (telega-i18n "lng_context_edit_msg")
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                '(and (not (type Poll))
+                      (message-property :can_be_edited)))]
+    ["stop-poll" telega-msg-stop-poll
+     :label (if (eq 'pollTypeQuiz
+                    (telega--tl-type
+                     (plist-get (telega-msg-match-p (telega-msg-for-interactive)
+                                  '(type Poll))
+                                :poll)))
+                "Stop Quiz"
+              (telega-i18n "lng_polls_stop"))
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                '(and (type Poll)
+                      (message-property :can_be_edited)))]
+    ["forward" telega-msg-forward-dwim
+     :label (telega-i18n "lng_context_forward_msg")
+     :enable (telega-msg-match-p (telega-msg-for-interactive)
+               '(message-property :can_be_forwarded))]
+    ["add-tag" telega-msg-add-reaction
+     :label (telega-i18n "lng_add_tag_button")
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                '(chat saved-messages))]
+    ["translate" telega-msg-translate
+     :label (telega-i18n "lng_context_translate")
+     :visible (telega-msg-content-text (telega-msg-for-interactive))]
+    "---"
+    ["pin" telega-msg-pin-toggle
+     :label (telega-i18n "lng_context_pin_msg")
+     :style toggle
+     :selected (telega-msg-match-p (telega-msg-for-interactive)
+                 '(prop :is_pinned))
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                '(message-property :can_be_pinned))]
+    ["copy" telega-msg-copy-text
+     :label (telega-i18n "lng_context_copy_text")
+     :visible (telega-msg-content-text
+               (telega-msg-for-interactive) 'with-voice-note)]
+    ["copy-link" telega-msg-copy-link
+     :label (telega-i18n "lng_context_copy_link")]
+    ["save" telega-msg-save
+     :label (telega-i18n "lng_context_save_file")]
+    ["favorite" telega-msg-favorite-toggle
+     :label "Toggle Favorite"
+     :style toggle
+     :selected (telega-msg-favorite-p (telega-msg-for-interactive))]
+    "---"
+    ["delete" telega-msg-delete-dwim
+     :label (propertize (telega-i18n "lng_context_delete_msg") 'face 'error)
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                '(or (message-property :can_be_deleted_for_all_users)
+                    (message-property :can_be_deleted_only_for_self)))]
+    ["ban-sender" telega-msg-ban-sender
+     :label (propertize "Ban Sender" 'face 'error)
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                '(chat (my-permission :can_restrict_members)))]
+    ["report-spam" telega-msg-report-dwim
+     :label (telega-i18n "lng_report_spam")
+      :visible (telega-msg-match-p (telega-msg-for-interactive)
+                 '(message-property :can_report_supergroup_spam))]
+    "---"
+    ["view-thread" telega-msg-open-thread-or-topic
+     :label (telega-i18n "lng_replies_view_thread")
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                'is-thread)]
+    ["view-topic" telega-msg-open-thread-or-topic
+     :label (telega-i18n "lng_replies_view_topic")
+     :visible (telega-msg-match-p (telega-msg-for-interactive)
+                'is-topic)]
+    "---"
+    ["mark" telega-msg-mark-toggle
+     :label (telega-i18n "lng_context_select_msg")
+     :style toggle
+     :selected (telega-msg-marked-p (telega-msg-for-interactive))]
+    ))
 
 (defvar telega-msg-button-map
   (let ((map (make-sparse-keymap)))
@@ -219,7 +186,7 @@
     (define-key map (kbd "*") 'telega-msg-favorite-toggle)
 
     ;; Menu for right mouse on a message
-    (define-key map [down-mouse-3] telega-msg-button-menu-map)
+    (define-key map [down-mouse-3] telega-msg-button-menu)
     (define-key map [mouse-3] #'ignore)
 
     ;; ffplay media controls for some media messages
@@ -242,12 +209,6 @@
 
     map))
 
-(defvar telega-msg-button-spoiler-map
-  (let ((map (make-sparse-keymap)))
-    (set-keymap-parent map telega-msg-button-map)
-    (define-key map (kbd "RET") 'telega-msg-remove-text-spoiler)
-    map))
-
 (define-button-type 'telega-msg
   :supertype 'telega
   :inserter telega-inserter-for-msg-button
@@ -1115,9 +1076,12 @@ non-nil."
 (defun telega-msg-emojis-only-p (msg)
   "Return non-nil if text message MSG contains only emojis."
   (when (telega-msg-match-p msg '(type Text))
-    (let ((text (telega--tl-get msg :content :text :text)))
-      (not (text-property-not-all
-            0 (length text) 'telega-emoji-p t text)))))
+    (let* ((content-text (telega--tl-get msg :content :text))
+           (text (plist-get content-text :text)))
+      ;; NOTE: Should not contain custom emojis or other entities
+      (and (seq-empty-p (plist-get content-text :entities))
+           (not (text-property-not-all
+                 0 (length text) 'telega-emoji-p t text))))))
 
 (defun telega-msg-open-content (msg &optional clicked-p)
   "Open message MSG content.
@@ -1310,6 +1274,25 @@ If WITH-PREFIX-P is non-nil, then prefix username with \"@\" char."
 
     (substring (telega-chat-title msg-sender 'no-badges) 0 1)))
 
+(defun telega-msg-sender--verification-badges (v-status)
+  "Return verification status bages string."
+  (when v-status
+    (concat 
+     (when (plist-get v-status :is_scam)
+       (propertize (telega-i18n "lng_scam_badge") 'face 'error))
+     (when (plist-get v-status :is_fake)
+       (propertize (telega-i18n "lng_fake_badge") 'face 'error))
+     (when (plist-get v-status :is_verified)
+       (let* ((v-custom-emoji-id
+               (plist-get v-status :bot_verification_icon_custom_emoji_id))
+              (v-sticker
+               (unless (telega-zerop v-custom-emoji-id)
+                 (telega-custom-emoji-get v-custom-emoji-id))))
+         (if v-sticker
+             (telega-ins--as-string
+              (telega-ins--image (telega-sticker--image sticker)))
+           (telega-symbol 'verified)))))))
+
 (defun telega-msg-sender-title (msg-sender &rest args)
   "Return title for the message sender MSG-SENDER.
 ARGS are passed directly to `telega-ins--msg-sender'."
@@ -1760,11 +1743,9 @@ If `\\[universal-argument]' is supplied, then copy without text properties."
                 msg-text))
     (message "Copied message text (%d chars)" (length msg-text))))
 
-(defun telega-msg--tl-entity-text (msg &optional tl-entity)
-  "Return entity text at point defined by `:tl-entity' text property."
-  (when-let ((tl-entity (or tl-entity
-                            (get-text-property (point) :tl-entity)))
-             (msg-fmt-text (or (telega--tl-get msg :content :text)
+(defun telega-msg--tl-entity-text (msg tl-entity)
+  "Return MSG text at point limited by TL-ENTITY."
+  (when-let ((msg-fmt-text (or (telega--tl-get msg :content :text)
                                (telega--tl-get msg :content :caption))))
     (telega-tl-str
      (telega-fmt-text-substring
@@ -1782,11 +1763,10 @@ If `\\[universal-argument]' is supplied, then copy without text properties."
   (interactive (list (telega-msg-for-interactive)
                      current-prefix-arg))
 
-  (let* ((ent (get-text-property (point) :tl-entity))
-         (ent-type (when ent
-                     (telega--tl-type (plist-get ent :type))))
+  (let* ((entities (get-text-property (point) :tl-entities))
          (telega-link (get-text-property (point) :telega-link))
          (telega-inhibit-telega-display-by t)
+         (ent nil)
          (ctext (cond ((region-active-p)
                        (prog1
                            (buffer-substring (region-beginning) (region-end))
@@ -1795,13 +1775,16 @@ If `\\[universal-argument]' is supplied, then copy without text properties."
                       ((memq (car telega-link) '(file url))
                        (cdr telega-link))
 
-                      ((eq 'textEntityTypeUrl ent-type)
+                      ((setq ent (telega--tl-entity-get
+                                  entities 'textEntityTypeUrl))
                        (telega-msg--tl-entity-text msg ent))
 
-                      ((eq 'textEntityTypeTextUrl ent-type)
+                      ((setq ent (telega--tl-entity-get
+                                  entities 'textEntityTypeTextUrl))
                        (telega-tl-str (plist-get ent :type) :url))
 
-                      ((eq 'textEntityTypePreCode ent-type)
+                      ((setq ent (telega--tl-entity-get
+                                  entities 'textEntityTypePreCode))
                        (telega-msg--tl-entity-text msg ent))
 
                       (t
@@ -1862,6 +1845,12 @@ Requires administrator rights in the chat."
        (list :@type "chatMemberStatusBanned"
              :banned_until_date 0)))))
 
+(defun telega-msg-stop-poll (msg)
+  "Stop poll message MSG."
+  (interactive (list (telega-msg-for-interactive)))
+  (when (yes-or-no-p (telega-i18n "lng_polls_stop_warning"))
+    (telega--stopPoll msg)))
+
 (defun telega-ins--message-read-date (msg-read-date)
   "Inserter for the MessageReadDate structure."
   (cl-ecase (telega--tl-type msg-read-date)
@@ -2165,6 +2154,11 @@ be added."
     (cond
      ((null reaction-type)
       (user-error "telega: Can't add reaction to this message"))
+     ((equal reaction-type '(:@type "reactionTypePaid"))
+      (telega--addPendingPaidMessageReaction msg)
+      (if (y-or-n-p-with-timeout "Undo paid reaction? " 5 nil)
+          (telega--removePendingPaidMessageReactions msg)
+        (telega--commitPendingPaidMessageReactions msg)))
      ((not (eq reaction-type 'custom))
       (telega--addMessageReaction msg reaction-type big-p 'update-recent))
      (t
@@ -2256,42 +2250,40 @@ By default `telega-translate-to-language-default' is used."
       (telega-msg-redisplay msg)
       )))
 
-(defun telega-msg-remove-text-spoiler (msg)
+(defun telega-msg-text-spoiler-toggle (msg)
   "Show spoiler text entity at point."
   (interactive (list (telega-msg-for-interactive)))
 
-  (when-let ((ent-type (get-text-property (point) :tl-entity-type)))
-    (when (eq 'textEntityTypeSpoiler (telega--tl-type ent-type))
-      (plist-put ent-type :telega-show-spoiler t)
-      (telega-msg-redisplay msg)
-      ;; NOTE: hide spoiler on next message's redisplay
-      (plist-put ent-type :telega-show-spoiler nil))))
+  (plist-put msg :telega-text-spoiler-removed
+             (not (plist-get msg :telega-text-spoiler-removed)))
+  (telega-msg-redisplay msg))
 
 (defun telega-msg-blockquote-expand-toggle (msg)
   "Toggle expandable block quote at point."
   (interactive (list (telega-msg-for-interactive)))
 
-  (when-let ((ent-type (get-text-property (point) :tl-entity-type)))
-    (when (eq 'textEntityTypeExpandableBlockQuote (telega--tl-type ent-type))
-      (plist-put ent-type :telega-blockquote-expanded
-                 (not (plist-get ent-type :telega-blockquote-expanded)))
-      (telega-msg-redisplay msg))))
+  (when-let ((ent (telega--tl-entity-get
+                   (get-text-property (point) :tl-entities)
+                   'textEntityTypeExpandableBlockQuote)))
+    (plist-put ent :telega-blockquote-expanded
+               (not (plist-get ent :telega-blockquote-expanded)))
+    (telega-msg-redisplay msg)))
 
-(defun telega-msg-remove-media-spoiler (msg)
-  "Remove spoiler for the media message MSG."
+(defun telega-msg-media-spoiler-toggle (msg)
+  "Toggle spoiler for the media message MSG."
   (interactive (list (telega-msg-for-interactive)))
-  (let ((content (plist-get msg :content)))
-    (when (and (plist-get content :has_spoiler)
-               (not (plist-get content :telega-spoiler-removed)))
-      (plist-put content :telega-spoiler-removed t)
-      (telega-msg-redisplay msg))))
+
+  (when (plist-get (plist-get msg :content) :has_spoiler)
+    (plist-put msg :telega-media-spoiler-removed
+               (not (plist-get msg :telega-media-spoiler-removed)))
+    (telega-msg-redisplay msg)))
 
 (defun telega-msg-disable-link-preview (msg)
   "Disable webpage preview for the given outgoing message."
   (interactive (list (telega-msg-for-interactive)))
   (unless (telega-msg-match-p msg '(type Text))
     (user-error "telega: can disable link preview only for text messages"))
-  (unless (telega-msg-match-p msg '(prop :can_be_edited))
+  (unless (telega-msg-match-p msg '(message-property :can_be_edited))
     (user-error "telega: can't edit this message"))
 
   (telega--editMessageText
diff --git a/telega-obsolete.el b/telega-obsolete.el
index 77a7c4a..da2f648 100644
--- a/telega-obsolete.el
+++ b/telega-obsolete.el
@@ -305,6 +305,10 @@
                            'telega-msg-heading-aux-format-plist
                            "0.8.390")
 
+(telega-obsolete--variable 'telega-msg-heading-with-date-and-status
+                           'telega-msg-heading-trail
+                           "0.8.393")
+
 ;; Check some obsolete var/fun is used
 (cl-eval-when (eval load)
   (dolist (obsolete-var telega-obsolete--variables)
diff --git a/telega-root.el b/telega-root.el
index 60369b8..2bdca11 100644
--- a/telega-root.el
+++ b/telega-root.el
@@ -83,6 +83,13 @@ Use `telega-root-aux-inserters' to customize it.")
 (defvar telega-idle--timer nil
   "Runs when Emacs gets idle.")
 
+(defvar-keymap telega-root-view-contacts-map
+  :doc "Keymap to view contacts."
+  "s" #'telega-view-contacts-search
+  "a" #'telega-view-contacts-all
+  "f" #'telega-view-close-friends
+  "o" #'telega-view-owned-bots)
+
 (defvar telega-root-view-map
   (let ((map (make-sparse-keymap)))
     ;;; ellit-org: rootbuf-view-bindings
@@ -149,9 +156,15 @@ Use `telega-root-aux-inserters' to customize it.")
     ;;   {{{fundoc(telega-view-settings, 2)}}}
     (define-key map (kbd "S") 'telega-view-settings)
     ;;; ellit-org: rootbuf-view-bindings
-    ;; - {{{where-is(telega-view-contacts,telega-root-mode-map)}}} ::
-    ;;   {{{fundoc(telega-view-contacts, 2)}}}
-    (define-key map (kbd "c") 'telega-view-contacts)
+    ;; - {{{where-is(telega-view-contacts-search,telega-root-mode-map)}}} ::
+    ;;   {{{fundoc(telega-view-contacts-search, 2)}}}
+    ;; - {{{where-is(telega-view-contacts-all,telega-root-mode-map)}}} ::
+    ;;   {{{fundoc(telega-view-contacts-all, 2)}}}
+    ;; - {{{where-is(telega-view-close-friends,telega-root-mode-map)}}} ::
+    ;;   {{{fundoc(telega-view-close-friends, 2)}}}
+    ;; - {{{where-is(telega-view-owned-bots,telega-root-mode-map)}}} ::
+    ;;   {{{fundoc(telega-view-owned-bots, 2)}}}
+    (define-key map (kbd "c") telega-root-view-contacts-map)
     ;;; ellit-org: rootbuf-view-bindings
     ;; - {{{where-is(telega-view-calls,telega-root-mode-map)}}} ::
     ;;   {{{fundoc(telega-view-calls, 2)}}}
@@ -1426,7 +1439,7 @@ VIEW-FILTER is additional chat filter for this root view."
   (telega-root--outgoing-doc-messages-search)
   )
 
-(defun telega-view-contacts (query)
+(defun telega-view-contacts-search (query)
   "View contacts searched by QUERY.
 If QUERY is empty string, then show all contacts."
   (interactive
@@ -1448,6 +1461,43 @@ If QUERY is empty string, then show all contacts."
                :on-user-update #'telega-root--contact-on-user-update))
    ))
 
+(defun telega-view-contacts-all ()
+  "View all contacts."
+  (interactive)
+  (telega-view-contacts-all ""))
+
+(defun telega-view-close-friends ()
+  "View close friends."
+  (interactive)
+  (telega-root-view--apply
+   (list 'telega-view-close-friends
+         (telega-i18n "lng_edit_privacy_close_friends")
+         (list :name "close-friends"
+               :pretty-printer #'telega-root--contact-pp
+               :sorter #'telega-user-cmp-by-status
+               :loading (telega--getCloseFriends
+                          (apply-partially
+                           #'telega-root-view--ewoc-loading-done "close-friends"))
+               :on-chat-update #'telega-root--contact-on-chat-update
+               :on-user-update #'telega-root--contact-on-user-update)))
+  )
+
+(defun telega-view-owned-bots ()
+  "View owned bots."
+  (interactive)
+  (telega-root-view--apply
+   (list 'telega-view-owned-bots
+         "Owned Bots"
+         (list :name "owned-bots"
+               :pretty-printer #'telega-root--contact-pp
+               :sorter #'telega-user-cmp-by-status
+               :loading (telega--getOwnedBots
+                          (apply-partially
+                           #'telega-root-view--ewoc-loading-done "owned-bots"))
+               :on-chat-update #'telega-root--contact-on-chat-update
+               :on-user-update #'telega-root--contact-on-user-update)))
+  )
+
 (defun telega-view-last-messages ()
   "View last messages in the chats."
   (interactive)
@@ -1821,7 +1871,7 @@ Default Disable Notification setting"))
       (telega-root--any-on-chat-update ewoc-name ewoc chat))))
 
 (defun telega-view-folders--ewoc-spec (folder-spec)
-  (let ((folder-name (telega-tl-str folder-spec :title)))
+  (let ((folder-name (telega-folder-name folder-spec)))
     (list :name folder-name
           :pretty-printer (telega-view-folders--gen-pp folder-name)
           :header (telega-folder-format "%i%f" folder-name)
@@ -2048,11 +2098,7 @@ state kinds to show. By default all kinds are shown."
                      (telega-ins--chat chat)))
           :pretty-printer #'telega-root--favorite-message-pp
           :loading (telega--getMessages (plist-get chat :id)
-                       (mapcar (telega--tl-prop :id)
-                               (seq-filter (lambda (fav)
-                                             (eq (plist-get chat :id)
-                                                 (plist-get fav :chat_id)))
-                                           telega--favorite-messages))
+                       (telega-chat-favorite-messages-ids chat)
                      (apply-partially #'telega-root-view--ewoc-loading-done
                                       ewoc-name))
           :sorter #'telega-root--messages-sorter
diff --git a/telega-sort.el b/telega-sort.el
index e1a5a3a..a6170d3 100644
--- a/telega-sort.el
+++ b/telega-sort.el
@@ -47,6 +47,7 @@
     ;; `a' mnemominc is to "add" some sorter
     (define-key map (kbd "a") 'telega-sort-by-sorter)
     (define-key map (kbd "s") 'telega-sort-by-sorter)
+    (define-key map (kbd "i") 'telega-sort-by-important)
     (define-key map (kbd "u") 'telega-sort-by-unread-count)
     (define-key map (kbd "t") 'telega-sort-by-title)
     (define-key map (kbd "j") 'telega-sort-by-join-date)
@@ -100,16 +101,18 @@ CRITERIA could be a lit of sort criterias."
 
     (let* ((cmpfun (alist-get (car criteria) telega-sort-criteria-alist))
            (c1-val (funcall cmpfun chat1))
-           (c2-val (funcall cmpfun chat2)))
-      (cond ((equal c1-val c2-val)
-             ;; Traverse the criteria list
-             (telega-chats-compare (cdr criteria) chat1 chat2))
-            ((null c1-val) nil)
-            ((null c2-val) t)
-            ((and (stringp c1-val) (stringp c2-val))
-             ;; Make "A" > "Z", so alphabetical order goes out-of-box
-             (string< c1-val c2-val))
-            (t (> c1-val c2-val))))))
+           (c2-val (funcall cmpfun chat2))
+           (result (cond ((equal c1-val c2-val)
+                          ;; Traverse the criteria list
+                          (telega-chats-compare (cdr criteria) chat1 chat2))
+                         ((null c1-val) nil)
+                         ((null c2-val) t)
+                         ((and (stringp c1-val) (stringp c2-val))
+                          (string> c1-val c2-val))
+                         (t (> c1-val c2-val)))))
+      (if (get (car criteria) :telega-sort-inverted)
+          (not result)
+        result))))
 
 (defun telega-sort-chats (criteria chats)
   "Sort CHATS by criteria."
@@ -188,6 +191,9 @@ overwriting currently active one."
   "Sort chats alphabetically by chat title."
   (telega-chat-title chat))
 
+;; Make "A" > "Z", so alphabetical order goes out-of-box
+(put 'title :telega-sort-inverted t)
+
 ;;; ellit-org: chat-sorting-criteria
 ;; - ~member-count~, {{{where-is(telega-sort-by-member-count,telega-root-mode-map)}}} ::
 ;;   {{{fundoc(telega--sort-member-count, 2)}}}
@@ -281,8 +287,21 @@ For other chats date of the last message is taken."
               0)))
     (or (telega--tl-get chat :last_message :date) 0)))
 
+;;; ellit-org: chat-sorting-criteria
+;; - ~important~ ::
+;;   {{{fundoc(telega--sort-important, 2)}}}
+(define-telega-sorter important ("updateChatUnreadMentionCount"
+                                 "updateChatUnreadReactionCount"
+                                 "updateChatReadInbox")
+                      (chat)
+  "Makes chats matching `telega-important-chat-temex' on top."
+  (cond ((telega-chat-match-p chat 'important)
+         (telega-chat-order chat))
+        (t "10")))
+
 ;; - TODO Date of last message sent by ~telega-user-me~
 ;; - TODO Date of last mention (thanks to https://t.me/lainposter)
+;; - TODO By custom chat temex, i.e. matching chats goes first
 
 (provide 'telega-sort)
 
diff --git a/telega-sticker.el b/telega-sticker.el
index 4926b93..bad2f44 100644
--- a/telega-sticker.el
+++ b/telega-sticker.el
@@ -202,7 +202,7 @@ CALLBACK is called without arguments"
              ;; Update corresponding sticker image
              (telega-media--image-update
               (cons sticker 'telega-sticker--create-image)
-              (cons sticker :sticker))
+              (plist-get sticker :sticker))
              (force-window-update))))
 
 (defun telega-sticker--svg-outline-path (svg tl-path factor &rest args)
@@ -394,6 +394,16 @@ Return path to png file."
 
 (defun telega-sticker--image (sticker &optional image-create-fun cache-prop)
   "Return image for the STICKER."
+  (unless (plist-get sticker :telega-image)
+    (telega--getStickerOutline (plist-get sticker :file)
+      :callback (lambda (outline)
+                  (unless (telega--tl-error-p outline)
+                    (plist-put sticker :outline outline)
+                    ;; TODO: Update sticker image if sticker is not
+                    ;; yet downloaded
+                    ())
+    (telega-sticker--download sticker))))
+
   (telega-media--image
    (cons sticker (or image-create-fun #'telega-sticker--create-image))
    (if (or (and telega-sticker--use-thumbnail
diff --git a/telega-tdlib-events.el b/telega-tdlib-events.el
index 20e2a30..f509f4c 100644
--- a/telega-tdlib-events.el
+++ b/telega-tdlib-events.el
@@ -599,11 +599,16 @@ NOTE: we store the number as custom chat property, to use it later."
   ;; that folders.  Because folder name might be displayed along the
   ;; side with chat's title in the rootbuf
   (let* ((new-tdlib-folders (append (plist-get event :chat_folders) nil))
-         (new-names (seq-difference (telega-folder-names new-tdlib-folders)
-                                    (telega-folder-names))))
+         (new-names (seq-difference
+                     (telega-folder-names new-tdlib-folders 'no-props)
+                     (telega-folder-names telega-tdlib--chat-folders 'no-props))))
     (setq telega-tdlib--chat-folders new-tdlib-folders)
 
     (dolist (folder-name new-names)
+      ;; NOTE: Fetch all custom emojis from folder's name
+      (telega-folder-name--fetch-custom-emojis
+       (telega-folder--chat-folder-info folder-name))
+
       (dolist (fchat (telega-filter-chats
                       telega--ordered-chats `(folder ,folder-name)))
         (telega-chat--mark-dirty fchat)))
@@ -1623,11 +1628,13 @@ Please downgrade TDLib and recompile `telega-server'"
   (when (and (equal "tdesktop" (plist-get event :localization_target))
              (equal telega-language (plist-get event :language_pack_id)))
     (seq-doseq (lps (plist-get event :strings))
+      ;; TODO
       )
     ))
 
 (defun telega--on-updateOwnedStarCount (event)
-  (setq telega--owned-stars (plist-get event :star_count))
+  (setq telega--owned-stars
+        (telega--tl-star-amount-as-float (plist-get event :star_amount)))
   ;; TODO: Update settings
   )
 
diff --git a/telega-tdlib.el b/telega-tdlib.el
index 10150c5..b125e39 100644
--- a/telega-tdlib.el
+++ b/telega-tdlib.el
@@ -604,19 +604,23 @@ LIMIT defaults to 20."
              (list :chat_id (plist-get chat :id))))
     callback))
 
-(cl-defun telega--searchStickers (emoji &key (tl-sticker-type
+(cl-defun telega--searchStickers (emojis &key (tl-sticker-type
                                               '(:@type "stickerTypeRegular"))
-                                        limit callback)
-  "Search for the public stickers that correspond to a given EMOJI.
+                                        query language-codes limit callback)
+  "Search for the public stickers that correspond to a given EMOJIS.
+EMOJIS is space-separated list of emojis to search for.
 LIMIT defaults to 20."
   (declare (indent 1))
   (with-telega-server-reply (reply)
       (append (plist-get reply :stickers) nil)
 
-    (list :@type "searchStickers"
-          :emojis emoji
-          :sticker_type tl-sticker-type
-          :limit (or limit 100))
+    (nconc (list :@type "searchStickers"
+                 :emojis emojis
+                 :sticker_type tl-sticker-type
+                 :limit (or limit 100))
+           (when (or query language-codes)
+             (list :query (or query "")
+                   :input_language_codes (apply #'vector language-codes))))
     callback))
 
 (cl-defun telega--getInstalledStickerSets (&key (tl-sticker-type
@@ -726,6 +730,17 @@ Pass non-nil ATTACHED-P to return only stickers attached to photos/videos."
          :sticker sticker-input-file)
    callback))
 
+(cl-defun telega--getStickerOutline (sticker-file &key for-animated-emoji-p
+                                                  for-clicked-p callback)
+  "Return outline of a sticker."
+  (declare (indent 1))
+  (telega-server--call
+   (list :@type "getStickerOutline"
+         :sticker_file_id (plist-get sticker-file :id)
+         :for_animated_emoji (if for-animated-emoji-p t :false)
+         :for_clicked_animated_emoji_message (if for-clicked-p t :false))
+   callback))
+
 (defun telega--changeStickerSet (stickerset install-p &optional archive-p)
   "Install/Uninstall STICKERSET."
   (telega-server--call
@@ -1892,17 +1907,20 @@ REVOKE is always non-nil for supergroups, channels and secret chats."
          :sender_id (telega--MessageSender sender))))
 
 (cl-defun telega--searchMessages (query &key offset (limit 100)
-                                         chat-list tl-msg-filter
+                                         chat-list
+                                         tl-msg-filter
+                                         tl-chat-filter
                                         (min-date 0) (max-date 0)
-                                        only-channels-p
                                         callback)
   "Search messages by QUERY.
 OFFSET is th offset of the first entry to return as received from the
 previous request.
 If CHAT-LIST is given, then fetch chats from TDLib's CHAT-LIST.
+TL-MSG-FILTER is a \"SearchMessagesFilter\" TL structure.
+TL-CHAT-FILTER is a \"SearchMessagesChatTypeFilter\" TL structure.
 If CALLBACK is specified, then do async call and run CALLBACK
-with list of chats received.
-Return FoundMessages TL structure."
+with the result.
+Return \"FoundMessages\" TL structure."
   (declare (indent 1))
   (telega-server--call
    (nconc (list :@type "searchMessages"
@@ -1910,7 +1928,6 @@ Return FoundMessages TL structure."
                 :offset (or offset "")
                 :min_date min-date
                 :max-date max-date
-                :only_in_channels (if only-channels-p t :false)
                 :limit limit)
           (when tl-msg-filter
             (list :filter tl-msg-filter))
@@ -1919,7 +1936,9 @@ Return FoundMessages TL structure."
           (when chat-list
             nil
             ;;   (list :chat_list chat-list)
-            ))
+            )
+          (when tl-chat-filter
+            (list :chat_type_filter tl-chat-filter)))
    callback))
 
 (cl-defun telega--searchOutgoingDocumentMessages (&optional query &key limit callback)
@@ -2250,7 +2269,7 @@ be marked as read."
    (list :@type "toggleChatIsPinned"
          :chat_list telega-tdlib--chat-list
          :chat_id (plist-get chat :id)
-         :is_pinned (if (plist-get (telega-chat-position chat) :is_pinned)
+         :is_pinned (if (telega-chat-match-p chat 'is-pinned)
                         :false
                       t))))
 
@@ -2455,6 +2474,14 @@ Return an ID of group call."
           :is_rtmp_stream (if rtmp-stream-p t :false))
     callback))
 
+(defun telega--createGroupCall (call-id &optional callback)
+  "Create a group call from a one-to-one call denoted by CALL-ID."
+  (declare (indent 1))
+  (telega-server--call
+   (list :@type "createGroupCall"
+         :call_id call-id)
+   callback))
+
 (defun telega--getVideoChatRtmpUrl (chat &optional callback)
   (declare (indent 1))
   (telega-server--call
@@ -3219,6 +3246,38 @@ Saved Messages topic is specified by SM-TOPIC-ID."
          :can_have_sponsored_messages (if enable-p t :false))
    (or callback #'ignore)))
 
+(cl-defun telega--addPendingPaidMessageReaction (msg &key (star-count 1)
+                                                     (anonymous-p 'default))
+  (telega-server--send
+   (list :@type "addPendingPaidMessageReaction"
+         :chat_id (plist-get msg :chat_id)
+         :message_id (plist-get msg :id)
+         :star_count star-count
+         :use_default_is_anonymous (if (eq anonymous-p 'default) t :false)
+         :is_anonymous (if (and anonymous-p (not (eq anonymous-p 'default)))
+                           t
+                         :false)
+         )))
+
+(defun telega--commitPendingPaidMessageReactions (msg)
+  (telega-server--send
+   (list :@type "commitPendingPaidMessageReactions"
+         :chat_id (plist-get msg :chat_id)
+         :message_id (plist-get msg :id))))
+
+(defun telega--removePendingPaidMessageReactions (msg)
+  (telega-server--send
+   (list :@type "removePendingPaidMessageReactions"
+         :chat_id (plist-get msg :chat_id)
+         :message_id (plist-get msg :id))))
+
+(defun telega--getOwnedBots (&optional callback)
+  (with-telega-server-reply (reply)
+      (mapcar #'telega-user-get (plist-get reply :user_ids))
+
+    (list :@type "getOwnedBots")
+    callback))
+
 (provide 'telega-tdlib)
 
 ;;; telega-tdlib.el ends here
diff --git a/telega-user.el b/telega-user.el
index 2408473..7ca1e6e 100644
--- a/telega-user.el
+++ b/telega-user.el
@@ -163,18 +163,18 @@ Return nil if given FMT-TYPE is not available."
            ;; Scam/Fake/Blacklist badge, apply for users only
            ;; see https://t.me/emacs_telega/30318
            (concat name
-                   ;; Badges
-                   (when (plist-get user :is_verified)
-                     (telega-symbol 'verified))
+                   ;; Verification Status badges
+                   (telega-msg-sender--verification-badges
+                    (plist-get user :verification_status))
+
+                   ;; Premium Badge
                    (cond ((plist-get user :emoji_status)
                           (telega-ins--as-string
                            (telega-ins--user-emoji-status user)))
                          ((plist-get user :is_premium)
                           (telega-symbol 'premium)))
-                   (when (plist-get user :is_scam)
-                     (propertize (telega-i18n "lng_scam_badge") 'face 'error))
-                   (when (plist-get user :is_fake)
-                     (propertize (telega-i18n "lng_fake_badge") 'face 'error))
+
+                   ;; Blocking Status badge
                    (when (telega-user-match-p user 'is-blocked)
                      (telega-symbol 'blocked))))
           (name name)
diff --git a/telega-util.el b/telega-util.el
index 976a403..8e301a6 100644
--- a/telega-util.el
+++ b/telega-util.el
@@ -54,6 +54,7 @@
 (declare-function telega-user-list "telega-user" (&optional temex))
 (declare-function telega-user> "telega-user" (user1 user2))
 
+(declare-function telega-match-p "telega-match-p" (object temex))
 
 (defun telega-file-exists-p (filename)
   "Return non-nil if FILENAME exists.
@@ -191,12 +192,23 @@ Used to calculate correct image `:width' in `em' elements."
   (telega-chars-in-width
    (car (window-text-pixel-size window (line-beginning-position) (point)))))
 
-(defun telega-window-string-width (str)
+(defun telega-window-string-width (str &optional from to)
   "Return correct width in chars.
 This function is very slow comparing to `string-width', however
 returns precise value."
-  (with-temp-buffer
-    (telega-ins str)
+  ;; Keeping a work buffer around is more efficient than creating a
+  ;; new temporary buffer.
+  (with-current-buffer (get-buffer-create " *telega-string-width*")
+    ;; If `display-line-numbers-mode' is enabled in internal
+    ;; buffers, it breaks width calculation, so disable it (bug#59311)
+    (when (bound-and-true-p display-line-numbers-mode)
+      (display-line-numbers-mode -1))
+    (delete-region (point-min) (point-max))
+    ;; Disable line-prefix and wrap-prefix, for the same reason.
+    (setq line-prefix nil
+          wrap-prefix nil)
+
+    (telega-ins (if (or from to) (substring str from to) str))
     (save-window-excursion
       (set-window-dedicated-p nil nil)
       (set-window-buffer nil (current-buffer))
@@ -740,8 +752,7 @@ If AS-IS is non-nil, then do not apply time adjustment using
 (defun telega-number-human-readable (num &optional fmt)
   "Convert METERS to human readable string.
 By default \"%.1f\" FMT format string is used."
-  (unless fmt
-    (setq fmt "%.1f"))
+  (unless fmt (setq fmt "%.1f"))
   (cond ((and telega-use-short-numbers (>= num 1000000))
          (concat (format fmt (/ num 1000000.0)) "M"))
         ((and telega-use-short-numbers (>= num 1000))
@@ -895,15 +906,28 @@ HOW specifies how list is changed, one of: `prepend', `append' or `remove'."
 Optional OBJECT could be a buffer (or nil, which means the current
 buffer) or a string."
   (let* ((ent-type (plist-get ent :type))
+         (ent-type-tl-type (telega--tl-type ent-type))
          (beg (plist-get ent :offset))
          (end (+ (plist-get ent :offset) (plist-get ent :length)))
          (ent-text (if object
                        (substring object beg end)
                      (buffer-substring beg end))))
     (add-text-properties beg end '(rear-nonsticky t front-sticky t) object)
-    (put-text-property beg end :tl-entity ent object)
+    (telega--change-text-property beg end :tl-entities (list ent) 'append object)
     (put-text-property beg end :tl-entity-type ent-type object)
-    (cl-case (telega--tl-type ent-type)
+
+    ;; NOTE: Include newline for blockquotes even if newline is
+    ;; outside of the blockquote, making
+    ;; `telega-entity-type-blockquote' face to extend to the end of
+    ;; line
+    (when (and (stringp object)
+               (memq ent-type-tl-type '(textEntityTypeBlockQuote
+                                        textEntityTypeExpandableBlockQuote))
+               (< end (length object))
+               (= (aref object end) ?\n))
+      (setq end (1+ end)))
+
+    (cl-case ent-type-tl-type
       (textEntityTypeMention
        (add-text-properties
         beg end (telega-link-props 'username ent-text) object)
@@ -985,8 +1009,15 @@ buffer) or a string."
            ;; differ, because consecutive chars has `eq' `display'
            ;; property it is displayed as single unit (ref: 40.16.1
            ;; Display Specs That Replace The Text)
+           ;; 
+           ;; However, copying image will breake
+           ;; `telega-sticker--animate' functionality, because
+           ;; `telega-media--image-update' updates image inplace
+           ;; (i.e. its cdr).  So we use `(magin nil)' hack to make
+           ;; display property non-eq
            (put-text-property
-            beg end 'display (copy-sequence (telega-sticker--image sticker))
+            beg end 'display (list '(margin nil)
+                                   (telega-sticker--image sticker))
             object)
            )))
       (textEntityTypePreCode
@@ -1024,26 +1055,28 @@ buffer) or a string."
        (add-face-text-property
         beg end 'telega-entity-type-spoiler 'append object)
 
-       (cond ((null object)
-              ;; NOTE: For chatbuf's input, when spoiler formatting is
-              ;; applied
-              (add-face-text-property
-               beg end (list :foreground (face-foreground 'default)
-                             :background (face-foreground 'default))
-               'append object))
-
-             ((not (plist-get ent-type :telega-show-spoiler))
-              (add-text-properties
-               beg end (list :action #'telega-msg-remove-text-spoiler
-                             'telega-display
-                             (with-temp-buffer
-                               (insert ent-text)
-                               (translate-region
-                                (point-min) (point-max)
-                                telega-spoiler-translation-table)
-                               (buffer-string))
-                             'telega-display-by 'telega-core)
-               object))))
+       (if (null object)
+           ;; NOTE: For chatbuf's input, when spoiler formatting is
+           ;; applied
+           (add-face-text-property
+            beg end (list :foreground (face-foreground 'default)
+                          :background (face-foreground 'default))
+            nil object)
+
+         (when telega-msg--current
+           (add-text-properties
+            beg end (list :action #'telega-msg-text-spoiler-toggle) object))
+         (unless (plist-get telega-msg--current :telega-text-spoiler-removed)
+           (add-text-properties
+            beg end (list 'telega-display
+                          (with-temp-buffer
+                            (insert ent-text)
+                            (translate-region
+                             (point-min) (point-max)
+                             telega-spoiler-translation-table)
+                            (buffer-string))
+                          'telega-display-by 'telega-core)
+            object))))
       (textEntityTypeBlockQuote
        (if (and nil (telega--inhibit-telega-display-p 'telega-core))
            (add-face-text-property
@@ -1058,7 +1091,7 @@ buffer) or a string."
             (telega-face-with-palette 'telega-entity-type-blockquote
                 palette :background)
             'append object)
-           (let ((lw-prefix (propertize 
+           (let ((lw-prefix (propertize
                              (telega-symbol 'vbar-left)
                              'face (append (assq :foreground palette)
                                            (assq :background palette)))))
@@ -1078,13 +1111,13 @@ buffer) or a string."
                            (telega-msg-sender-palette
                             (telega-msg-sender telega-msg--current))))
                 (expanded-p
-                 (plist-get ent-type :telega-blockquote-expanded)))
+                 (plist-get ent :telega-blockquote-expanded)))
            (add-face-text-property
             beg end
             (telega-face-with-palette 'telega-entity-type-blockquote
                 palette :background)
             'append object)
-           (let ((lw-prefix (propertize 
+           (let ((lw-prefix (propertize
                              (telega-symbol 'vbar-left)
                              'face (append (assq :foreground palette)
                                            (assq :background palette)))))
@@ -1097,46 +1130,34 @@ buffer) or a string."
             beg end
             :action #'telega-msg-blockquote-expand-toggle
             object)
-           )
-         ;; (add-text-properties
-         ;;  beg end
-         ;;  (list :action #'telega-msg-blockquote-expand-toggle
-         ;;        'telega-display
-         ;;        (let* ((telega-palette-context 'blockquote)
-         ;;               (palette (when telega-msg--current
-         ;;                          (telega-msg-sender-palette
-         ;;                           (telega-msg-sender telega-msg--current))))
-         ;;               (expanded-p
-         ;;                (plist-get ent-type :telega-blockquote-expanded)))
-         ;;          (add-face-text-property
-         ;;           beg end
-         ;;           (telega-face-with-palette 'telega-entity-type-blockquote
-         ;;               palette :background)
-         ;;           'append object)
-         ;;          (telega-ins--as-string
-         ;;           (unless (telega--text-entity-at-newline-p ent object)
-         ;;             (telega-ins "\n"))
-         ;;           (telega-ins--line-wrap-prefix
-         ;;               (propertize (telega-symbol 'vbar-left)
-         ;;                           'face (append (assq :foreground palette)
-         ;;                                         (assq :background palette)))
-         ;;             (telega-ins--with-attrs
-         ;;                 (list :max (unless expanded-p (* 2.5 fill-column))
-         ;;                       :elide t
-         ;;                       :elide-string (propertize telega-symbol-eliding
-         ;;                                                 'face 'telega-shadow))
-         ;;               (telega-ins (telega-strip-newlines
-         ;;                            (telega--desurrogate-apply object))))
-         ;;             (telega-ins--with-face 'telega-shadow
-         ;;               (telega-ins (telega-symbol (if expanded-p
-         ;;                                              'collapse-details
-         ;;                                            'expand-details))))
-         ;;             (telega-ins "\n"))))
-         ;;        'telega-display-by 'telega-core)
-         ;;  object)
-         ))
-      )
-    ))
+
+           ;; Collapse at point configurable by
+           ;; `telega-expandable-blockquote-limit'
+           (let ((collapse-pos
+                  (when (and (not expanded-p) (stringp object))
+                    (cond ((consp telega-expandable-blockquote-limit)
+                           (let ((lbeg (car telega-expandable-blockquote-limit))
+                                 (lend (cdr telega-expandable-blockquote-limit)))
+                             (cond ((>= (+ beg lbeg) end)
+                                    nil)
+                                   ((and (string-match "\n" object (+ beg lbeg))
+                                         (< (match-beginning 0) (+ beg lend)))
+                                    (match-beginning 0))
+                                   (t
+                                    (+ beg lend)))))
+                          ((integerp telega-expandable-blockquote-limit)
+                           (+ telega-expandable-blockquote-limit beg))))))
+             (when (and collapse-pos (< collapse-pos (- end 10)))
+               (put-text-property
+                collapse-pos end
+                'display (propertize (concat (telega-symbol 'eliding)
+                                             (telega-symbol 'expand-details)
+                                             (when (= (aref object (1- end)) ?\n)
+                                               "\n"))
+                                     'face 'telega-shadow)
+                object)))
+           )))
+      )))
 
 ;; https://core.telegram.org/bots/api#markdown-style
 (defsubst telega--entity-to-markdown (entity-text)
@@ -1631,10 +1652,12 @@ Return text string with applied faces."
   (let ((text (copy-sequence (plist-get fmt-text :text)))
         complex-ents)
     ;; NOTE: First we apply emphasis entities, then we apply
-    ;; formatting for complex entities, such as block quotes
+    ;; formatting for complex entities, such as block quotes, because
+    ;; it might override `display' property
     (seq-doseq (ent (plist-get fmt-text :entities))
-      (if (memq (telega--tl-type ent) '(textEntityTypeBlockQuote
-                                        textEntityTypeExpandableBlockQuote))
+      (if (telega-match-p (plist-get ent :type)
+            '(tl-type textEntityTypeBlockQuote
+                      textEntityTypeExpandableBlockQuote))
           (setq complex-ents (cons ent complex-ents))
         (telega--text-entity-apply ent text)))
 
diff --git a/telega-webpage.el b/telega-webpage.el
index 45d4128..2c899ff 100644
--- a/telega-webpage.el
+++ b/telega-webpage.el
@@ -276,7 +276,8 @@ Keymap:
          (photo-image (when pb-photo
                         (telega-photo--image pb-photo (list 10 3 10 3))))
          (url (telega-tl-str pageblock :url))
-         (title (plist-get pageblock :title))
+         (title (telega-tl-str pageblock :title))
+         (desc (telega-tl-str pageblock :description))
          (author (plist-get pageblock :author))
          (publish-date (plist-get pageblock :publish_date)))
     (telega-ins--with-attrs (list :max telega-webpage-fill-column
@@ -364,7 +365,8 @@ Keymap:
      (telega-ins--with-face 'telega-webpage-title
        (telega-webpage--ins-rt (plist-get pb :title))))
     (pageBlockSubtitle
-     (telega-webpage--ins-rt (plist-get pb :subtitle)))
+     (telega-ins--with-face 'telega-webpage-subtitle
+       (telega-webpage--ins-rt (plist-get pb :subtitle))))
     (pageBlockAuthorDate
      (telega-ins--with-face 'telega-shadow
        (telega-ins-prefix "By "
@@ -522,10 +524,12 @@ Keymap:
                  (telega-browse-url (telega-tl-str pb :url)))
        :help-echo (concat "URL: " (telega-tl-str pb :url))))
     (pageBlockRelatedArticles
-     (telega-ins--with-face '(telega-msg-heading bold)
-       (when (telega-webpage--ins-rt (plist-get pb :header))
-         (telega-ins "\n")))
-     (mapc 'telega-webpage--ins-pb (plist-get pb :articles)))
+     (telega-ins--with-face '(telega-webpage-outline bold)
+       (telega-webpage--ins-rt (plist-get pb :header))
+       (telega-ins "\n"))
+     (seq-doseq (article-pb (plist-get pb :articles))
+       (telega-webpage--ins-pb article-pb)
+       (telega-ins "\n")))
     (pageBlockKicker
      (telega-webpage--ins-rt (plist-get pb :kicker)))
     )
@@ -572,7 +576,8 @@ instant view for the URL."
 
     ;; IV status
     (telega-ins-from-newline
-     (telega-ins--with-face '(:inherit telega-msg-heading :overline t :extend t)
+     (telega-ins "\n")
+     (telega-ins--with-face '(:inherit telega-webpage-outline :overline t)
        (let ((view-count (plist-get telega-webpage--iv :view_count)))
          (unless (telega-zerop view-count)
            (telega-ins-i18n "lng_views_tooltip"
diff --git a/telega.el b/telega.el
index 09446c9..64efc04 100644
--- a/telega.el
+++ b/telega.el
@@ -1,6 +1,6 @@
 ;;; telega.el --- Telegram client (unofficial)  -*- lexical-binding:t -*-
 
-;; Copyright (C) 2016-2024 by Zajcev Evgeny
+;; Copyright (C) 2016-2025 by Zajcev Evgeny
 ;; Copyright (C) 2019-2020 by Brett Gilio
 
 ;; Author: Zajcev Evgeny 
@@ -8,10 +8,10 @@
 ;; Keywords: comm
 ;; Package-Requires: ((emacs "27.1") (visual-fill-column "1.9") (transient "0.3.0"))
 ;; URL: https://github.com/zevlg/telega.el
-;; Version: 0.8.394
-(defconst telega-version "0.8.394")
+;; Version: 0.8.420
+(defconst telega-version "0.8.420")
 (defconst telega-server-min-version "0.7.7")
-(defconst telega-tdlib-min-version "1.8.39")
+(defconst telega-tdlib-min-version "1.8.42")
 (defconst telega-tdlib-max-version nil)
 
 (defconst telega-tdlib-releases '("1.8.0" . "1.9.0")
diff --git a/test.el b/test.el
index 76933a7..6d4b163 100644
--- a/test.el
+++ b/test.el
@@ -80,9 +80,11 @@ Have Stoploss 690 Satoshi." :entities []))))
   (puthash (plist-get chat :id) chat telega--chats))
 
 (setq telega-tdlib--chat-folders
-      '((:@type "chatFolderInfo" :id 2 :title "Emacs"
+      '((:@type "chatFolderInfo" :id 2
+                :name (:@type "chatFolderName" :text (:@type "formattedText" :text "Emacs" :entities []))
                 :icon (list :@type "chatFolderIcon" :name ""))
-        (:@type "chatFolderInfo" :id 3 :title #("\ud83d\ude39\ud83d\ude39\ud83d\ude39" 0 2 (telega-display "😹" telega-emoji-p t) 2 4 (telega-display "😹" telega-emoji-p t) 4 6 (telega-display "😹" telega-emoji-p t))
+        (:@type "chatFolderInfo" :id 3
+                :name (:@type "chatFolderName" :text (:@type "formattedText" :text #("" 0 2 (telega-emoji-p t telega-display "😹") 2 4 (telega-emoji-p t telega-display "😹") 4 6 (telega-emoji-p t telega-display "😹")) :entities []) :animate_custom_emoji t)
                 :icon (list :@type "chatFolderIcon" :name ""))))