+
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 ""))))