From e8ace6a8578e1142f6115ec98ebcf6bf4fea8bd6 Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Wed, 21 May 2025 14:52:42 +0200 Subject: [PATCH] update mastodon --- .../dir | 0 .../mastodon-async.el | 0 .../mastodon-auth.el | 160 ++++- .../mastodon-autoloads.el | 13 +- .../mastodon-client.el | 176 +++-- .../mastodon-discover.el | 82 +-- .../mastodon-http.el | 170 ++--- .../mastodon-inspect.el | 0 .../mastodon-iso.el | 0 .../mastodon-media.el | 36 +- .../mastodon-notifications.el | 292 ++++++-- .../mastodon-pkg.el | 9 +- .../mastodon-profile.el | 187 ++--- .../mastodon-search.el | 81 ++- .../mastodon-tl.el | 657 +++++++++++------- .../mastodon-toot.el | 225 +++--- .../mastodon-transient.el | 93 ++- .../mastodon-views.el | 229 +++--- .../mastodon-widget.el | 0 .../mastodon.el | 249 ++++--- .../mastodon.info | 279 +++++--- 21 files changed, 1802 insertions(+), 1136 deletions(-) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/dir (100%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-async.el (100%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-auth.el (53%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-autoloads.el (89%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-client.el (50%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-discover.el (57%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-http.el (78%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-inspect.el (100%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-iso.el (100%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-media.el (95%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-notifications.el (76%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-pkg.el (69%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-profile.el (89%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-search.el (87%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-tl.el (87%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-toot.el (93%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-transient.el (83%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-views.el (85%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon-widget.el (100%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon.el (69%) rename elpa/{mastodon-20241223.1040 => mastodon-20250330.1519}/mastodon.info (77%) diff --git a/elpa/mastodon-20241223.1040/dir b/elpa/mastodon-20250330.1519/dir similarity index 100% rename from elpa/mastodon-20241223.1040/dir rename to elpa/mastodon-20250330.1519/dir diff --git a/elpa/mastodon-20241223.1040/mastodon-async.el b/elpa/mastodon-20250330.1519/mastodon-async.el similarity index 100% rename from elpa/mastodon-20241223.1040/mastodon-async.el rename to elpa/mastodon-20250330.1519/mastodon-async.el diff --git a/elpa/mastodon-20241223.1040/mastodon-auth.el b/elpa/mastodon-20250330.1519/mastodon-auth.el similarity index 53% rename from elpa/mastodon-20241223.1040/mastodon-auth.el rename to elpa/mastodon-20250330.1519/mastodon-auth.el index 01639fb2..2902c319 100644 --- a/elpa/mastodon-20241223.1040/mastodon-auth.el +++ b/elpa/mastodon-20250330.1519/mastodon-auth.el @@ -2,6 +2,7 @@ ;; Copyright (C) 2017-2019 Johnson Denen ;; Copyright (C) 2021 Abhiseck Paira +;; Copyright (C) 2025 Marty Hiatt ;; Author: Johnson Denen ;; Maintainer: Marty Hiatt ;; Homepage: https://codeberg.org/martianh/mastodon.el @@ -32,7 +33,9 @@ (require 'plstore) (require 'auth-source) (require 'json) -(eval-when-compile (require 'subr-x)) ; for if-let +(require 'url) + +(eval-when-compile (require 'subr-x)) ; for if-let* (autoload 'mastodon-client "mastodon-client") (autoload 'mastodon-client--active-user "mastodon-client") @@ -44,6 +47,8 @@ (autoload 'mastodon-http--get-json "mastodon-http") (autoload 'mastodon-http--post "mastodon-http") (autoload 'mastodon-return-credential-account "mastodon") +(autoload 'mastodon-client--general-read "mastodon-client") +(autoload 'mastodon-client--token-file "mastodon-client") (defvar mastodon-instance-url) (defvar mastodon-client-scopes) @@ -55,6 +60,19 @@ :prefix "mastodon-auth-" :group 'mastodon) +(defcustom mastodon-auth-use-auth-source t + "Whether to use auth sources for user credentials. +If t, save and read user access token in the user's auth source +file (see `auth-sources'). If nil, use `mastodon-client--token-file' +instead. +If you change the value of this variable, call +`mastodon-forget-all-logins' and log in again. +If for some reason you generate a new token, you'll have to update your +auth souce file manually, or at least remove the entry and authenticate +again, as auth-source.el only provides unreliable tools for updating +entries." + :type 'boolean) + (defvar mastodon-auth-source-file nil "This variable is obsolete. This variable currently serves no purpose and will be removed in @@ -69,26 +87,25 @@ the future.") (defvar mastodon-auth--user-unaware " ** MASTODON.EL - NOTICE ** -It appears that you are not aware of the recent developments in -mastodon.el. In short we now require that you also set the -variable `mastodon-active-user' in your init file in addition to -`mastodon-instance-url'. +User variables not set: mastodon.el requires that you set both +`mastodon-active-user' and `mastodon-instance-url' in your init file. Please see its documentation to understand what value it accepts by running M-x describe-variable on it or visiting our web page: -https://codeberg.org/martianh/mastodon.el - -We apologize for the inconvenience. +https://codeberg.org/martianh/mastodon.el. ") (defun mastodon-auth--get-browser-login-url () "Return properly formed browser login url." - (mastodon-http--concat-params-to-url - (concat mastodon-instance-url "/oauth/authorize/") - `(("response_type" . "code") - ("redirect_uri" . ,mastodon-client-redirect-uri) - ("scope" . ,mastodon-client-scopes) - ("client_id" . ,(plist-get (mastodon-client) :client_id))))) + (let ((client-id (plist-get (mastodon-client) :client_id))) + (if (not client-id) + (error "Failed to set up client id") + (mastodon-http--concat-params-to-url + (concat mastodon-instance-url "/oauth/authorize/") + `(("response_type" . "code") + ("redirect_uri" . ,mastodon-client-redirect-uri) + ("scope" . ,mastodon-client-scopes) + ("client_id" . ,client-id)))))) (defvar mastodon-auth--explanation (format @@ -168,28 +185,72 @@ When ASK is absent return nil." (json-string (buffer-substring-no-properties (point) (point-max)))) (json-read-from-string json-string)))) +(defun mastodon-auth--plstore-token-check (&optional auth-source) + "Signal an error if plstore contains unencrypted access-token. +If AUTH-SOURCE, and if `mastodon-auth-use-auth-source' is non-nil, +return non-nil if it contains any access token. +Used to help users switch to the new encrypted auth token flow." + ;; FIXME: is it poss to move this plstore read to have one less read? + ;; e.g. inside of `mastodon-client--active-user'? the issue is that + ;; ideally we want to test "user-" entry, even if fetching "active-user" + ;; entry, so we would have to re-do the plstore read functions. + (when + (mastodon-auth--plstore-access-token-member auth-source) + (if auth-source + (user-error "Auth source storage of tokens is enabled,\ + but there is also an access token in your plstore.\ + If you're seeing this message after updating,\ + call `mastodon-forget-all-logins', and try again. + If you don't want to use auth sources,\ + also set `mastodon-auth-use-auth-source' to nil.\ + If this message is in error, contact us on the mastodon.el repo") + (user-error "Unencrypted access token in your plstore.\ + If you're seeing this message after updating,\ + call `mastodon-forget-all-logins', and log in again. + If this message is in error, contact us on the mastodon.el repo")))) + +(defun mastodon-auth--plstore-access-token-member (&optional auth-source) + "Return non-nil if the user entry of the plstore contains :access_token. +If AUTH-SOURCE, also check if it contains :secret-access_token." + (let* ((plstore (plstore-open (mastodon-client--token-file))) + (name (concat "user-" (mastodon-client--form-user-from-vars))) + ;; get alist like plstore.el does, so that keys will display with + ;; ":secret-" prefix if encrypted: + (alist (assoc name (plstore--get-merged-alist plstore)))) + (if (and auth-source mastodon-auth-use-auth-source) + (or (member :access_token alist) + (member :secret-access_token alist)) + (member :access_token alist)))) + (defun mastodon-auth--access-token () "Return the access token to use with `mastodon-instance-url'. Generate/save token if none known yet." - (cond (mastodon-auth--token-alist - ;; user variables are known and initialised. - (alist-get mastodon-instance-url mastodon-auth--token-alist nil nil #'string=)) - ((plist-get (mastodon-client--active-user) :access_token) - ;; user variables need to be read from plstore. - (push (cons mastodon-instance-url - (plist-get (mastodon-client--active-user) :access_token)) - mastodon-auth--token-alist) - (alist-get mastodon-instance-url mastodon-auth--token-alist nil nil #'string=)) - ((null mastodon-active-user) - ;; user not aware of 2FA-related changes and has not set - ;; `mastodon-active-user'. Make user aware and error out. - (mastodon-auth--show-notice mastodon-auth--user-unaware - "*mastodon-notice*") - (error "Variables not set properly")) - (t - ;; user access-token needs to fetched from the server and - ;; stored and variables initialised. - (mastodon-auth--handle-token-response (mastodon-auth--get-token))))) + (cond + (mastodon-auth--token-alist + ;; user variables are known and initialised. + (alist-get mastodon-instance-url + mastodon-auth--token-alist nil nil #'string=)) + ;; if auth source enabled, but we have an access token in plstore, + ;; error out and tell user to remove plstore and start over or disable + ;; auth source: + ((mastodon-auth--plstore-token-check)) + ((plist-get (mastodon-client--active-user) :access_token) + ;; user variables need to be read from plstore active-user entry. + (push (cons mastodon-instance-url + (plist-get (mastodon-client--active-user) :access_token)) + mastodon-auth--token-alist) + (alist-get mastodon-instance-url + mastodon-auth--token-alist nil nil #'string=)) + ((null mastodon-active-user) + ;; user not aware of 2FA-related changes and has not set + ;; `mastodon-active-user'. Make user aware and error out. + (mastodon-auth--show-notice mastodon-auth--user-unaware + "*mastodon-notice*") + (user-error "Variables not set properly")) + (t + ;; user access-token needs to fetched from the server and + ;; stored and variables initialised. + (mastodon-auth--handle-token-response (mastodon-auth--get-token))))) (defun mastodon-auth--handle-token-response (response) "Add token RESPONSE to `mastodon-auth--token-alist'. @@ -206,6 +267,39 @@ Handle any errors from the server." (error "Mastodon-auth--access-token: %s: %s" class error)) (_ (error "Unknown response from mastodon-auth--get-token!")))) +(defun mastodon-auth-source-get (user host &optional token create) + "Fetch an auth source token, searching by USER and HOST. +If CREATE, use TOKEN or prompt for it, and save it if there is no such entry. +Return a list of user, password/secret, and the item's save-function." + (let* ((auth-source-creation-prompts + '((secret . "%u access token: "))) + (source + (car + (auth-source-search :host host :user user + :require '(:user :secret) + :secret (if token token nil) + ;; "create" alone doesn't work here!: + :create (if create t nil))))) + (when source + (let ((creds + `(,(plist-get source :user) + ,(auth-info-password source) + ,(plist-get source :save-function)))) + (when create ;; call save function: + (when (functionp (nth 2 creds)) + (funcall (nth 2 creds)))) + creds)))) + +(defun mastodon-auth-source-token (url handle &optional token create) + "Parse URL, search auth sources with it, user HANDLE and TOKEN. +Calls `mastodon-auth-source-get', returns only the token. +If CREATE, create an entry is none is found." + (let ((host (url-host + (url-generic-parse-url url))) + (username (car (split-string handle "@")))) + (nth 1 + (mastodon-auth-source-get username host token create)))) + (defun mastodon-auth--get-account-name () "Request user credentials and return an account name." (alist-get 'acct diff --git a/elpa/mastodon-20241223.1040/mastodon-autoloads.el b/elpa/mastodon-20250330.1519/mastodon-autoloads.el similarity index 89% rename from elpa/mastodon-20241223.1040/mastodon-autoloads.el rename to elpa/mastodon-20250330.1519/mastodon-autoloads.el index 4108b8e2..d94babd3 100644 --- a/elpa/mastodon-20241223.1040/mastodon-autoloads.el +++ b/elpa/mastodon-20250330.1519/mastodon-autoloads.el @@ -11,6 +11,9 @@ ;;; Generated autoloads from mastodon.el +(autoload 'mastodon-forget-all-logins "mastodon" "\ +Delete `mastodon-client--token-file'. +Also nil `mastodon-auth--token-alist'." t) (autoload 'mastodon "mastodon" "\ Connect client to `mastodon-instance-url' instance. If there are any open mastodon.el buffers, switch to one instead. @@ -102,7 +105,7 @@ disabled. ;;; Generated autoloads from mastodon-notifications.el -(register-definition-prefixes "mastodon-notifications" '("mastodon-notifications-")) +(register-definition-prefixes "mastodon-notifications" '("mastodon-")) ;;; Generated autoloads from mastodon-profile.el @@ -117,7 +120,7 @@ disabled. ;;; Generated autoloads from mastodon-tl.el -(register-definition-prefixes "mastodon-tl" '("mastodon-tl-" "with-mastodon-buffer")) +(register-definition-prefixes "mastodon-tl" '("mastodon-" "with-mastodon-buffer")) ;;; Generated autoloads from mastodon-toot.el @@ -128,11 +131,15 @@ disabled. ;;; Generated autoloads from mastodon-transient.el -(register-definition-prefixes "mastodon-transient" '("mastodon-transient-")) +(register-definition-prefixes "mastodon-transient" '("mastodon-")) ;;; Generated autoloads from mastodon-views.el +(autoload 'mastodon-views-view-scheduled-toots "mastodon-views" "\ +Show the user's scheduled toots in a new buffer." t) +(autoload 'mastodon-views-view-filters "mastodon-views" "\ +View the user's filters in a new buffer." t) (register-definition-prefixes "mastodon-views" '("mastodon-views-")) diff --git a/elpa/mastodon-20241223.1040/mastodon-client.el b/elpa/mastodon-20250330.1519/mastodon-client.el similarity index 50% rename from elpa/mastodon-20241223.1040/mastodon-client.el rename to elpa/mastodon-20250330.1519/mastodon-client.el index c0db3d61..1ad6d2be 100644 --- a/elpa/mastodon-20241223.1040/mastodon-client.el +++ b/elpa/mastodon-20250330.1519/mastodon-client.el @@ -2,6 +2,7 @@ ;; Copyright (C) 2017-2019 Johnson Denen ;; Copyright (C) 2021 Abhiseck Paira +;; Copyright (C) 2025 Marty Hiatt ;; Author: Johnson Denen ;; Maintainer: Marty Hiatt ;; Homepage: https://codeberg.org/martianh/mastodon.el @@ -35,11 +36,14 @@ (defvar mastodon-instance-url) (defvar mastodon-active-user) +(defvar mastodon-auth-use-auth-source) (autoload 'mastodon-http--api "mastodon-http") (autoload 'mastodon-http--post "mastodon-http") +(autoload 'mastodon-auth-source-token "mastodon-auth") -(defcustom mastodon-client--token-file (concat user-emacs-directory "mastodon.plstore") +(defcustom mastodon-client--token-file + (concat user-emacs-directory "mastodon.plstore") "File path where Mastodon access tokens are stored." :group 'mastodon :type 'file) @@ -71,14 +75,23 @@ (defun mastodon-client--fetch () "Return JSON from `mastodon-client--register' call." - (with-current-buffer (mastodon-client--register) - (goto-char (point-min)) - (re-search-forward "^$" nil 'move) - (let ((json-object-type 'plist) - (json-key-type 'keyword) - (json-array-type 'vector) - (json-string (buffer-substring-no-properties (point) (point-max)))) - (json-read-from-string json-string)))) + (let ((buf (mastodon-client--register))) + (if (not buf) + (user-error "Client registration failed.\ + Is `mastodon-instance-url' correct?") + (with-current-buffer buf + (goto-char (point-min)) + (re-search-forward "^$" nil 'move) + (let* ((json-object-type 'plist) + (json-key-type 'keyword) + (json-array-type 'vector) + (json-string + (buffer-substring-no-properties (point) (point-max))) + (parsed + (json-read-from-string json-string))) + (if (eq :error (car parsed)) + (error "Error: %s" (cadr parsed)) + parsed)))))) (defun mastodon-client--token-file () "Return `mastodon-client--token-file'." @@ -86,19 +99,28 @@ (defun mastodon-client--store () "Store client_id and client_secret in `mastodon-client--token-file'. - -Make `mastodon-client--fetch' call to determine client values." - (let ((plstore (plstore-open (mastodon-client--token-file))) - (client (mastodon-client--fetch)) - ;; alexgriffith reported seeing ellipses in the saved output - ;; which indicate some output truncating. Nothing in `plstore-save' - ;; seems to ensure this cannot happen so let's do that ourselves: - (print-length nil) - (print-level nil)) - (plstore-put plstore (concat "mastodon-" mastodon-instance-url) client nil) +Make `mastodon-client--fetch' call to determine client values. +Return a plist of secret and non-secret key/val pairs." + (let* ((plstore (plstore-open (mastodon-client--token-file))) + (client (mastodon-client--fetch)) + (secrets `( :client_id ,(plist-get client :client_id) + :client_secret ,(plist-get client :client_secret))) + (sans-secrets + (dolist (x '(:client_id :client_secret) client) + (cl-remf client x))) + ;; alexgriffith reported seeing ellipses in the saved output + ;; which indicate some output truncating. Nothing in + ;; `plstore-save' seems to ensure this cannot happen so let's do + ;; that ourselves: + (print-length nil) + (print-level nil)) + (plstore-put plstore + (concat "mastodon-" mastodon-instance-url) + sans-secrets secrets) + ;; FIXME: breaks tests: prompts for gpg passphrase (plstore-save plstore) (plstore-close plstore) - client)) + (append secrets sans-secrets))) (defun mastodon-client--remove-key-from-plstore (plstore) "Remove KEY from PLSTORE." @@ -109,7 +131,10 @@ Make `mastodon-client--fetch' call to determine client values." (defun mastodon-client--read () "Retrieve client_id and client_secret from `mastodon-client--token-file'." (let* ((plstore (plstore-open (mastodon-client--token-file))) - (mastodon (plstore-get plstore (concat "mastodon-" mastodon-instance-url)))) + (mastodon + (plstore-get plstore + (concat "mastodon-" mastodon-instance-url)))) + (plstore-close plstore) (mastodon-client--remove-key-from-plstore mastodon))) (defun mastodon-client--general-read (key) @@ -117,38 +142,69 @@ Make `mastodon-client--fetch' call to determine client values." Return plist without the KEY." (let* ((plstore (plstore-open (mastodon-client--token-file))) (plstore-item (plstore-get plstore key))) + (plstore-close plstore) (mastodon-client--remove-key-from-plstore plstore-item))) (defun mastodon-client--make-user-details-plist () "Make a plist with current user details. Return it." - `(:username ,(mastodon-client--form-user-from-vars) - :instance ,mastodon-instance-url - :client_id ,(plist-get (mastodon-client) :client_id) - :client_secret ,(plist-get (mastodon-client) :client_secret))) + `( :username ,(mastodon-client--form-user-from-vars) + :instance ,mastodon-instance-url + :client_id ,(plist-get (mastodon-client) :client_id) + :client_secret ,(plist-get (mastodon-client) :client_secret))) (defun mastodon-client--store-access-token (token) - "Save TOKEN as :access_token in plstore of the current user. -Return the plist after the operation." + "Save TOKEN as :access_token encrypted in the plstore. +Return the plist after the operation. +If `mastodon-auth-use-auth-source', encrypt it in auth source file." (let* ((user-details (mastodon-client--make-user-details-plist)) (plstore (plstore-open (mastodon-client--token-file))) - (username (plist-get user-details :username)) - (plstore-value (setq user-details - (plist-put user-details :access_token token))) + (username (mastodon-client--form-user-from-vars)) + (key (concat "user-" username)) + (secrets `( :client_id ,(plist-get user-details :client_id) + :client_secret ,(plist-get user-details :client_secret))) + (sans-secrets + (dolist (x '(:client_id :client_secret) user-details) + (cl-remf user-details x))) (print-length nil) (print-level nil)) - (plstore-put plstore (concat "user-" username) plstore-value nil) + (if mastodon-auth-use-auth-source + ;; auth-source: + (progn + (mastodon-auth-source-token + mastodon-instance-url username token :create) + (plstore-put plstore key sans-secrets secrets)) + ;; plstore encrypted: + (plstore-put plstore key sans-secrets + (append secrets `(:access_token ,token)))) (plstore-save plstore) (plstore-close plstore) - plstore-value)) + (cdr (plstore-get plstore key)))) (defun mastodon-client--make-user-active (user-details) - "USER-DETAILS is a plist consisting of user details." - (let ((plstore (plstore-open (mastodon-client--token-file))) - (print-length nil) - (print-level nil)) - (plstore-put plstore "active-user" user-details nil) + "USER-DETAILS is a plist consisting of user details. +Save it to plstore under key \"active-user\". +If `mastodon-auth-use-auth-source' is non-nil, fetch the access token +from the user's auth source file and add it to the active user entry. +Return a plist of secret and non-secret key/val pairs." + (let* ((plstore (plstore-open (mastodon-client--token-file))) + (handle (plist-get user-details :username)) + (token + (if mastodon-auth-use-auth-source + (mastodon-auth-source-token mastodon-instance-url handle) + (plist-get user-details :access_token))) + (secrets `( :access_token ,token + :client_id ,(plist-get user-details :client_id) + :client_secret ,(plist-get user-details :client_secret))) + (deets (copy-sequence user-details)) + (sans-secrets + (dolist (x '(:client_id :client_secret :access_token) deets) + (cl-remf deets x))) + (print-length nil) + (print-level nil)) + (plstore-put plstore "active-user" sans-secrets secrets) (plstore-save plstore) - (plstore-close plstore))) + (plstore-close plstore) + (append secrets sans-secrets))) (defun mastodon-client--form-user-from-vars () "Create a username from user variable. Return that username. @@ -161,12 +217,12 @@ variables `mastodon-instance-url' and `mastodon-active-user'." (defun mastodon-client--make-current-user-active () "Make the user specified by user variables active user. Return the details (plist)." - (let ((username (mastodon-client--form-user-from-vars)) - user-plist) - (when (setq user-plist - (mastodon-client--general-read (concat "user-" username))) - (mastodon-client--make-user-active user-plist)) - user-plist)) + (let* ((username (mastodon-client--form-user-from-vars)) + (user-plist (mastodon-client--general-read + (concat "user-" username)))) + (when user-plist + (mastodon-client--make-user-active user-plist) + user-plist))) (defun mastodon-client--current-user-active-p () "Return user-details if the current user is active. @@ -180,28 +236,26 @@ Otherwise return nil." (defun mastodon-client--active-user () "Return the details of the currently active user. Details is a plist." - (let ((active-user-details mastodon-client--active-user-details-plist)) - (unless active-user-details - (setq active-user-details - (or (mastodon-client--current-user-active-p) - (mastodon-client--make-current-user-active))) + (or mastodon-client--active-user-details-plist (setq mastodon-client--active-user-details-plist - active-user-details)) - active-user-details)) + (or (mastodon-client--current-user-active-p) + (mastodon-client--make-current-user-active))))) (defun mastodon-client () "Return variable client secrets to use for `mastodon-instance-url'. -Read plist from `mastodon-client--token-file' if variable is nil. -Fetch and store plist if `mastodon-client--read' returns nil." +If `mastodon-client--client-details-alist' is nil, read plist from +`mastodon-client--token-file'. +Fetch and store plist if `mastodon-client--read' returns nil. +Return a plist." (let ((client-details - (cdr (assoc mastodon-instance-url mastodon-client--client-details-alist)))) - (unless client-details - (setq client-details - (or (mastodon-client--read) - (mastodon-client--store))) - (push (cons mastodon-instance-url client-details) - mastodon-client--client-details-alist)) - client-details)) + (cdr (assoc mastodon-instance-url + mastodon-client--client-details-alist)))) + (or client-details + (let ((client-details (or (mastodon-client--read) + (mastodon-client--store)))) + (push (cons mastodon-instance-url client-details) + mastodon-client--client-details-alist) + client-details)))) (provide 'mastodon-client) ;;; mastodon-client.el ends here diff --git a/elpa/mastodon-20241223.1040/mastodon-discover.el b/elpa/mastodon-20250330.1519/mastodon-discover.el similarity index 57% rename from elpa/mastodon-20241223.1040/mastodon-discover.el rename to elpa/mastodon-20250330.1519/mastodon-discover.el index 53f1a926..81dc3937 100644 --- a/elpa/mastodon-20241223.1040/mastodon-discover.el +++ b/elpa/mastodon-20250330.1519/mastodon-discover.el @@ -50,50 +50,50 @@ (description "Mastodon feed viewer") (actions ("Toots" - ("A" "View profile of author" mastodon-profile--get-toot-author) + ("A" "View profile of author" mastodon-profile-get-toot-author) ("b" "Boost" mastodon-toot--boost) ("f" "Favourite" mastodon-toot--favourite) - ("c" "Toggle hidden text (CW)" mastodon-tl--toggle-spoiler-text-in-toot) - ("k" "Bookmark toot" mastodon-toot--toggle-bookmark) - ("v" "Vote on poll" mastodon-tl--poll-vote) - ("n" "Next" mastodon-tl--goto-next-item) - ("p" "Prev" mastodon-tl--goto-prev-item) - ("TAB" "Next link item" mastodon-tl--next-tab-item) - ("S-TAB" "Prev link item" mastodon-tl--previous-tab-item) + ("c" "Toggle hidden text (CW)" mastodon-tl-toggle-spoiler-text-in-toot) + ("k" "Bookmark toot" mastodon-toot-toggle-bookmark) + ("v" "Vote on poll" mastodon-tl-poll-vote) + ("n" "Next" mastodon-tl-goto-next-item) + ("p" "Prev" mastodon-tl-goto-prev-item) + ("TAB" "Next link item" mastodon-tl-next-tab-item) + ("S-TAB" "Prev link item" mastodon-tl-previous-tab-item) ;; NB: (when (require 'mpv etc. calls don't work here - ("C-RET" "Play media" mastodon-tl--mpv-play-video-at-point) + ("C-RET" "Play media" mastodon-tl-mpv-play-video-at-point) ("t" "New toot" mastodon-toot) - ("r" "Reply" mastodon-toot--reply) - ("C" "Copy toot URL" mastodon-toot--copy-toot-url) - ("o" "Open toot URL" mastodon-toot--browse-toot-url) - ("d" "Delete (your) toot" mastodon-toot--delete-toot) - ("D" "Delete and redraft (your) toot" mastodon-toot--delete-toot) - ("e" "Edit (your) toot" mastodon-toot--edit-toot-at-point) - ("E" "View edits of (your) toot" mastodon-toot--view-toot-edits) - ("i" "Pin/Unpin (your) toot" mastodon-toot--pin-toot-toggle) - ("P" "View user profile" mastodon-profile--show-user) - ("a" "Translate toot at point" mastodon-toot--translate-toot-text) - ("T" "View thread" mastodon-tl--thread) - ("v" "Vote on poll" mastodon-tl--poll-vote) + ("r" "Reply" mastodon-toot-reply) + ("C" "Copy toot URL" mastodon-toot-copy-toot-url) + ("o" "Open toot URL" mastodon-toot-browse-toot-url) + ("d" "Delete (your) toot" mastodon-toot-delete-toot) + ("D" "Delete and redraft (your) toot" mastodon-toot-delete-toot) + ("e" "Edit (your) toot" mastodon-toot-edit-toot-at-point) + ("E" "View edits of (your) toot" mastodon-toot-view-toot-edits) + ("i" "Pin/Unpin (your) toot" mastodon-toot-pin-toot-toggle) + ("P" "View user profile" mastodon-profile-show-user) + ("a" "Translate toot at point" mastodon-toot-translate-toot-text) + ("T" "View thread" mastodon-tl-thread) + ("v" "Vote on poll" mastodon-tl-poll-vote) ("," "View toot's favouriters" mastodon-toot--list-toot-favouriters) ("." "View toot's boosters" mastodon-toot--list-toot-boosters) ("/" "Switch buffers" mastodon-switch-to-buffer)) ("Views" ("h/?" "View mode help/keybindings" describe-mode) - ("#" "Tag search" mastodon-tl--get-tag-timeline) - ("\"" "List followed tags" mastodon-tl--list-followed-tags) - ("'" "Followed tags timeline" mastodon-tl--followed-tags-timeline) - ("F" "Federated" mastodon-tl--get-federated-timeline) - ("H" "Home" mastodon-tl--get-home-timeline) - ("L" "Local" mastodon-tl--get-local-timeline) + ("#" "Tag search" mastodon-tl-get-tag-timeline) + ("\"" "List followed tags" mastodon-tl-list-followed-tags) + ("'" "Followed tags timeline" mastodon-tl-followed-tags-timeline) + ("F" "Federated" mastodon-tl-get-federated-timeline) + ("H" "Home" mastodon-tl-get-home-timeline) + ("L" "Local" mastodon-tl-get-local-timeline) ("N" "Notifications" mastodon-notifications-get) - ("@" "Notifications with mentions" mastodon-notifications--get-mentions) - ("g/u" "Update timeline" mastodon-tl--update) - ("s" "Search" mastodon-search--query) - ("O" "Jump to your profile" mastodon-profile--my-profile) - ("U" "Update your profile note" mastodon-profile--update-user-profile-note) - ("K" "View bookmarks" mastodon-profile--view-bookmarks) - ("V" "View favourites" mastodon-profile--view-favourites) + ("@" "Notifications with mentions" mastodon-notifications-get-mentions) + ("g/u" "Update timeline" mastodon-tl-update) + ("s" "Search" mastodon-search-query) + ("O" "Jump to your profile" mastodon-profile-my-profile) + ("U" "Update your profile note" mastodon-profile-update-user-profile-note) + ("K" "View bookmarks" mastodon-profile-view-bookmarks) + ("V" "View favourites" mastodon-profile-view-favourites) ("R" "View follow requests" mastodon-profile--view-follow-requests) ("G" "View follow suggestions" mastodon-tl--get-follow-suggestions) ("I" "View filters" mastodon-tl--view-filters) @@ -101,12 +101,12 @@ ("S" "View scheduled toots" mastodon-tl--view-scheduled-toots) (";" "View instance description" mastodon-tl--view-instance-description)) ("Users" - ("W" "Follow" mastodon-tl--follow-user) - ("C-S-W" "Unfollow" mastodon-tl--unfollow-user) - ("M" "Mute" mastodon-tl--mute-user) - ("C-S-M" "Unmute" mastodon-tl--unmute-user) - ("B" "Block" mastodon-tl--block-user) - ("C-S-B" "Unblock" mastodon-tl--unblock-user)) + ("W" "Follow" mastodon-tl-follow-user) + ("C-S-W" "Unfollow" mastodon-tl-unfollow-user) + ("M" "Mute" mastodon-tl-mute-user) + ("C-S-M" "Unmute" mastodon-tl-unmute-user) + ("B" "Block" mastodon-tl-block-user) + ("C-S-B" "Unblock" mastodon-tl-unblock-user)) ("Images" ;; RET errors here also :/ ("/i" "Load full image in browser" 'shr-browse-image) @@ -115,7 +115,7 @@ ("-" "zoom out" 'image-decrease-size) ("u" "copy URL" 'shr-maybe-probe-and-copy-url)) ("Profile view" - ("C-c C-c" "Cycle profile views" mastodon-profile--account-view-cycle)) + ("C-c C-c" "Cycle profile views" mastodon-profile-account-view-cycle)) ("Quit" ("q" "Quit mastodon and bury buffer." kill-this-buffer) ("Q" "Quit mastodon buffer and kill window." mastodon--kill-window) diff --git a/elpa/mastodon-20241223.1040/mastodon-http.el b/elpa/mastodon-20250330.1519/mastodon-http.el similarity index 78% rename from elpa/mastodon-20241223.1040/mastodon-http.el rename to elpa/mastodon-20250330.1519/mastodon-http.el index 47b96fed..a55097df 100644 --- a/elpa/mastodon-20241223.1040/mastodon-http.el +++ b/elpa/mastodon-20250330.1519/mastodon-http.el @@ -31,8 +31,8 @@ ;;; Code: (require 'json) -(require 'request) ; for attachments upload (require 'url) +(require 'url-http) (require 'shr) (defvar mastodon-instance-url) @@ -41,6 +41,7 @@ (autoload 'mastodon-auth--access-token "mastodon-auth") (autoload 'mastodon-toot--update-status-fields "mastodon-toot") +(autoload 'url-insert "url-handlers") (defvar mastodon-http--api-version "v1") @@ -57,23 +58,6 @@ Optionally specify VERSION in format vX." "Return Mastodon API v2 URL for ENDPOINT." (mastodon-http--api endpoint "v2")) -(defun mastodon-http--response () - "Capture response buffer content as string." - (with-current-buffer (current-buffer) - (buffer-substring-no-properties (point-min) (point-max)))) - -(defun mastodon-http--response-body (pattern) - "Return substring matching PATTERN from `mastodon-http--response'." - (let ((resp (mastodon-http--response))) - (string-match pattern resp) - (match-string 0 resp))) - -(defun mastodon-http--status () - "Return HTTP Response Status Code from `mastodon-http--response'." - (let* ((status-line (mastodon-http--response-body "^HTTP/1.*$"))) - (string-match "[0-9][0-9][0-9]" status-line) - (match-string 0 status-line))) - (defun mastodon-http--url-retrieve-synchronously (url &optional silent) "Retrieve URL asynchronously. This is a thin abstraction over the system @@ -86,13 +70,16 @@ SILENT means don't message." (defun mastodon-http--triage (response success) "Determine if RESPONSE was successful. -Call SUCCESS if successful. Message status and JSON error from -RESPONSE if unsuccessful." +Call SUCCESS on RESPONSE if successful. Message status and JSON error +from RESPONSE if unsuccessful." (let ((status (with-current-buffer response - (mastodon-http--status)))) - (if (string-prefix-p "2" status) + ;; FIXME: breaks tests, as url-http-end-of-headers not set + (url-http-parse-response)))) + (if (and (>= 200 status) + (<= status 299)) + ;; (string-prefix-p "2" (number-to-string status)) (funcall success response) - (if (string-prefix-p "404" status) + (if (= 404 status) (message "Error %s: page not found" status) (let ((json-response (with-current-buffer response (mastodon-http--process-json)))) @@ -151,7 +138,7 @@ Authorization header is included by default unless UNAUTHENTICATED-P is non-nil. If JSON is :json, encode PARAMS as JSON for the request data. If it is :raw, just use the plain params." - ;; NB: raw is used by `mastodon-tl--unfilter-user-languages'; not sure if + ;; NB: raw is used by `mastodon-tl-unfilter-user-languages'; not sure if ;; there's a way around it? (mastodon-http--authorized-request "POST" (let* ((url-request-data @@ -189,13 +176,13 @@ PARAMS is an alist of any extra parameters to send with the request. SILENT means don't message. NO-HEADERS means don't collect http response headers. VECTOR means return json arrays as vectors." -(let ((buf (mastodon-http--get url params silent))) - ;; --get can return nil if instance unresponsive: - (if (not buf) - (user-error "Looks like the server response borked. \ + (let ((buf (mastodon-http--get url params silent))) + ;; --get can return nil if instance unresponsive: + (if (not buf) + (user-error "Looks like the server response borked. \ Is your instance up?") - (with-current-buffer buf - (mastodon-http--process-response no-headers vector))))) + (with-current-buffer buf + (mastodon-http--process-response no-headers vector))))) (defun mastodon-http--get-json (url &optional params silent vector) "Return only JSON data from URL request. @@ -315,8 +302,9 @@ JSON means send params as JSON data." (encode-coding-string (json-encode params) 'utf-8))) ;; (mastodon-http--build-params-string params)))) - (url (unless json - (mastodon-http--concat-params-to-url url params))) + (url (if (not json) + (mastodon-http--concat-params-to-url url params) + url)) (headers (when json '(("Content-Type" . "application/json") ("Accept" . "application/json")))) @@ -359,68 +347,80 @@ PARAMS is an alist of any extra parameters to send with the request." Then run function CALLBACK with arguements CBARGS. Authorization header is included by default unless UNAUTHENTICED-P is non-nil." (mastodon-http--authorized-request "POST" - (let (;(request-timeout 5) ; this is from request.el no url.el! - (url-request-data (when params + (let ((url-request-data (when params (mastodon-http--build-params-string params)))) (with-temp-buffer (url-retrieve url callback cbargs))))) -;; TODO: test for curl first? +(defun mastodon-http--get-cb-data (status) + "Return data using `json-read' after a successful async request. +If STATUS includes an error, emit a message describing it and return nil." + (let* ((buf (current-buffer)) + (data (with-temp-buffer + (url-insert buf) + (goto-char (point-min)) + (json-read)))) + (if-let* ((error-thrown (plist-get status :error))) + ;; not necessarily a user error, but we want its functionality: + (user-error "%S %s" error-thrown (alist-get 'error data)) + data))) + +(defun mastodon-http--post-media-callback (status file caption buffer) + "Callback function called after posting FILE as an attachment with CAPTION. +The toot is being composed in BUFFER. See `url-retrieve' for STATUS." + (unwind-protect + (when-let* ((data (mastodon-http--get-cb-data status))) + (with-current-buffer buffer + (let ((id (alist-get 'id data))) + ;; update ids: + (if (not mastodon-toot--media-attachment-ids) + ;; add first id: + (push id mastodon-toot--media-attachment-ids) + ;; add new id to end of list to preserve order: + (push id (cdr + (last mastodon-toot--media-attachment-ids)))) + ;; pleroma, PUT the description: + ;; this is how the mangane akkoma web client does it + ;; and it seems easier than the other options! + (when (and caption + (not (string= caption (alist-get 'description data)))) + (let ((url (mastodon-http--api (format "media/%s" id)))) + ;; (message "PUTting image description") + (mastodon-http--put url `(("description" . ,caption))))) + (message "Uploading %s... (done)" file) + (mastodon-toot--update-status-fields)))) + (kill-buffer (current-buffer)))) + +(defun mastodon-http--post-media-prep-file (filename) + "Return the request data to upload FILENAME." + (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally filename) + (let ((boundary (buffer-hash))) + (goto-char (point-min)) + (insert "--" boundary "\r\n" + (format "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n\r\n" + (file-name-nondirectory filename))) + (goto-char (point-max)) + (insert "\r\n" "--" boundary "--" "\r\n") + `(,boundary . ,(buffer-substring-no-properties (point-min) (point-max)))))) + (defun mastodon-http--post-media-attachment (url filename caption) "Make POST request to upload FILENAME with CAPTION to the server's media URL. The upload is asynchronous. On succeeding, `mastodon-toot--media-attachment-ids' is set to the id(s) of the item uploaded, and `mastodon-toot--update-status-fields' is run." - (let* ((file (file-name-nondirectory filename)) - (request-backend 'curl) - (desc `(("description" . ,caption))) - (cb (cl-function - (lambda (&key data &allow-other-keys) - (when data - (let* ((id (alist-get 'id data))) - ;; update ids: - (push id mastodon-toot--media-attachment-ids) - ;; pleroma, PUT the description: - ;; this is how the mangane akkoma web client does it - ;; and it seems easier than the other options! - (when (and caption - (not (string= caption (alist-get 'description data)))) - (let ((url (mastodon-http--api (format "media/%s" id)))) - ;; (message "PUTting image description") - (mastodon-http--put url desc))) - (message "Uploading %s... (done)" file) - (mastodon-toot--update-status-fields))))))) - (request - url - :type "POST" - :params desc - :files `(("file" . (,file :file ,filename - :mime-type "multipart/form-data"))) - :parser 'json-read - :headers `(("Authorization" . ,(concat "Bearer " - (mastodon-auth--access-token)))) - :sync nil - :success (apply-partially cb) - :error (cl-function - (lambda (&key error-thrown &allow-other-keys) - (cond - ;; handle curl errors first (eg 26, can't read file/path) - ;; because the '=' test below fails for them - ;; they have the form (error . error message 24) - ((not (proper-list-p error-thrown)) ; not dotted list - (message "Got error: %s. Shit went south." (cdr error-thrown))) - ;; handle mastodon api errors - ;; they have the form (error http 401) - ((= (car (last error-thrown)) 401) - (message "Got error: %s Unauthorized: The access token is invalid" - error-thrown)) - ((= (car (last error-thrown)) 422) - (message "Got error: %s Unprocessable entity: file or file\ - type is unsupported or invalid" - error-thrown)) - (t - (message "Got error: %s Shit went south" - error-thrown)))))))) + (mastodon-http--authorized-request "POST" + (let* ((data (mastodon-http--post-media-prep-file filename)) + (url-request-extra-headers + (append url-request-extra-headers ; auth set in macro + `(("Content-Type" . ,(format "multipart/form-data; boundary=%s" + (car data)))))) + (url-request-data (cdr data)) + (params `(("description" . ,caption))) + (url (mastodon-http--concat-params-to-url url params))) + (url-retrieve url #'mastodon-http--post-media-callback + `(,filename ,caption ,(current-buffer)))))) (provide 'mastodon-http) ;;; mastodon-http.el ends here diff --git a/elpa/mastodon-20241223.1040/mastodon-inspect.el b/elpa/mastodon-20250330.1519/mastodon-inspect.el similarity index 100% rename from elpa/mastodon-20241223.1040/mastodon-inspect.el rename to elpa/mastodon-20250330.1519/mastodon-inspect.el diff --git a/elpa/mastodon-20241223.1040/mastodon-iso.el b/elpa/mastodon-20250330.1519/mastodon-iso.el similarity index 100% rename from elpa/mastodon-20241223.1040/mastodon-iso.el rename to elpa/mastodon-20250330.1519/mastodon-iso.el diff --git a/elpa/mastodon-20241223.1040/mastodon-media.el b/elpa/mastodon-20250330.1519/mastodon-media.el similarity index 95% rename from elpa/mastodon-20241223.1040/mastodon-media.el rename to elpa/mastodon-20250330.1519/mastodon-media.el index cfb20de0..0e3c24a2 100644 --- a/elpa/mastodon-20241223.1040/mastodon-media.el +++ b/elpa/mastodon-20250330.1519/mastodon-media.el @@ -39,6 +39,7 @@ (autoload 'mastodon-tl--propertize-img-str-or-url "mastodon-tl") (autoload 'mastodon-tl--image-trans-check "mastodon-tl") +(autoload 'mastodon-image-mode "mastodon-tl") (defvar url-show-status) @@ -332,13 +333,19 @@ image-data prop so it can be toggled." mastodon-media--sensitive-image-data nil t) sensitive-state hidden image-data ,image)))) -(defun mastodon-media--process-full-sized-image-response (status-plist url) +(defvar mastodon-media--attachments nil + "A list attachment details for full sized image view buffer. +The first element is the URL of the image displayed, followed by plists +of details of all of a toot's attachments.") + +(defun mastodon-media--process-full-sized-image-response + (status-plist url attachments &optional prev-buf) ;; FIXME: refactor this with but not into ;; `mastodon-media--process-image-response'. "Callback function processing the `url-retrieve' response for URL. URL is a full-sized image URL attached to a timeline image. STATUS-PLIST is a plist of status events as per `url-retrieve'." - (if-let (error-response (plist-get status-plist :error)) + (if-let* ((error-response (plist-get status-plist :error))) (user-error "error in loading image: %S" error-response) (when mastodon-media--enable-image-caching (unless (url-is-cached url) ;; cache if not already cached @@ -347,16 +354,25 @@ STATUS-PLIST is a plist of status events as per `url-retrieve'." ;; https://codeberg.org/martianh/mastodon.el/issues/540 (let* ((handle (mm-dissect-buffer t)) (image (mm-get-image handle)) - (str (image-property image :data))) - (with-current-buffer (get-buffer-create "*masto-image*") + (str (image-property image :data)) + (buf "*masto-image*")) + (with-current-buffer (get-buffer-create buf) (let ((inhibit-read-only t)) (erase-buffer) (insert-image image str) (special-mode) ; prevent image-mode loop bug - (image-mode) + (mastodon-image-mode) ;; for our keymap (goto-char (point-min)) - (switch-to-buffer-other-window (current-buffer)) - (image-transform-fit-both)))))) + (image-transform-fit-both) + ;; set image metadata for view cycling: + (setq-local mastodon-media--attachments (cons url attachments)))) + ;; switch to buf if not already viewing it: + (unless (equal buf prev-buf) + (switch-to-buffer-other-window buf)) + ;; display bindings if multiple images: + (when (< 1 (length (cdr mastodon-media--attachments))) + (message (substitute-command-keys + "\\`.'/\\`>'/\\`' to cycle images")))))) (defun mastodon-media--image-or-cached (url process-fun args) "Fetch URL from cache or fro host. @@ -467,7 +483,7 @@ START and END are the beginning and end of the media item to overlay." (propertize "" 'help-echo "Video" 'face - '((:height 3.5 :inherit font-lock-comment-face)))))) + '((:height 3.5 :inherit mastodon-toot-docs-face)))))) ;; (cl-pushnew ov mastodon-media--overlays))) (defun mastodon-media--get-avatar-rendering (avatar-url) @@ -499,8 +515,8 @@ CAPTION is the image caption if provided. SENSITIVE is a flag from the item's JSON data." (let* ((help-echo-base (substitute-command-keys - (concat "\\`RET'/\\`i': load full image (prefix: copy URL), \\`+'/\\`-': zoom,\ - \\`r': rotate, \\`o': save preview" + (concat "\\`RET': load full image or play video,\ + \\`i' for image options" (when (not (eq sensitive :json-false)) ", \\`S': toggle sensitive media")))) (help-echo (if caption diff --git a/elpa/mastodon-20241223.1040/mastodon-notifications.el b/elpa/mastodon-20250330.1519/mastodon-notifications.el similarity index 76% rename from elpa/mastodon-20241223.1040/mastodon-notifications.el rename to elpa/mastodon-20250330.1519/mastodon-notifications.el index 5790757c..e21aad30 100644 --- a/elpa/mastodon-20241223.1040/mastodon-notifications.el +++ b/elpa/mastodon-20250330.1519/mastodon-notifications.el @@ -52,8 +52,8 @@ (autoload 'mastodon-tl--reload-timeline-or-profile "mastodon-tl") (autoload 'mastodon-tl--spoiler "mastodon-tl") (autoload 'mastodon-tl--item-id "mastodon-tl") -(autoload 'mastodon-tl--update "mastodon-tl") -(autoload 'mastodon-views--view-follow-requests "mastodon-views") +(autoload 'mastodon-tl-update "mastodon-tl") +(autoload 'mastodon-views-view-follow-requests "mastodon-views") (autoload 'mastodon-tl--current-filters "mastodon-views") (autoload 'mastodon-tl--render-text "mastodon-tl") (autoload 'mastodon-notifications-get "mastodon") @@ -64,10 +64,13 @@ (autoload 'mastodon-tl--image-trans-check "mastodon-tl") (autoload 'mastodon-tl--symbol "mastodon-tl") (autoload 'mastodon-tl--display-or-uname "mastodon-tl") -(autoload 'mastodon-tl--goto-next-item "mastodon-tl") +(autoload 'mastodon-tl-goto-next-item "mastodon-tl") (autoload 'mastodon-tl--buffer-type-eq "mastodon-tl") (autoload 'mastodon-tl--buffer-property "mastodon-tl") (autoload 'mastodon-http--patch "mastodon-http") +(autoload 'mastodon-views--minor-view "mastodon-views") +(autoload 'mastodon-tl--goto-first-item "mastodon-tl") +(autoload 'mastodon-tl--init-sync "mastodon-tl") ;; notifications defcustoms moved into mastodon.el ;; as some need to be available without loading this file @@ -85,16 +88,17 @@ (defvar mastodon-profile-note-in-foll-reqs-max-length) (defvar mastodon-group-notifications) (defvar mastodon-notifications-grouped-names-count) - +(defvar mastodon-tl--link-keymap) +(defvar mastodon-tl--update-point) ;;; VARIABLES (defvar mastodon-notifications--map (let ((map (make-sparse-keymap))) (set-keymap-parent map mastodon-mode-map) - (define-key map (kbd "a") #'mastodon-notifications--follow-request-accept) - (define-key map (kbd "j") #'mastodon-notifications--follow-request-reject) - (define-key map (kbd "C-k") #'mastodon-notifications--clear-current) - (define-key map (kbd "C-c C-c") #'mastodon-notifications--cycle-type) + (define-key map (kbd "a") #'mastodon-notifications-follow-request-accept) + (define-key map (kbd "j") #'mastodon-notifications-follow-request-reject) + (define-key map (kbd "C-k") #'mastodon-notifications-clear-current) + (define-key map (kbd "C-c C-c") #'mastodon-notifications-cycle-type) map) "Keymap for viewing notifications.") @@ -106,14 +110,14 @@ (defvar mastodon-notifications--filter-types-alist '(("all" . mastodon-notifications-get) - ("favourite" . mastodon-notifications--get-favourites) - ("reblog" . mastodon-notifications--get-boosts) - ("mention" . mastodon-notifications--get-mentions) - ("poll" . mastodon-notifications--get-polls) - ("follow_request" . mastodon-notifications--get-follow-requests) - ("follow" . mastodon-notifications--get-follows) - ("status" . mastodon-notifications--get-statuses) - ("update" . mastodon-notifications--get-edits)) + ("favourite" . mastodon-notifications-get-favourites) + ("reblog" . mastodon-notifications-get-boosts) + ("mention" . mastodon-notifications-get-mentions) + ("poll" . mastodon-notifications-get-polls) + ("follow_request" . mastodon-notifications-get-follow-requests) + ("follow" . mastodon-notifications-get-follows) + ("status" . mastodon-notifications-get-statuses) + ("update" . mastodon-notifications-get-edits)) "An alist of notification types and their corresponding load functions. Notification types are named according to their name on the server.") @@ -128,10 +132,6 @@ Notification types are named according to their name on the server.") ("Edited" . "their post")) "Alist of subjects for notification types.") -(defvar mastodon-notifications-grouped-types - '(follow reblog favourite) - "List of notification types for which grouping is implemented.") - (defvar mastodon-notifications--action-alist '((reblog . "Boosted") (favourite . "Favourited") @@ -209,18 +209,18 @@ follow-requests view." response (lambda (_) (if f-reqs-view-p - (mastodon-views--view-follow-requests) + (mastodon-views-view-follow-requests) (mastodon-tl--reload-timeline-or-profile)) (message "Follow request of %s (@%s) %s!" .username .acct (if reject "rejected" "accepted"))))))))))) -(defun mastodon-notifications--follow-request-accept () +(defun mastodon-notifications-follow-request-accept () "Accept a follow request. Can be called in notifications view or in follow-requests view." (interactive) (mastodon-notifications--follow-request-process)) -(defun mastodon-notifications--follow-request-reject () +(defun mastodon-notifications-follow-request-reject () "Reject a follow request. Can be called in notifications view or in follow-requests view." (interactive) @@ -298,8 +298,7 @@ Can be called in notifications view or in follow-requests view." str)))) (status (mastodon-tl--field 'status note)) (follower (alist-get 'account note)) - (follower-name (or (alist-get 'display_name follower) - (alist-get 'username follower))) + (follower-name (mastodon-notifications--follower-name follower)) (filtered (mastodon-tl--field 'filtered status)) (filters (when filtered (mastodon-tl--current-filters filtered)))) @@ -336,8 +335,7 @@ ACCOUNTS is data of the accounts that have reacted to the notification." str)))) (follower (when (member type '(follow follow_request)) (car accounts))) - (follower-name (or (alist-get 'display_name follower) - (alist-get 'username follower))) + (follower-name (mastodon-notifications--follower-name follower)) (filtered (mastodon-tl--field 'filtered status)) (filters (when filtered (mastodon-tl--current-filters filtered)))) @@ -358,6 +356,12 @@ ACCOUNTS is data of the accounts that have reacted to the notification." status) folded group accounts)))))) +(defun mastodon-notifications--follower-name (follower) + "Return display_name or username of FOLLOWER." + (if (not (string= "" (alist-get 'display_name follower))) + (alist-get 'display_name follower) + (alist-get 'username follower))) + (defun mastodon-notifications--comment-note-text (str) "Add comment face to all text in STR with `shr-text' face only." (with-temp-buffer @@ -367,7 +371,7 @@ ACCOUNTS is data of the accounts that have reacted to the notification." (while (setq prop (text-property-search-forward 'face 'shr-text t)) (add-text-properties (prop-match-beginning prop) (prop-match-end prop) - '(face (font-lock-comment-face shr-text))))) + '(face (mastodon-toot-docs-face shr-text))))) (buffer-string))) (defun mastodon-notifications--body-arg @@ -377,7 +381,7 @@ The string returned is passed to `mastodon-notifications--insert-note'. TYPE is a symbol, a member of `mastodon-notifiations--types'. FILTERS STATUS PROFILE-NOTE FOLLOWER-NAME GROUP NOTE." (let ((body - (if-let ((match (assoc "warn" filters))) + (if-let* ((match (assoc "warn" filters))) (mastodon-tl--spoiler status (cadr match)) (mastodon-tl--clean-tabs-and-nl (cond ((mastodon-tl--has-spoiler status) @@ -578,23 +582,22 @@ When AVATAR, include the account's avatar image." "Display grouped notifications in JSON. NO-GROUP means don't render grouped notifications." ;; (setq masto-grouped-notifs json) - (if no-group - (cl-loop for x in json - do (mastodon-notifications--format-note x)) - (let ((groups (alist-get 'notification_groups json))) + (let ((start-pos (point))) + (if no-group + (cl-loop for x in json + do (mastodon-notifications--format-note x)) (cl-loop - for g in groups - for start-pos = (point) + for g in (alist-get 'notification_groups json) for accounts = (mastodon-notifications--group-accounts (alist-get 'sample_account_ids g) (alist-get 'accounts json)) for type = (alist-get 'type g) for status = (mastodon-notifications--status-or-event g type json) - do (mastodon-notifications--format-group-note g status accounts) - (when mastodon-tl--display-media-p - ;; images-in-notifs custom is handeld in - ;; `mastodon-tl--media-attachment', not here - (mastodon-media--inline-images start-pos (point))))))) + do (mastodon-notifications--format-group-note g status accounts))) + (when mastodon-tl--display-media-p + ;; images-in-notifs custom is handeld in + ;; `mastodon-tl--media-attachment', not here + (mastodon-media--inline-images start-pos (point))))) (defun mastodon-notifications--status-or-event (group type json) "Return a notification's status or event data. @@ -609,6 +612,10 @@ Using GROUP data, notification TYPE, and overall notifs JSON." 'id (alist-get 'statuses json)))) +(defun mastodon-notifications--empty-group-json-p (json) + "Non-nil if JSON is empty grouped notifs data." + (equal json '((accounts) (statuses) (notification_groups)))) + (defun mastodon-notifications--timeline (json &optional type update) "Format JSON in Emacs buffer. Optionally specify TYPE. @@ -621,23 +628,32 @@ UPDATE means we are updating, so skip some things." (or type "all") (lambda (widget &rest _ignore) (let ((value (widget-value widget))) - (mastodon-notifications--get-type value))) + (mastodon-notifications-get-type value))) :newline) (insert "\n")) + ;; filtered/requests message: + (when (mastodon-notifications--notif-requests) + (insert + (substitute-command-keys + "You have filtered notifications. \ +\\[mastodon-notifications-requests] to view requests.\n\n"))) + ;; set update point: + (setq mastodon-tl--update-point (point)) + ;; render: (mastodon-notifications--render json (not mastodon-group-notifications)) (goto-char (point-min)) ;; set last read notif ID: (save-excursion - (mastodon-tl--goto-next-item :no-refresh) + (mastodon-tl-goto-next-item :no-refresh) (let ((id (mastodon-tl--property 'item-id))) ;; notif not base (mastodon-notifications--set-last-read id))) (unless update ;; already in tl--update - (mastodon-tl--goto-next-item)))) + (mastodon-tl-goto-next-item)))) ;;; VIEW LOADING FUNCTIONS -(defun mastodon-notifications--get-type (&optional type) +(defun mastodon-notifications-get-type (&optional type) "Read a notification type and load its timeline. Optionally specify TYPE." (interactive) @@ -649,7 +665,7 @@ Optionally specify TYPE." choice mastodon-notifications--filter-types-alist nil nil #'equal)))) -(defun mastodon-notifications--cycle-type (&optional prefix) +(defun mastodon-notifications-cycle-type (&optional prefix) "Cycle the current notifications view. With arg PREFIX, `completing-read' a type and load it." (interactive "P") @@ -680,44 +696,44 @@ With arg PREFIX, `completing-read' a type and load it." (or (cadr (member type mastodon-notifications--types)) (car mastodon-notifications--types))))) -(defun mastodon-notifications--get-mentions () +(defun mastodon-notifications-get-mentions () "Display mention notifications in buffer." (interactive) (mastodon-notifications-get "mention" "mentions")) -(defun mastodon-notifications--get-favourites () +(defun mastodon-notifications-get-favourites () "Display favourite notifications in buffer." (interactive) (mastodon-notifications-get "favourite" "favourites")) -(defun mastodon-notifications--get-boosts () +(defun mastodon-notifications-get-boosts () "Display boost notifications in buffer." (interactive) (mastodon-notifications-get "reblog" "boosts")) -(defun mastodon-notifications--get-polls () +(defun mastodon-notifications-get-polls () "Display poll notifications in buffer." (interactive) (mastodon-notifications-get "poll" "polls")) -(defun mastodon-notifications--get-statuses () +(defun mastodon-notifications-get-statuses () "Display status notifications in buffer. Status notifications are created when you call -`mastodon-tl--enable-notify-user-posts'." +`mastodon-tl-enable-notify-user-posts'." (interactive) (mastodon-notifications-get "status" "statuses")) -(defun mastodon-notifications--get-follows () +(defun mastodon-notifications-get-follows () "Display follow notifications in buffer." (interactive) (mastodon-notifications-get "follow" "follows")) -(defun mastodon-notifications--get-follow-requests () +(defun mastodon-notifications-get-follow-requests () "Display follow request notifications in buffer." (interactive) (mastodon-notifications-get "follow_request" "follow-requests")) -(defun mastodon-notifications--get-edits () +(defun mastodon-notifications-get-edits () "Display edited post notifications in buffer." (interactive) (mastodon-notifications-get "update" "edits")) @@ -728,7 +744,7 @@ Status notifications are created when you call ;;; CLEAR/DISMISS NOTIFS -(defun mastodon-notifications--clear-all () +(defun mastodon-notifications-clear-all () "Clear all notifications." (interactive) (when (y-or-n-p "Clear all notifications?") @@ -741,14 +757,17 @@ Status notifications are created when you call (mastodon-tl--reload-timeline-or-profile)) (message "All notifications cleared!")))))) -(defun mastodon-notifications--clear-current () +(defun mastodon-notifications-clear-current () "Dismiss the notification at point." (interactive) - (let* ((id (or (or (mastodon-tl--property 'notification-id) ;; grouped - (mastodon-tl--property 'item-id) - (mastodon-tl--field - 'id - (mastodon-tl--property 'item-json))))) + (let* ((id (or ;; grouping enabled + ;; (*should* also work for ungrouped items): + (mastodon-tl--property 'notification-id) + ;; FIXME: are these all required? + (mastodon-tl--property 'item-id) + (mastodon-tl--field + 'id + (mastodon-tl--property 'item-json)))) (endpoint (mastodon-notifications--api (format "notifications/%s/dismiss" id))) (response (mastodon-http--post endpoint))) @@ -773,19 +792,15 @@ Status notifications are created when you call (resp (mastodon-http--get-json endpoint params))) (map-nested-elt resp '(notifications last_read_id)))) -(defun mastodon-notifications--get-single-notif () +(defun mastodon-notifications-get-single-notif () "Return a single notification JSON for v2 notifs." (interactive) - (let* ((id (mastodon-tl--property - 'notification-id)) ;; grouped, doesn't work for ungrouped! - ;; (key (format "ungrouped-%s" - ;; (mastodon-tl--property 'item-id))) + (let* ((id ;; grouped (should work for ungrouped items): + (mastodon-tl--property 'notification-id)) (endpoint (mastodon-notifications--api (format "notifications/%s" id))) (response (mastodon-http--get-json endpoint))) - (mastodon-http--triage - response (lambda (response) - (message "%s" (prin1-to-string response)))))) + (message "%s" (prin1-to-string response)))) (defun mastodon-notifications--get-unread-count () "Return the number of unread notifications for the current account." @@ -796,23 +811,148 @@ Status notifications are created when you call (resp (mastodon-http--get-json url))) (alist-get 'count resp))) -(defvar mastodon-notifications--policy-vals +;;; NOTIFICATION REQUESTS / FILTERING / POLICY + +(defvar mastodon-notifications--requests-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map mastodon-mode-map) + (define-key map (kbd "j") #'mastodon-notifications-request-reject) + (define-key map (kbd "a") #'mastodon-notifications-request-accept) + (define-key map (kbd "g") #'mastodon-notifications-requests) + map) + "Keymap for viewing follow requests.") + +;; FIXME: these are only for grouped notifs, else the fields are JSON bools +(defvar mastodon-notifications-policy-vals '("accept" "filter" "drop")) -(defun mastodon-notifications--get-policy () +(defun mastodon-notifications-get-policy () "Return the notification filtering policy." - (interactive) - (let ((url - (mastodon-notifications--api "notifications/policy"))) + (let ((url (mastodon-notifications--api "notifications/policy"))) (mastodon-http--get-json url))) +(defun mastodon-notifications--notif-requests () + "Non-nil if the user currently has pending/filtered notifications. +Returns" + (let* ((policy (mastodon-notifications-get-policy)) + (count (map-nested-elt policy '(summary pending_notifications_count)))) + (if (and count (> count 0)) + count))) + +(defun mastodon-notifications--pending-p () + "Non-nil if there are any pending requests or notifications." + (let* ((json (mastodon-notifications-get-policy)) + (summary (alist-get 'summary json))) + (or (not (= 0 (alist-get 'pending_requests_count summary))) + (not (= 0 (alist-get 'pending_notifications_count summary)))))) + (defun mastodon-notifications--update-policy (&optional params) "Update notifications filtering policy. PARAMS is an alist of parameters." ;; https://docs.joinmastodon.org/methods/notifications/#update-the-filtering-policy-for-notifications - (let ((url - (mastodon-notifications--api "notifications/policy"))) + (let ((url (mastodon-notifications--api "notifications/policy"))) (mastodon-http--patch url params))) +(defun mastodon-notifications--get-requests (&optional params) + "Get a list of notification requests data from the server. +PARAMS is an alist of parameters." + ;; NB: link header pagination + (let ((url (mastodon-notifications--api "notifications/requests"))) + (mastodon-http--get-json url params))) + +(defun mastodon-notifications-request-accept (&optional reject) + "Accept a notification request for a user. +This will merge any filtered notifications from them into the main +notifications and accept any future notification from them. +REJECT means reject notifications instead." + ;; POST /api/v1/notifications/requests/:id/accept + (interactive) + (let* ((id (mastodon-tl--property 'item-id)) + (user (mastodon-tl--property 'notif-req-user)) + (url (mastodon-http--api + (format "notifications/requests/%s/%s" + id (if reject "dismiss" "accept")))) + (resp (mastodon-http--post url))) + (mastodon-http--triage + resp + (lambda (_resp) + (message "%s notifications from %s" + (if reject "Not accepting" "Accepting") user) + ;; reload view: + (mastodon-notifications-requests))))) + +(defun mastodon-notifications-request-reject () + "Reject a notification request for a user. +Rejecting a request means any notifications from them will continue to +be filtered." + (interactive) + (mastodon-notifications-request-accept :reject)) + +(defun mastodon-notifications-requests () + "Open a new buffer displaying the user's notification requests." + ;; calqued off `mastodon-views-view-follow-requests' + (interactive) + (mastodon-tl--init-sync + "notification-requests" + "notifications/requests" + 'mastodon-views--insert-notification-requests + nil + '(("limit" . "40")) ; server max is 80 + :headers + "notification requests" + "a/j - accept/reject request at point\n\ + n/p - go to next/prev request\n\ + \\[mastodon-notifications-policy] - set filtering policy") + (mastodon-tl--goto-first-item) + (with-current-buffer "*mastodon-notification-requests*" + (use-local-map mastodon-notifications--requests-map))) + +(defun mastodon-views--insert-notification-requests (json) + "Insert the user's current notification requests. +JSON is the data returned by the server." + (mastodon-views--minor-view + "notification requests" + #'mastodon-notifications--insert-users + json)) +;; masto-notif-req)) + +(defun mastodon-notifications--insert-users (json) + "Insert users list into the buffer. +JSON is the data from the server." + ;; calqued off `mastodon-views--insert-users-propertized-note' + ;; and `mastodon-search--insert-users-propertized' + (cl-loop for req in json + do (insert + (concat + (mastodon-notifications--format-req-user req) + mastodon-tl--horiz-bar "\n\n")))) + +(defun mastodon-notifications--format-req-user (req &optional note) + "Format a notification request user, REQ. +NOTE means to include a profile note." + ;; calqued off `mastodon-search--propertize-user' + (let-alist req + (propertize + (concat + (propertize .account.username + 'face 'mastodon-display-name-face + 'byline t + 'notif-req-user .account.username + 'item-type 'notif-req + 'item-id .id) ;; notif req id + " : \n : " + (propertize (concat "@" .account.acct) + 'face 'mastodon-handle-face + 'mouse-face 'highlight + 'mastodon-tab-stop 'user-handle + 'keymap mastodon-tl--link-keymap + 'mastodon-handle (concat "@" .account.acct) + 'help-echo (concat "Browse user profile of @" .account.acct)) + " : \n" + (when note + (mastodon-tl--render-text .account.note .account)) + "\n") + 'item-json req))) + (provide 'mastodon-notifications) ;;; mastodon-notifications.el ends here diff --git a/elpa/mastodon-20241223.1040/mastodon-pkg.el b/elpa/mastodon-20250330.1519/mastodon-pkg.el similarity index 69% rename from elpa/mastodon-20241223.1040/mastodon-pkg.el rename to elpa/mastodon-20250330.1519/mastodon-pkg.el index 14d0de12..184d35c6 100644 --- a/elpa/mastodon-20241223.1040/mastodon-pkg.el +++ b/elpa/mastodon-20250330.1519/mastodon-pkg.el @@ -1,13 +1,12 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "mastodon" "20241223.1040" +(define-package "mastodon" "20250330.1519" "Client for fediverse services using the Mastodon API." '((emacs "28.1") - (request "0.3.0") (persist "0.4") - (tp "0.6")) + (tp "0.7")) :url "https://codeberg.org/martianh/mastodon.el" - :commit "e2443f1cd425b31228e87739d1fc5035640bba06" - :revdesc "e2443f1cd425" + :commit "163ba2b0b89a292b99bff0f574f5584ec888469a" + :revdesc "163ba2b0b89a" :authors '(("Johnson Denen" . "johnson.denen@gmail.com") ("Marty Hiatt" . "mousebot@disroot.org")) :maintainers '(("Marty Hiatt" . "mousebot@disroot.org"))) diff --git a/elpa/mastodon-20241223.1040/mastodon-profile.el b/elpa/mastodon-20250330.1519/mastodon-profile.el similarity index 89% rename from elpa/mastodon-20241223.1040/mastodon-profile.el rename to elpa/mastodon-20250330.1519/mastodon-profile.el index 417a7872..95d275d2 100644 --- a/elpa/mastodon-20241223.1040/mastodon-profile.el +++ b/elpa/mastodon-20250330.1519/mastodon-profile.el @@ -56,8 +56,8 @@ (autoload 'mastodon-media--get-media-link-rendering "mastodon-media.el") (autoload 'mastodon-media--inline-images "mastodon-media.el") (autoload 'mastodon-mode "mastodon.el") -(autoload 'mastodon-notifications--follow-request-accept "mastodon-notifications") -(autoload 'mastodon-notifications--follow-request-reject "mastodon-notifications") +(autoload 'mastodon-notifications-follow-request-accept "mastodon-notifications") +(autoload 'mastodon-notifications-follow-request-reject "mastodon-notifications") (autoload 'mastodon-search--insert-users-propertized "mastodon-search") (autoload 'mastodon-tl--as-string "mastodon-tl.el") (autoload 'mastodon-tl--buffer-type-eq "mastodon tl") @@ -79,10 +79,10 @@ (autoload 'mastodon-tl--item-id "mastodon-tl") (autoload 'mastodon-toot--count-toot-chars "mastodon-toot") (autoload 'mastodon-toot--get-max-toot-chars "mastodon-toot") -(autoload 'mastodon-views--add-account-to-list "mastodon-views") +(autoload 'mastodon-views-add-account-to-list "mastodon-views") (autoload 'mastodon-return-credential-account "mastodon") (autoload 'mastodon-tl--buffer-property "mastodon-tl") -(autoload 'mastodon-search--query "mastodon-search") +(autoload 'mastodon-search-query "mastodon-search") (autoload 'mastodon-tl--field-status "mastodon-tl") (autoload 'mastodon-toot--with-toot-item "mastodon-toot" nil nil 'macro) (autoload 'mastodon-tl--toot-or-base "mastodon-tl") @@ -100,9 +100,9 @@ (defvar mastodon-profile-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") #'mastodon-profile--account-view-cycle) - (define-key map (kbd "C-c C-s") #'mastodon-profile--account-search) - (define-key map (kbd "C-c #") #'mastodon-profile--open-statuses-tagged) + (define-key map (kbd "C-c C-c") #'mastodon-profile-account-view-cycle) + (define-key map (kbd "C-c C-s") #'mastodon-profile-account-search) + (define-key map (kbd "C-c #") #'mastodon-profile-open-statuses-tagged) map) "Keymap for `mastodon-profile-mode'.") @@ -125,8 +125,8 @@ It contains details of the current user's account.") (defvar mastodon-profile-update-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") #'mastodon-profile--user-profile-send-updated) - (define-key map (kbd "C-c C-k") #'mastodon-profile--update-profile-note-cancel) + (define-key map (kbd "C-c C-c") #'mastodon-profile-user-profile-send-updated) + (define-key map (kbd "C-c C-k") #'mastodon-profile-update-profile-note-cancel) map) "Keymap for `mastodon-profile-update-mode'.") @@ -165,20 +165,21 @@ MAX-ID is a flag to include the max_id pagination parameter." ;;; PROFILE VIEW COMMANDS (defvar mastodon-profile--account-view-alist - '((statuses . mastodon-profile--open-statuses) - (no-boosts . mastodon-profile--open-statuses-no-reblogs) - (no-replies . mastodon-profile--open-statuses-no-replies) - (only-media . mastodon-profile--open-statuses-only-media) - (followers . mastodon-profile--open-followers) - (following . mastodon-profile--open-following) - (tag . mastodon-profile--open-statuses-tagged))) + '((statuses . mastodon-profile-open-statuses) + (no-boosts . mastodon-profile-open-statuses-no-reblogs) + (no-replies . mastodon-profile-open-statuses-no-replies) + (only-media . mastodon-profile-open-statuses-only-media) + (followers . mastodon-profile-open-followers) + (following . mastodon-profile-open-following) + (tag . mastodon-profile-open-statuses-tagged))) (defun mastodon-profile--view-types () "Return the keys of `mastodon-profile--account-view-alist' as a list." (map-keys mastodon-profile--account-view-alist)) -(defun mastodon-profile--account-view-cycle (&optional prefix) - "Cycle through profile view: toots, toot sans boosts, followers, and following." +(defun mastodon-profile-account-view-cycle (&optional prefix) + "Cycle through profile view: toots, toot sans boosts, followers, and following. +If a PREFIX argument is provided, prompt for a view type and load." (interactive "P") (if prefix (let* ((choice @@ -187,19 +188,19 @@ MAX-ID is a flag to include the max_id pagination parameter." (fun (alist-get choice mastodon-profile--account-view-alist))) (funcall fun)) (cond ((mastodon-tl--buffer-type-eq 'profile-statuses) - (mastodon-profile--open-statuses-no-reblogs)) + (mastodon-profile-open-statuses-no-reblogs)) ((mastodon-tl--buffer-type-eq 'profile-statuses-no-boosts) - (mastodon-profile--open-statuses-no-replies)) + (mastodon-profile-open-statuses-no-replies)) ((mastodon-tl--buffer-type-eq 'profile-statuses-no-replies) - (mastodon-profile--open-statuses-only-media)) + (mastodon-profile-open-statuses-only-media)) ((mastodon-tl--buffer-type-eq 'profile-statuses-only-media) - (mastodon-profile--open-followers)) + (mastodon-profile-open-followers)) ((mastodon-tl--buffer-type-eq 'profile-followers) - (mastodon-profile--open-following)) + (mastodon-profile-open-following)) ((mastodon-tl--buffer-type-eq 'profile-following) - (mastodon-profile--open-statuses))))) + (mastodon-profile-open-statuses))))) -(defun mastodon-profile--open-statuses () +(defun mastodon-profile-open-statuses () "Open a profile showing statuses." (interactive) (if mastodon-profile--account @@ -207,7 +208,7 @@ MAX-ID is a flag to include the max_id pagination parameter." mastodon-profile--account) (user-error "Not in a mastodon profile"))) -(defun mastodon-profile--open-statuses-no-replies () +(defun mastodon-profile-open-statuses-no-replies () "Open a profile buffer showing statuses without replies." (interactive) (if mastodon-profile--account @@ -215,7 +216,7 @@ MAX-ID is a flag to include the max_id pagination parameter." mastodon-profile--account nil :no-replies) (user-error "Not in a mastodon profile"))) -(defun mastodon-profile--open-statuses-no-reblogs () +(defun mastodon-profile-open-statuses-no-reblogs () "Open a profile buffer showing statuses without reblogs." (interactive) (if mastodon-profile--account @@ -223,7 +224,7 @@ MAX-ID is a flag to include the max_id pagination parameter." mastodon-profile--account :no-reblogs) (user-error "Not in a mastodon profile"))) -(defun mastodon-profile--open-statuses-only-media () +(defun mastodon-profile-open-statuses-only-media () "Open a profile buffer showing only statuses with media." (interactive) (if mastodon-profile--account @@ -231,7 +232,7 @@ MAX-ID is a flag to include the max_id pagination parameter." mastodon-profile--account nil nil :only-media) (user-error "Not in a mastodon profile"))) -(defun mastodon-profile--open-statuses-tagged () +(defun mastodon-profile-open-statuses-tagged () "Prompt for a hashtag and display a profile with only statuses containing it." (interactive) (let ((tag (read-string "Statuses containing tag: "))) @@ -240,7 +241,7 @@ MAX-ID is a flag to include the max_id pagination parameter." mastodon-profile--account nil nil nil tag) (user-error "Not in a mastodon profile")))) -(defun mastodon-profile--open-following () +(defun mastodon-profile-open-following () "Open a profile buffer showing the accounts that current profile follows." (interactive) (if mastodon-profile--account @@ -249,7 +250,7 @@ MAX-ID is a flag to include the max_id pagination parameter." #'mastodon-profile--format-user nil :headers) (user-error "Not in a mastodon profile"))) -(defun mastodon-profile--open-followers () +(defun mastodon-profile-open-followers () "Open a profile buffer showing the accounts following the current profile." (interactive) (if mastodon-profile--account @@ -258,34 +259,34 @@ MAX-ID is a flag to include the max_id pagination parameter." #'mastodon-profile--format-user nil :headers) (user-error "Not in a mastodon profile"))) -(defun mastodon-profile--view-favourites () +(defun mastodon-profile-view-favourites () "Open a new buffer displaying the user's favourites." (interactive) (message "Loading your favourited toots...") (mastodon-tl--init "favourites" "favourites" 'mastodon-tl--timeline :headers)) -(defun mastodon-profile--view-bookmarks () +(defun mastodon-profile-view-bookmarks () "Open a new buffer displaying the user's bookmarks." (interactive) (message "Loading your bookmarked toots...") (mastodon-tl--init "bookmarks" "bookmarks" 'mastodon-tl--timeline :headers)) -(defun mastodon-profile--add-account-to-list () +(defun mastodon-profile-add-account-to-list () "Add account of current profile buffer to a list." (interactive) (if (not mastodon-profile--account) (user-error "No profile to add?") (let-alist mastodon-profile--account - (mastodon-views--add-account-to-list nil .id .acct)))) + (mastodon-views-add-account-to-list nil .id .acct)))) -(defun mastodon-profile--account-search (query) +(defun mastodon-profile-account-search (query) "Run a statuses search QUERY for the currently viewed account." (interactive "sSearch account for: ") (let* ((ep (mastodon-tl--buffer-property 'endpoint)) (id (nth 1 (split-string ep "/")))) - (mastodon-search--query query "statuses" nil nil id))) + (mastodon-search-query query "statuses" nil nil id))) ;;; ACCOUNT PREFERENCES @@ -313,7 +314,7 @@ If value is :json-false, return nil." (mastodon-profile--get-account-value pref #'mastodon-profile--get-source-values)) -(defun mastodon-profile--update-user-profile-note () +(defun mastodon-profile-update-user-profile-note () "Fetch user's profile note and display for editing." (interactive) (let* ((source (mastodon-profile--get-source-values)) @@ -335,7 +336,7 @@ If value is :json-false, return nil." 'display nil) "/500 characters") 'read-only t - 'face 'font-lock-comment-face + 'face 'mastodon-toot-docs-face 'note-header t) "\n") (make-local-variable 'after-change-functions) @@ -359,7 +360,7 @@ If value is :json-false, return nil." (add-text-properties (car count-region) (cdr count-region) (list 'display count)))) -(defun mastodon-profile--update-profile-note-cancel () +(defun mastodon-profile-update-profile-note-cancel () "Cancel updating user profile and kill buffer and window." (interactive) (when (y-or-n-p "Cancel updating your profile note?") @@ -371,7 +372,7 @@ If value is :json-false, return nil." (point-min)))) (buffer-substring (cdr header-region) (point-max)))) -(defun mastodon-profile--user-profile-send-updated () +(defun mastodon-profile-user-profile-send-updated () "Send PATCH request with the updated profile note. Ask for confirmation if length > 500 characters." (interactive) @@ -443,24 +444,24 @@ If NO-FORCE, only fetch if `mastodon-profile-account-settings' is nil." mastodon-toot--content-nsfw (mastodon-profile--get-pref 'sensitive)) mastodon-profile-account-settings))) -(defun mastodon-profile--account-locked-toggle () +(defun mastodon-profile-account-locked-toggle () "Toggle the locked status of your account. Locked means follow requests have to be approved." (interactive) (mastodon-profile--toggle-account-key 'locked)) -(defun mastodon-profile--account-discoverable-toggle () +(defun mastodon-profile-account-discoverable-toggle () "Toggle the discoverable status of your account. Discoverable means the account is listed in the server directory." (interactive) (mastodon-profile--toggle-account-key 'discoverable)) -(defun mastodon-profile--account-bot-toggle () +(defun mastodon-profile-account-bot-toggle () "Toggle the bot status of your account." (interactive) (mastodon-profile--toggle-account-key 'bot)) -(defun mastodon-profile--account-sensitive-toggle () +(defun mastodon-profile-account-sensitive-toggle () "Toggle the sensitive status of your account. When enabled, statuses are marked as sensitive by default." (interactive) @@ -486,7 +487,7 @@ Current settings are fetched from the server." val))) (mastodon-profile--update-preference (symbol-name key) new-val))) -(defun mastodon-profile--update-display-name () +(defun mastodon-profile-update-display-name () "Update display name for your account." (interactive) (mastodon-profile--edit-string-value 'display_name)) @@ -504,7 +505,7 @@ Returns an alist." append (list (cons (car a-pair) (car b-pair)) (cons (cdr a-pair) (cdr b-pair)))))) -(defun mastodon-profile--update-meta-fields () +(defun mastodon-profile-update-meta-fields () "Prompt for new metadata fields information and PATCH the server." (interactive) (let* ((url (mastodon-http--api "accounts/update_credentials")) @@ -556,7 +557,7 @@ The endpoint only holds a few preferences. For others, see (mastodon-http--get-json (mastodon-http--api "preferences")))))) -(defun mastodon-profile--view-preferences () +(defun mastodon-profile-view-preferences () "View user preferences in another window." (interactive) (let* ((url (mastodon-http--api "preferences")) @@ -611,14 +612,14 @@ FIELDS means provide a fields vector fetched by other means." (defun mastodon-profile--insert-statuses-pinned (pinned-statuses) "Insert each of the PINNED-STATUSES for a given account." - (mapc (lambda (pinned-status) - (insert - (concat "\n " - (propertize " pinned " - 'face '(:inherit success :box t)) - " ")) - (mastodon-tl--toot pinned-status)) - pinned-statuses)) + (cl-loop for s in pinned-statuses + do (progn + (insert + (concat "\n " + (propertize " pinned " + 'face '(:inherit success :box t)) + " ")) + (mastodon-tl--toot s)))) (defun mastodon-profile--follows-p (list) "T if you have any relationship with the accounts in LIST." @@ -729,25 +730,27 @@ MAX-ID is a flag to include the max_id pagination parameter." ;; sharkey has no relationships endpoint, returns 500. ;; or poss it has a different endpoint "" - (let* ((followsp (mastodon-profile--follows-p - (list .requested_by .following .followed_by))) - (rels (mastodon-profile--relationships-get .id)) - (langs-filtered (if-let ((langs (alist-get 'languages rels))) - (concat " (" - (mapconcat #'identity langs " ") - ")") - ""))) - (if followsp - (mastodon-tl--set-face - (concat (when (eq .following t) - (format " | FOLLOWED BY YOU%s" langs-filtered)) - (when (eq .followed_by t) - " | FOLLOWS YOU") - (when (eq .requested_by t) - " | REQUESTED TO FOLLOW YOU") - "\n\n") - 'success) - ""))))) ; for insert call + (let* ((followsp (mastodon-profile--follows-p + (list .requested_by .following .followed_by .blocked_by))) + (rels (mastodon-profile--relationships-get .id)) + (langs-filtered (if-let* ((langs (alist-get 'languages rels))) + (concat " (" + (mapconcat #'identity langs " ") + ")") + ""))) + (if followsp + (mastodon-tl--set-face + (concat (when (eq .following t) + (format " | FOLLOWED BY YOU%s" langs-filtered)) + (when (eq .followed_by t) + " | FOLLOWS YOU") + (when (eq .requested_by t) + " | REQUESTED TO FOLLOW YOU") + (when (eq .blocked_by t) + " | BLOCKS YOU") + "\n\n") + 'success) + ""))))) ; for insert call (mastodon-media--inline-images (point-min) (point)) ;; widget items description (mastodon-widget--create @@ -767,14 +770,16 @@ MAX-ID is a flag to include the max_id pagination parameter." (let* ((inhibit-read-only t)) ;; insert pinned toots first (when (and pinned (string= endpoint-type "statuses")) - (mastodon-profile--insert-statuses-pinned pinned) - (setq mastodon-tl--update-point (point))) ; updates after pinned toots + (let ((beg (point))) + (mastodon-profile--insert-statuses-pinned pinned) + (setq mastodon-tl--update-point (point)) + (mastodon-media--inline-images beg (point)))) ; updates after pinned toots ;; insert items (funcall update-function json) (goto-char (point-min)) (message (substitute-command-keys - ;; "\\[mastodon-profile--account-view-cycle]" ; not always bound? + ;; "\\[mastodon-profile-account-view-cycle]" ; not always bound? "\\`C-c C-c' to cycle profile views: toots, no replies, no boosts,\ only media, followers, following. \\`C-c C-s' to search user's toots, \\`C-c \#' to search user's posts for a hashtag."))))))) @@ -797,7 +802,7 @@ the format \"2000-01-31T00:00:00.000Z\"." (format-time-string "Joined: %d %B %Y" (parse-iso8601-time-string joined))) -(defun mastodon-profile--get-toot-author (&optional max-id) +(defun mastodon-profile-get-toot-author (&optional max-id) "Open profile of author of toot under point. If toot is a boost, load the profile of the author of the original item. MAX-ID is a flag to include the max_id pagination parameter." @@ -816,7 +821,7 @@ IMG-TYPE is the JSON key from the account data." (unless (string= img "/avatars/original/missing.png") (mastodon-media--get-media-link-rendering img)))) -(defun mastodon-profile--show-user (user-handle) +(defun mastodon-profile-show-user (user-handle) "Query for USER-HANDLE from current status and show that user's profile." (interactive (list @@ -842,7 +847,7 @@ IMG-TYPE is the JSON key from the account data." (message "Loading profile of user %s..." user-handle) (mastodon-profile--make-author-buffer account)))))) -(defun mastodon-profile--my-profile () +(defun mastodon-profile-my-profile () "Show the profile of the currently signed in user." (interactive) (message "Loading your profile...") @@ -935,7 +940,7 @@ These include the author, author of reblogged entries and any user mentioned." ;;; REMOVE -(defun mastodon-profile--remove-user-from-followers (&optional id) +(defun mastodon-profile-remove-user-from-followers (&optional id) "Remove a user from your followers. Optionally provide the ID of the account to remove." (interactive) @@ -952,7 +957,7 @@ Optionally provide the ID of the account to remove." (lambda (_) (message "Follower %s removed!" handle))))))) -(defun mastodon-profile--remove-from-followers-at-point () +(defun mastodon-profile-remove-from-followers-at-point () "Prompt for a user in the item at point and remove from followers." (interactive) (let* ((handles (mastodon-profile--extract-users-handles @@ -961,12 +966,12 @@ Optionally provide the ID of the account to remove." (account (mastodon-profile--lookup-account-in-status handle (mastodon-profile--item-json))) (id (alist-get 'id account))) - (mastodon-profile--remove-user-from-followers id))) + (mastodon-profile-remove-user-from-followers id))) -(defun mastodon-profile--remove-from-followers-list () +(defun mastodon-profile-remove-from-followers-list () "Select a user from your followers and remove from followers. Currently limited to 100 handles. If not found, try -`mastodon-search--query'." +`mastodon-search-query'." (interactive) (let* ((endpoint (format "accounts/%s/followers" (mastodon-auth--get-account-id))) @@ -975,11 +980,11 @@ Currently limited to 100 handles. If not found, try (handles (mastodon-tl--map-alist-vals-to-alist 'acct 'id response)) (choice (completing-read "Remove from followers: " handles)) (id (alist-get choice handles))) - (mastodon-profile--remove-user-from-followers id))) + (mastodon-profile-remove-user-from-followers id))) ;;; PRIVATE NOTES -(defun mastodon-profile--add-private-note-to-account () +(defun mastodon-profile-add-private-note-to-account () "Add a private note to an account. Can be called from a profile page or normal timeline. Send an empty note to clear an existing one." @@ -1001,7 +1006,7 @@ NOTE-OLD is the text of any existing note." (lambda (_) (message "Private note on %s added!" handle))))) -(defun mastodon-profile--view-account-private-note () +(defun mastodon-profile-view-account-private-note () "Display the private note about a user." (interactive) (mastodon-profile--add-or-view-private-note @@ -1049,7 +1054,7 @@ ACTION-FUN does the adding or viewing, MESSAGE is a prompt for ;;; FAMILIAR FOLLOWERS -(defun mastodon-profile--show-familiar-followers () +(defun mastodon-profile-show-familiar-followers () "Show a list of familiar followers. Familiar followers are accounts that you follow, and that follow the given account." @@ -1076,7 +1081,7 @@ the given account." (if (null handles) (user-error "Looks like there are no familiar followers for this account") (let ((choice (completing-read "Show profile of user: " handles))) - (mastodon-profile--show-user choice))))) + (mastodon-profile-show-user choice))))) (provide 'mastodon-profile) ;;; mastodon-profile.el ends here diff --git a/elpa/mastodon-20241223.1040/mastodon-search.el b/elpa/mastodon-20250330.1519/mastodon-search.el similarity index 87% rename from elpa/mastodon-20241223.1040/mastodon-search.el rename to elpa/mastodon-20250330.1519/mastodon-search.el index 5f1e9809..dda71765 100644 --- a/elpa/mastodon-20241223.1040/mastodon-search.el +++ b/elpa/mastodon-20250330.1519/mastodon-search.el @@ -86,19 +86,19 @@ QUERY is the string to search." ;; trending tags -(defun mastodon-search--trending-tags () +(defun mastodon-search-trending-tags () "Display a list of tags trending on your instance." (interactive) (mastodon-search--view-trending "tags" #'mastodon-search--print-tags)) -(defun mastodon-search--trending-statuses () +(defun mastodon-search-trending-statuses () "Display a list of statuses trending on your instance." (interactive) (mastodon-search--view-trending "statuses" #'mastodon-tl--timeline)) -(defun mastodon-search--trending-links () +(defun mastodon-search-trending-links () "Display a list of links trending on your instance." (interactive) (mastodon-search--view-trending "links" @@ -123,11 +123,11 @@ QUERY is the string to search." 'byline t ;; nav 'help-echo (substitute-command-keys - "\\[`mastodon-search--load-link-posts'] to view a link's timeline")) + "\\[`mastodon-search-load-link-posts'] to view a link's timeline")) ;; TODO: display card link author here "\n\n"))) -(defun mastodon-search--load-link-posts () +(defun mastodon-search-load-link-posts () "Load timeline of posts containing link at point." (interactive) (let* ((url (mastodon-tl--property 'shr-url))) @@ -178,7 +178,7 @@ NO-NEWLINE means don't add add a newline at end." (defvar mastodon-search-types '("statuses" "accounts" "hashtags")) -(defun mastodon-search--query (query &optional type limit +(defun mastodon-search-query (query &optional type limit following account-id offset) "Prompt for a search QUERY and return accounts, statuses, and hashtags. TYPE is a member of `mastodon-search-types'. @@ -201,13 +201,12 @@ is used for pagination." (limit (or limit "40")) (offset (or offset "0")) (buffer (format "*mastodon-search-%s-%s*" type query)) - (params (cl-remove nil - `(("q" . ,query) - ,(when type `("type" . ,type)) - ,(when limit `("limit" . ,limit)) - ,(when offset `("offset" . ,offset)) - ,(when following `("following" . ,following)) - ,(when account-id `("account_id" . ,account-id))))) + (params `(("q" . ,query) + ,@(when type `(("type" . ,type))) + ,@(when limit `(("limit" . ,limit))) + ,@(when offset `(("offset" . ,offset))) + ,@(when following `(("following" . ,following))) + ,@(when account-id `(("account_id" . ,account-id))))) (response (mastodon-http--get-json url params)) (items (alist-get (intern type) response))) (with-mastodon-buffer buffer #'mastodon-mode nil @@ -219,7 +218,7 @@ is used for pagination." (intern type) (lambda (widget &rest _ignore) (let ((value (widget-value widget))) - (mastodon-search--query query (symbol-name value))))) + (mastodon-search-query query (symbol-name value))))) (insert "\n\n") (cond ((string= type "accounts") (mastodon-search--render-response items type buffer params @@ -236,14 +235,14 @@ is used for pagination." (goto-char (point-min)) (message (substitute-command-keys - "\\[mastodon-search--query-cycle] to cycle result types."))))) + "\\[mastodon-search-query-cycle] to cycle result types."))))) (defun mastodon-search-insert-no-results (&optional thing) "Insert a no results message for object THING." (let ((thing (or thing "items"))) (insert (propertize (format "Looks like search returned no %s." thing) - 'face 'font-lock-comment-face)))) + 'face 'mastodon-toot-docs-face)))) (defun mastodon-search--render-response (data type buffer params insert-fun update-fun) @@ -261,23 +260,23 @@ BUFFER, PARAMS, and UPDATE-FUN are for `mastodon-tl--buffer-spec'." (let* ((spec (mastodon-tl--buffer-property 'update-params))) (alist-get "type" spec nil nil #'string=))) -(defun mastodon-search--query-cycle () +(defun mastodon-search-query-cycle () "Cycle through search types: accounts, hashtags, and statuses." (interactive) (let* ((spec (mastodon-tl--buffer-property 'update-params)) (type (alist-get "type" spec nil nil #'string=)) (query (alist-get "q" spec nil nil #'string=))) (cond ((string= type "hashtags") - (mastodon-search--query query "accounts")) + (mastodon-search-query query "accounts")) ((string= type "accounts") - (mastodon-search--query query "statuses")) + (mastodon-search-query query "statuses")) ((string= type "statuses") - (mastodon-search--query query "hashtags"))))) + (mastodon-search-query query "hashtags"))))) -(defun mastodon-search--query-accounts-followed (query) +(defun mastodon-search-query-accounts-followed (query) "Run an accounts search QUERY, limited to your followers." (interactive "sSearch mastodon for: ") - (mastodon-search--query query "accounts" :following)) + (mastodon-search-query query "accounts" :following)) (defun mastodon-search--insert-users-propertized (json &optional note) "Insert users list into the buffer. @@ -285,11 +284,10 @@ JSON is the data from the server. If NOTE is non-nil, include user's profile note. This is also called by `mastodon-tl--get-follow-suggestions' and `mastodon-profile--insert-follow-requests'." - (mapc (lambda (acct) - (insert (concat (mastodon-search--propertize-user acct note) - mastodon-tl--horiz-bar - "\n\n"))) - json)) + (cl-loop for acct in json + do (insert (concat (mastodon-search--propertize-user acct note) + mastodon-tl--horiz-bar + "\n\n")))) (defun mastodon-search--propertize-user (acct &optional note) "Propertize display string for ACCT, optionally including profile NOTE." @@ -323,20 +321,19 @@ If NOTE is non-nil, include user's profile note. This is also (defun mastodon-search--print-tags-list (tags-list) "Insert a propertized list of TAGS-LIST." - (mapc (lambda (el) - (insert - " : " - (propertize (concat "#" (car el)) - 'face '(:box t) - 'mouse-face 'highlight - 'mastodon-tag (car el) - 'mastodon-tab-stop 'hashtag - 'item-type 'tag ; for next/prev nav - 'byline t ; for next/prev nav - 'help-echo (concat "Browse tag #" (car el)) - 'keymap mastodon-tl--link-keymap) - " : \n\n")) - tags-list)) + (cl-loop for el in tags-list + do (insert + " : " + (propertize (concat "#" (car el)) + 'face '(:box t) + 'mouse-face 'highlight + 'mastodon-tag (car el) + 'mastodon-tab-stop 'hashtag + 'item-type 'tag ; for next/prev nav + 'byline t ; for next/prev nav + 'help-echo (concat "Browse tag #" (car el)) + 'keymap mastodon-tl--link-keymap) + " : \n\n"))) (defun mastodon-search--get-user-info (account) "Get user handle, display name, account URL and profile note from ACCOUNT." @@ -376,7 +373,7 @@ If NOTE is non-nil, include user's profile note. This is also (defvar mastodon-search-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") #'mastodon-search--query-cycle) + (define-key map (kbd "C-c C-c") #'mastodon-search-query-cycle) map) "Keymap for `mastodon-search-mode'.") diff --git a/elpa/mastodon-20241223.1040/mastodon-tl.el b/elpa/mastodon-20250330.1519/mastodon-tl.el similarity index 87% rename from elpa/mastodon-20241223.1040/mastodon-tl.el rename to elpa/mastodon-20250330.1519/mastodon-tl.el index 781c2736..508332b0 100644 --- a/elpa/mastodon-20241223.1040/mastodon-tl.el +++ b/elpa/mastodon-20250330.1519/mastodon-tl.el @@ -58,15 +58,15 @@ (autoload 'mastodon-media--get-media-link-rendering "mastodon-media") (autoload 'mastodon-media--inline-images "mastodon-media") (autoload 'mastodon-notifications--filter-types-list "mastodon-notifications") -(autoload 'mastodon-notifications--get-mentions "mastodon-notifications") +(autoload 'mastodon-notifications-get-mentions "mastodon-notifications") (autoload 'mastodon-profile--account-from-id "mastodon-profile") (autoload 'mastodon-profile--extract-users-handles "mastodon-profile") (autoload 'mastodon-profile--get-preferences-pref "mastodon-profile") -(autoload 'mastodon-profile--get-toot-author "mastodon-profile") +(autoload 'mastodon-profile-get-toot-author "mastodon-profile") (autoload 'mastodon-profile--lookup-account-in-status "mastodon-profile") (autoload 'mastodon-profile--make-author-buffer "mastodon-profile") -(autoload 'mastodon-profile--my-profile "mastodon-profile") -(autoload 'mastodon-profile--open-statuses-no-reblogs "mastodon-profile") +(autoload 'mastodon-profile-my-profile "mastodon-profile") +(autoload 'mastodon-profile-open-statuses-no-reblogs "mastodon-profile") (autoload 'mastodon-profile--profile-json "mastodon-profile") (autoload 'mastodon-profile--search-account-by-handle "mastodon-profile") (autoload 'mastodon-profile--item-json "mastodon-profile") @@ -76,10 +76,10 @@ (autoload 'mastodon-search--insert-users-propertized "mastodon-search") (autoload 'mastodon-search--propertize-user "mastodon-search") (autoload 'mastodon-toot--compose-buffer "mastodon-toot") -(autoload 'mastodon-toot--delete-toot "mastodon-toot") +(autoload 'mastodon-toot-delete-toot "mastodon-toot") (autoload 'mastodon-toot--get-toot-edits "mastodon-toot") (autoload 'mastodon-toot--iso-to-human "mastodon-toot") -(autoload 'mastodon-toot--schedule-toot "mastodon-toot") +(autoload 'mastodon-toot-schedule-toot "mastodon-toot") (autoload 'mastodon-toot--set-toot-properties "mastodon-toot") (autoload 'mastodon-toot--update-status-fields "mastodon-toot") (autoload 'mastodon-search--buf-type "mastodon-search") @@ -87,14 +87,14 @@ (autoload 'mastodon-http--get-response "mastodon-http") (autoload 'mastodon-search--insert-heading "mastodon-search") (autoload 'mastodon-media--process-full-sized-image-response "mastodon-media") -(autoload 'mastodon-search--trending-statuses "mastodon-search") +(autoload 'mastodon-search-trending-statuses "mastodon-search") (autoload 'mastodon-search--format-heading "mastodon-search") (autoload 'mastodon-media--image-or-cached "mastodon-media") (autoload 'mastodon-toot--base-toot-or-item-json "mastodon-toot") -(autoload 'mastodon-search--load-link-posts "mastodon-search") +(autoload 'mastodon-search-load-link-posts "mastodon-search") (autoload 'mastodon-notifications--current-type "mastodon-notifications") (autoload 'mastodon-notifications--timeline "mastodon-notifications") - +(autoload 'mastodon-notifications--empty-group-json-p "mastodon-notifications") (defvar mastodon-toot--visibility) (defvar mastodon-toot-mode) (defvar mastodon-active-user) @@ -110,6 +110,7 @@ (defvar mastodon-media--enable-image-caching) (defvar mastodon-media--generic-broken-image-data) (defvar mastodon-media--sensitive-image-data) +(defvar mastodon-media--attachments) ;;; CUSTOMIZES @@ -171,7 +172,7 @@ nil." (status . ("✍" . "[posted]")) (replied . ("⬇" . "↓")) (reply-bar . ("┃" . "|")) - (poll . ("📊" . "")) + (poll . ("📊" . "[poll]")) (follow . ("👤" . "+")) (follow_request . ("👤" . "+")) (severed_relationships . ("🔗" . "//")) @@ -219,7 +220,7 @@ re-load mastodon.el, or restart Emacs." (const :tag "follow server setting" server))) (defcustom mastodon-tl--tag-timeline-tags nil - "A list of up to four tags for use with `mastodon-tl--followed-tags-timeline'." + "A list of up to four tags for use with `mastodon-tl-followed-tags-timeline'." :type '(repeat string)) (defcustom mastodon-tl--load-full-sized-images-in-emacs t @@ -232,7 +233,7 @@ respects the user's `browse-url' settings." (defcustom mastodon-tl--remote-local-domains nil "A list of domains to view the local timelines of. -See `mastodon-tl--get-remote-local-timeline' for view remote local domains." +See `mastodon-tl-get-remote-local-timeline' for view remote local domains." :type '(repeat string)) (defcustom mastodon-tl--fold-toots-at-length 1200 @@ -269,8 +270,8 @@ If nil `(point-min)' is used instead.") (defvar mastodon-tl--link-keymap (let ((map (make-sparse-keymap))) - (define-key map [return] #'mastodon-tl--do-link-action-at-point) - (define-key map [mouse-2] #'mastodon-tl--do-link-action) + (define-key map [return] #'mastodon-tl-do-link-action-at-point) + (define-key map [mouse-2] #'mastodon-tl-do-link-action) (define-key map [follow-link] 'mouse-face) map) "The keymap for link-like things in buffer (except for shr.el generate links). @@ -283,15 +284,15 @@ etc.") (set-keymap-parent map shr-map) ;; Replace the move to next/previous link bindings with our ;; version that knows about more types of links. - (define-key map [remap shr-next-link] #'mastodon-tl--next-tab-item) - (define-key map [remap shr-previous-link] #'mastodon-tl--previous-tab-item) + (define-key map [remap shr-next-link] #'mastodon-tl-next-tab-item) + (define-key map [remap shr-previous-link] #'mastodon-tl-previous-tab-item) ;; keep new my-profile binding; shr 'O' doesn't work here anyway - (define-key map (kbd "O") #'mastodon-profile--my-profile) + (define-key map (kbd "O") #'mastodon-profile-my-profile) ;; remove shr's u binding, as it the maybe-probe-and-copy-url ;; is already bound to w also - (define-key map (kbd "u") #'mastodon-tl--update) + (define-key map (kbd "u") #'mastodon-tl-update) (define-key map [remap shr-browse-url] #'mastodon-url-lookup) - (define-key map (kbd "M-RET") #'mastodon-search--load-link-posts) + (define-key map (kbd "M-RET") #'mastodon-search-load-link-posts) map) "The keymap to be set for shr.el generated links that are not images. We need to override the keymap so tabbing will navigate to all @@ -304,20 +305,20 @@ types of mastodon links and not just shr.el-generated ones.") shr-map)) ;; Replace the move to next/previous link bindings with our ;; version that knows about more types of links. - (define-key map [remap shr-next-link] #'mastodon-tl--next-tab-item) - (define-key map [remap shr-previous-link] #'mastodon-tl--previous-tab-item) + (define-key map [remap shr-next-link] #'mastodon-tl-next-tab-item) + (define-key map [remap shr-previous-link] #'mastodon-tl-previous-tab-item) ;; browse-url loads the preview only, we want browse-image ;; on RET to browse full sized image URL - (define-key map [remap shr-browse-url] #'mastodon-tl--view-full-image-or-play-video) + (define-key map [remap shr-browse-url] #'mastodon-tl-view-full-image-or-play-video) ;; remove shr's u binding, as it the maybe-probe-and-copy-url ;; is already bound to w also - (define-key map (kbd "u") #'mastodon-tl--update) + (define-key map (kbd "u") #'mastodon-tl-update) ;; keep new my-profile binding; shr 'O' doesn't work here anyway - (define-key map (kbd "O") #'mastodon-profile--my-profile) - (define-key map (kbd "C") #'mastodon-tl--copy-image-caption) - (define-key map (kbd "S") #'mastodon-tl--toggle-sensitive-image) - (define-key map (kbd "") #'mastodon-tl--mpv-play-video-at-point) - (define-key map (kbd "") #'mastodon-tl--click-image-or-video) + (define-key map (kbd "O") #'mastodon-profile-my-profile) + (define-key map (kbd "C") #'mastodon-tl-copy-image-caption) + (define-key map (kbd "S") #'mastodon-tl-toggle-sensitive-image) + (define-key map (kbd "") #'mastodon-tl-mpv-play-video-at-point) + (define-key map (kbd "") #'mastodon-tl-click-image-or-video) map) "The keymap to be set for shr.el generated image links. We need to override the keymap so tabbing will navigate to all @@ -326,12 +327,28 @@ types of mastodon links and not just shr.el-generated ones.") (defvar mastodon-tl--byline-link-keymap (when (require 'mpv nil :no-error) (let ((map (make-sparse-keymap))) - (define-key map (kbd "") #'mastodon-tl--mpv-play-video-from-byline) - (define-key map (kbd "RET") #'mastodon-profile--get-toot-author) - (define-key map (kbd "S") #'mastodon-tl--toggle-sensitive-image) + (define-key map (kbd "") #'mastodon-tl-mpv-play-video-from-byline) + (define-key map (kbd "RET") #'mastodon-profile-get-toot-author) + (define-key map (kbd "S") #'mastodon-tl-toggle-sensitive-image) map)) "The keymap to be set for the author byline. -It is active where point is placed by `mastodon-tl--goto-next-item.'") +It is active where point is placed by `mastodon-tl-goto-next-item.'") + +(require 'image-mode) + +(defvar mastodon-image-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map image-mode-map) + (define-key map (kbd ">") #'mastodon-tl-next-full-image) + (define-key map (kbd "<") #'mastodon-tl-prev-full-image) + (define-key map (kbd ".") #'mastodon-tl-next-full-image) + (define-key map (kbd ",") #'mastodon-tl-prev-full-image) + ;; matches view full image binding in main keymap: + (define-key map (kbd "=") #'mastodon-tl-next-full-image) + (define-key map (kbd "-") #'mastodon-tl-prev-full-image) + (define-key map (kbd "") #'mastodon-tl-next-full-image) + (define-key map (kbd "") #'mastodon-tl-prev-full-image) + map)) ;;; MACROS @@ -365,7 +382,7 @@ than `pop-to-buffer'." ;;; NAV -(defun mastodon-tl--scroll-up-command () +(defun mastodon-tl-scroll-up-command () "Call `scroll-up-command', loading more toots if necessary. If we hit `point-max', call `mastodon-tl--more' then `scroll-up-command'." (interactive) @@ -374,7 +391,7 @@ If we hit `point-max', call `mastodon-tl--more' then `scroll-up-command'." (mastodon-tl--more) (scroll-up-command))) -(defun mastodon-tl--next-tab-item (&optional previous) +(defun mastodon-tl-next-tab-item (&optional previous) "Move to the next interesting item. This could be the next toot, link, or image; whichever comes first. Don't move if nothing to move to is found, i.e. near the end of the buffer. @@ -397,14 +414,14 @@ PREVIOUS means move to previous item." (goto-char (car next-range)) (message "%s" (mastodon-tl--property 'help-echo :no-move))))) -(defun mastodon-tl--previous-tab-item () +(defun mastodon-tl-previous-tab-item () "Move to the previous interesting item. This could be the previous toot, link, or image; whichever comes first. Don't move if nothing else to move to is found, i.e. near the start of the buffer. This also skips tab items in invisible text, i.e. hidden spoiler text." (interactive) - (mastodon-tl--next-tab-item :previous)) + (mastodon-tl-next-tab-item :previous)) (defun mastodon-tl--goto-item-pos (find-pos refresh &optional pos) "Search for item with function FIND-POS. @@ -430,7 +447,7 @@ Optionally start from POS." (funcall refresh) (error "No more items"))))) -(defun mastodon-tl--goto-next-item (&optional no-refresh) +(defun mastodon-tl-goto-next-item (&optional no-refresh) "Jump to next item. Load more items it no next item. NO-REFRESH means do no not try to load more items if no next item @@ -441,7 +458,7 @@ found." (unless no-refresh 'mastodon-tl--more)) (t (error "No more items")))) -(defun mastodon-tl--goto-prev-item (&optional no-refresh) +(defun mastodon-tl-goto-prev-item (&optional no-refresh) "Jump to previous item. Update if no previous items. NO-REFRESH means do no not try to load more items if no next item @@ -449,7 +466,7 @@ found." (interactive) (condition-case nil (mastodon-tl--goto-item-pos 'previous-single-property-change - (unless no-refresh 'mastodon-tl--update)) + (unless no-refresh 'mastodon-tl-update)) (t (error "No more items")))) (defun mastodon-tl--goto-first-item () @@ -464,29 +481,27 @@ Used on initializing a timeline or thread." ;;; TIMELINES -(defun mastodon-tl--get-federated-timeline (&optional prefix local max-id) +(defun mastodon-tl-get-federated-timeline (&optional prefix local max-id) "Open federated timeline. If LOCAL, get only local timeline. With a single PREFIX arg, hide-replies. With a double PREFIX arg, only show posts with media." (interactive "p") (let ((params - (cl-remove - nil - `(("limit" . ,mastodon-tl--timeline-posts-count) - ,(when (eq prefix 16) - '("only_media" . "true")) - ,(when local - '("local" . "true")) - ,(when max-id - `("max_id" . ,(mastodon-tl--buffer-property 'max-id))))))) + `(("limit" . ,mastodon-tl--timeline-posts-count) + ,@(when (eq prefix 16) + '(("only_media" . "true"))) + ,@(when local + '(("local" . "true"))) + ,@(when max-id + `(("max_id" . ,(mastodon-tl--buffer-property 'max-id))))))) (message "Loading federated timeline...") (mastodon-tl--init (if local "local" "federated") "timelines/public" 'mastodon-tl--timeline nil params (when (eq prefix 4) t)))) -(defun mastodon-tl--get-home-timeline (&optional arg max-id) +(defun mastodon-tl-get-home-timeline (&optional arg max-id) "Open home timeline. With a single prefix ARG, hide replies. MAX-ID is a flag to add the max_id pagination parameter." @@ -500,7 +515,7 @@ MAX-ID is a flag to add the max_id pagination parameter." params (when (eq arg 4) t)))) -(defun mastodon-tl--get-remote-local-timeline (&optional endpoint) +(defun mastodon-tl-get-remote-local-timeline (&optional endpoint) "Prompt for an instance domain and try to display its local timeline. You can enter any working instance domain. Domains that you want to regularly load can be stored in @@ -509,7 +524,7 @@ Note that some instances do not make their local timelines public, in which case this will not work. To interact with any item, you must view it from your own instance, which you can do with -`mastodon-tl--view-item-on-own-instance'. +`mastodon-tl-view-item-on-own-instance'. Optionally, provide API ENDPOINT." (interactive) (let* ((domain (completing-read "Domain for remote local tl: " @@ -530,14 +545,14 @@ Optionally, provide API ENDPOINT." 'mastodon-tl--timeline nil params nil domain)))) -(defun mastodon-tl--remote-tag-timeline (&optional tag) - "Call `mastodon-tl--get-remote-local-timeline' but for a TAG timeline." +(defun mastodon-tl-remote-tag-timeline (&optional tag) + "Call `mastodon-tl-get-remote-local-timeline' but for a TAG timeline." (interactive) (let* ((tag (or tag (read-string "Tag: "))) (endpoint (format "timelines/tag/%s" tag))) - (mastodon-tl--get-remote-local-timeline endpoint))) + (mastodon-tl-get-remote-local-timeline endpoint))) -(defun mastodon-tl--view-item-on-own-instance () +(defun mastodon-tl-view-item-on-own-instance () "Load current toot on your own instance. Use this to re-load remote-local items in order to interact with them." (interactive) @@ -546,16 +561,16 @@ Use this to re-load remote-local items in order to interact with them." (uri (mastodon-tl--field 'uri toot))) (mastodon-url-lookup uri)))) -(defun mastodon-tl--get-local-timeline (&optional prefix max-id) +(defun mastodon-tl-get-local-timeline (&optional prefix max-id) "Open local timeline. With a single PREFIX arg, hide-replies. With a double PREFIX arg, only show posts with media. MAX-ID is a flag to add the max_id pagination parameter." (interactive "p") (message "Loading local timeline...") - (mastodon-tl--get-federated-timeline prefix :local max-id)) + (mastodon-tl-get-federated-timeline prefix :local max-id)) -(defun mastodon-tl--get-tag-timeline (&optional prefix tag) +(defun mastodon-tl-get-tag-timeline (&optional prefix tag) "Prompt for tag and opens its timeline. Optionally load TAG timeline directly. With a single PREFIX arg, only show posts with media. @@ -574,13 +589,11 @@ If TAG is a list, show a timeline for all tags. With a single PREFIX arg, only show posts with media. With a double PREFIX arg, limit results to your own instance." (let ((params - (cl-remove - nil - `(("limit" . ,mastodon-tl--timeline-posts-count) - ,(when (eq prefix 4) - '("only_media" . "true")) - ,(when (eq prefix 16) - '("local" . "true")))))) + `(("limit" . ,mastodon-tl--timeline-posts-count) + ,@(when (eq prefix 4) + '(("only_media" . "true"))) + ,@(when (eq prefix 16) + '(("local" . "true")))))) (when (listp tag) (let ((list (mastodon-http--build-array-params-alist "any[]" (cdr tag)))) (while list @@ -597,7 +610,7 @@ With a double PREFIX arg, limit results to your own instance." 'mastodon-tl--timeline nil params))) -(defun mastodon-tl--announcements () +(defun mastodon-tl-announcements () "Display announcements from your instance." (interactive) (mastodon-tl--init "announcements" "announcements" @@ -717,7 +730,7 @@ ACCOUNT is optionally acccount data to use." Displays a toot's media types and optionally the binding to play moving image media from the byline. Used when point is at the start of a byline, i.e. where -`mastodon-tl--goto-next-item' leaves point." +`mastodon-tl-goto-next-item' leaves point." (let* ((toot-to-count (or ; simply praying this order works (alist-get 'status toot) ; notifications timeline @@ -754,8 +767,10 @@ The result is added as an attachments property to author-byline." (let ((media (mastodon-tl--field 'media_attachments toot))) (mapcar (lambda (attachment) (let-alist attachment - (list :url (or .remote_url .url) ; fallback for notifications - :type .type))) + (list :id .id + :type .type + ;; fallback for notifications: + :url (or .remote_url .url)))) media))) (defun mastodon-tl--byline-booster (toot) @@ -839,7 +854,7 @@ TS is a timestamp from the server, if any." (concat ;; Boosted/favourited markers are not technically part of the byline, so ;; we don't propertize them with 'byline t', as per the rest. This - ;; ensures that `mastodon-tl--goto-next-item' puts point on + ;; ensures that `mastodon-tl-goto-next-item' puts point on ;; author-byline, not before the (F) or (B) marker. Not propertizing like ;; this makes the behaviour of these markers consistent whether they are ;; displayed for an already boosted/favourited toot or as the result of @@ -854,7 +869,7 @@ TS is a timestamp from the server, if any." (mastodon-tl--format-faved-or-boosted-byline (mastodon-tl--symbol 'bookmark)))) ;; we remove avatars from the byline also, so that they also do not - ;; mess with `mastodon-tl--goto-next-item': + ;; mess with `mastodon-tl-goto-next-item': (when (and mastodon-tl--show-avatars mastodon-tl--display-media-p (mastodon-tl--image-trans-check)) @@ -897,10 +912,10 @@ TS is a timestamp from the server, if any." 'face 'mastodon-display-name-face 'follow-link t 'mouse-face 'highlight - 'mastodon-tab-stop 'shr-url - 'shr-url app-url + 'mastodon-tab-stop 'shr-url + 'shr-url app-url 'help-echo app-url - 'keymap mastodon-tl--shr-map-replacement))))) + 'keymap mastodon-tl--shr-map-replacement))))) ;; edited: (when edited-time (concat @@ -910,7 +925,7 @@ TS is a timestamp from the server, if any." (propertize (format-time-string mastodon-toot-timestamp-format edited-parsed) - 'face 'font-lock-comment-face + 'face 'mastodon-toot-docs-face 'timestamp edited-parsed 'display (if mastodon-tl--enable-relative-timestamps (mastodon-tl--relative-time-description edited-parsed) @@ -1014,7 +1029,7 @@ links in the text. If TOOT is nil no parsing occurs." (defun mastodon-tl--format-card-author (data) "Render card author DATA." - (when-let ((account (alist-get 'account data))) ;.account + (when-let* ((account (alist-get 'account data))) ;.account (let-alist account ;.account ;; FIXME: replace with refactored handle render fun ;; in byline refactor branch: @@ -1027,10 +1042,10 @@ links in the text. If TOOT is nil no parsing occurs." (propertize (concat "@" .acct) 'face 'mastodon-handle-face 'mouse-face 'highlight - 'mastodon-tab-stop 'user-handle - 'keymap mastodon-tl--link-keymap + 'mastodon-tab-stop 'user-handle + 'keymap mastodon-tl--link-keymap 'mastodon-handle (concat "@" .acct) - 'help-echo (concat "Browse user profile of @" .acct)))))) + 'help-echo (concat "Browse user profile of @" .acct)))))) (defun mastodon-tl--process-link (toot start end url) "Process link URL in TOOT as hashtag, userhandle, or normal link. @@ -1108,24 +1123,30 @@ Return nil if no matching element." (setq mention (pop mentions))) return)))) -(defun mastodon-tl--userhandle-from-url (url buffer-text) +(defun mastodon-tl--userhandle-from-url (url &optional buffer-text) "Return the user hande the URL points to or nil if it is not a profile link. BUFFER-TEXT is the text covered by the link with URL, for a user profile -this should be of the form , e.g. \"@Gargon\"." +this should be of the form , e.g. \"@Gargon\". +This is called on all post URLs, so needs to handle non profile URLs +gracefully." (let* ((parsed-url (url-generic-parse-url url)) (host (url-host parsed-url)) (local-p (string= (url-host (url-generic-parse-url mastodon-instance-url)) host)) - (path (url-filename parsed-url))) - (when (and (string= "@" (substring buffer-text 0 1)) - ;; don't error on domain only url (rare): - (not (string= "" path)) - (string= (downcase buffer-text) - (downcase (substring path 1)))) - (if local-p - buffer-text ; no instance suffix for local mention - (concat buffer-text "@" host))))) + (path-raw (url-filename parsed-url))) + (unless (string-empty-p path-raw) + (let ((path (substring path-raw 1))) ;; remove "/" prefix + (if (not buffer-text) + (when (string-prefix-p "@" path) + (if local-p path (concat "@" host))) + (when (and (string= "@" (substring buffer-text 0 1)) + ;; don't error on domain only url (rare): + (string= (downcase buffer-text) + (downcase path))) + (if local-p + buffer-text ; no instance suffix for local mention + (concat buffer-text "@" host)))))))) (defun mastodon-tl--hashtag-from-url (url instance-url) "Return the hashtag that URL points to or nil if URL is not a tag link. @@ -1153,7 +1174,7 @@ the toot)." (defun mastodon-tl--push-button (button) "Do the appropriate action for BUTTON." - (mastodon-tl--do-link-action-at-point (button-start button))) + (mastodon-tl-do-link-action-at-point (button-start button))) (defun mastodon-tl--buttonify-link (string &rest properties) "Make STRING a `mastodon-tl-link' type button. @@ -1177,7 +1198,7 @@ LINK-TYPE is the type of link to produce." 'mastodon-tab-stop link-type 'help-echo help-text))) -(defun mastodon-tl--do-link-action-at-point (pos) +(defun mastodon-tl-do-link-action-at-point (pos) "Do the action of the link at POS. Used for hitting RET on a given link." (interactive "d") @@ -1212,17 +1233,17 @@ Used for hitting RET on a given link." ((eq link-type 'shr-url) (mastodon-url-lookup (get-text-property pos 'shr-url))) ((eq link-type 'read-more) - (mastodon-tl--unfold-post)) + (mastodon-tl-unfold-post)) ((eq link-type 'read-less) - (mastodon-tl--fold-post)) + (mastodon-tl-fold-post)) (t (error "Unknown link type %s" link-type))))) -(defun mastodon-tl--do-link-action (event) +(defun mastodon-tl-do-link-action (event) "Do the action of the link at point. Used for a mouse-click EVENT on a link." (interactive "@e") - (mastodon-tl--do-link-action-at-point (posn-point (event-end event)))) + (mastodon-tl-do-link-action-at-point (posn-point (event-end event)))) ;;; CONTENT WARNINGS @@ -1247,7 +1268,7 @@ content should be hidden." (list 'invisible new-state)) new-state))) ;; return what we set invisibility to -(defun mastodon-tl--toggle-spoiler-text-in-toot () +(defun mastodon-tl-toggle-spoiler-text-in-toot () "Toggle the visibility of the spoiler text in the current toot." (interactive) (let* ((toot-range (or (mastodon-tl--find-property-range @@ -1266,7 +1287,7 @@ content should be hidden." (t (mastodon-tl--toggle-spoiler-text (car spoiler-range)))))) -(defun mastodon-tl--toggle-spoiler-in-thread () +(defun mastodon-tl-toggle-spoiler-in-thread () "Toggler content warning for all posts in current thread." (interactive) (let ((thread-p (eq (mastodon-tl--buffer-property 'update-function) @@ -1276,13 +1297,13 @@ content should be hidden." (save-excursion (goto-char (point-min)) (while (not (string= "No more items" ; improve this hack test! - (mastodon-tl--goto-next-item :no-refresh))) + (mastodon-tl-goto-next-item :no-refresh))) (let* ((json (mastodon-tl--property 'item-json :no-move)) (cw (alist-get 'spoiler_text json))) (when (not (string= "" cw)) (let ((new-state (pcase - (mastodon-tl--toggle-spoiler-text-in-toot) + (mastodon-tl-toggle-spoiler-text-in-toot) ('t 'folded) ('nil 'unfolded)))) (plist-put mastodon-tl--buffer-spec @@ -1403,20 +1424,105 @@ SENSITIVE is a flag from the item's JSON data." help-echo (concat help-echo "\nC-RET: play " type " with mpv")))) -(defun mastodon-tl--view-full-image () +;;; FULL IMAGE VIEW + +(define-derived-mode mastodon-image-mode image-mode + "mastodon-image" + :group 'mastodon) + +;; patch `shr-browse-image' to accept url arg: +(defun mastodon-tl-shr-browse-image (&optional image-url copy-url) + "Browse the image under point. +If COPY-URL (the prefix if called interactively) is non-nil, copy +the URL of the image to the kill buffer instead. +Optionally use IMAGE-URL rather than the image-url property at point." + (interactive "sP") + (let ((url (or image-url (get-text-property (point) 'image-url)))) + (cond + ((not url) + (message "No image under point")) + (copy-url + (with-temp-buffer + (insert url) + (copy-region-as-kill (point-min) (point-max)) + (message "Copied %s" url))) + (t + (message "Browsing %s..." url) + (browse-url url))))) + +(defun mastodon-tl--view-image-url (url attachments) + "View image URL. Set ATTACHMENTS metadata in image buffer." + (if (not url) + (user-error "No url found") + (if (not mastodon-tl--load-full-sized-images-in-emacs) + (mastodon-tl-shr-browse-image url) + (mastodon-media--image-or-cached + url #'mastodon-media--process-full-sized-image-response + `(nil ,url ,attachments ,(buffer-name)))))) + +(defun mastodon-tl-view-full-image-at-point () "Browse full-sized version of image at point in a new window." (interactive) (if (not (eq (mastodon-tl--property 'mastodon-tab-stop) 'image)) (user-error "No image at point?") - (let* ((url (mastodon-tl--property 'image-url))) - (if (not mastodon-tl--load-full-sized-images-in-emacs) - (shr-browse-image) - (mastodon-media--image-or-cached - url - #'mastodon-media--process-full-sized-image-response - `(nil ,url)))))) - -(defun mastodon-tl--toggle-sensitive-image () + (let* ((url (mastodon-tl--property 'image-url)) + (attachments (mastodon-tl--property 'attachments))) + (mastodon-tl--view-image-url url attachments)))) + +(defun mastodon-tl-view-first-full-image () + "From item byline, fetch load its first full image." + (interactive) + (let* ((attachments (mastodon-tl--property 'attachments)) + (url (plist-get (car attachments) :url))) + (if (not attachments) + (user-error "Toot has no attachments") + (mastodon-tl--view-image-url url attachments)))) + +(defun mastodon-tl--get-next-image-url () + "Return the url for the next image to load. +Cycles through values in `mastodon-media--attachments'." + (let* ((url (car mastodon-media--attachments)) + ;; match url against our plists: + (current (mastodon-tl--current-image-url url))) + ;; fetch from next item in current or use first item if current has + ;; only 1 item: + (plist-get (if (= 1 (length current)) + (cadr mastodon-media--attachments) + (cadr current)) + :url))) + +(defun mastodon-tl--current-image-url (url) + "Try to fetch URL from `mastodon-media--attachments'. +The return value is that of `cl-member-if', ie if a match is found, it +returns the match and the list of which it is the car." + (cl-member-if + (lambda (attachment) + (equal url (plist-get attachment :url))) + (cdr mastodon-media--attachments))) + +(defun mastodon-tl--get-prev-image-url () + "Return the URL of the previous item in `mastodon-media--attachments'." + (let* ((url (car mastodon-media--attachments)) + (current (mastodon-tl--current-image-url url))) + (plist-get (nth (1- (length current)) + (cdr mastodon-media--attachments)) + :url))) + +(defun mastodon-tl-next-full-image () + "From full image view buffer, load the toot's next image." + (interactive) + (let* ((next-url (mastodon-tl--get-next-image-url))) + (mastodon-tl--view-image-url next-url + (cdr mastodon-media--attachments)))) + +(defun mastodon-tl-prev-full-image () + "From full image view buffer, load the toot's prev image." + (interactive) + (let* ((prev-url (mastodon-tl--get-prev-image-url))) + (mastodon-tl--view-image-url prev-url + (cdr mastodon-media--attachments)))) + +(defun mastodon-tl-toggle-sensitive-image () "Toggle dislay of sensitive image at point." (interactive) (if (not (eq t (mastodon-tl--property 'sensitive))) @@ -1459,7 +1565,9 @@ LENGTH is of the longest option, for formatting." (let* ((options (mastodon-tl--map-alist 'title .options)) (longest (car (sort (mapcar #'length options ) #'>))) (counter 0)) - (concat "\nPoll: \n\n" + (concat "\n" + (mastodon-tl--symbol 'poll) + "\n\n" (mapconcat (lambda (option) (setq counter (1+ counter)) (mastodon-tl--format-poll-option @@ -1474,13 +1582,13 @@ LENGTH is of the longest option, for formatting." (.vote_count (format "%s votes | " .vote_count)) (t "")) - 'face 'font-lock-comment-face) + 'face 'mastodon-toot-docs-face) (let ((str (if (eq .expired :json-false) (if (eq .expires_at nil) "" (mastodon-tl--format-poll-expiry .expires_at)) "Poll expired."))) - (propertize str 'face 'font-lock-comment-face)) + (propertize str 'face 'mastodon-toot-docs-face)) "\n")))) (defconst mastodon-tl--time-units @@ -1570,7 +1678,7 @@ OPTIONS is an alist." candidates nil :match))) (list (cdr (assoc choice candidates))))))) -(defun mastodon-tl--poll-vote (option) +(defun mastodon-tl-poll-vote (option) "If there is a poll at point, prompt user for OPTION to vote on it." (interactive (mastodon-tl--read-poll-option)) (let ((toot (mastodon-tl--property 'item-json))) @@ -1596,34 +1704,33 @@ OPTIONS is an alist." "Return the first media attachment that is a moving image." (let ((attachments (mastodon-tl--property 'attachments)) vids) - (mapc (lambda (x) - (let ((att-type (plist-get x :type))) - (when (or (string= "video" att-type) - (string= "gifv" att-type)) - (push x vids)))) - attachments) + (cl-loop for x in attachments + do (let ((att-type (plist-get x :type))) + (when (or (string= "video" att-type) + (string= "gifv" att-type)) + (push x vids)))) (car vids))) -(defun mastodon-tl--mpv-play-video-from-byline () - "Run `mastodon-tl--mpv-play-video-at-point' on first moving image in post." +(defun mastodon-tl-mpv-play-video-from-byline () + "Run `mastodon-tl-mpv-play-video-at-point' on first moving image in post." (interactive) (let* ((video (mastodon-tl--find-first-video-in-attachments)) (url (plist-get video :url)) (type (plist-get video :type))) - (mastodon-tl--mpv-play-video-at-point url type))) + (mastodon-tl-mpv-play-video-at-point url type))) -(defun mastodon-tl--view-full-image-or-play-video (_pos) +(defun mastodon-tl-view-full-image-or-play-video (_pos) "View full sized version of image at point, or try to play video." (interactive "d") (if (mastodon-tl--media-video-p) - (mastodon-tl--mpv-play-video-at-point) - (mastodon-tl--view-full-image))) + (mastodon-tl-mpv-play-video-at-point) + (mastodon-tl-view-full-image-at-point))) -(defun mastodon-tl--click-image-or-video (event) +(defun mastodon-tl-click-image-or-video (event) "Click to play video with `mpv.el'. EVENT is a mouse-click arg." (interactive "@e") - (mastodon-tl--view-full-image-or-play-video + (mastodon-tl-view-full-image-or-play-video (posn-point (event-end event)))) (defun mastodon-tl--media-video-p (&optional type) @@ -1633,7 +1740,7 @@ TYPE is a mastodon media type." (or (string= type "gifv") (string= type "video")))) -(defun mastodon-tl--mpv-play-video-at-point (&optional url type) +(defun mastodon-tl-mpv-play-video-at-point (&optional url type) "Play the video or gif at point with an mpv process. URL and TYPE are provided when called while point is on byline, in which case play first video or gif from current toot." @@ -1650,10 +1757,10 @@ in which case play first video or gif from current toot." (message "Looks like mpv.el not installed. Error: %s" (error-message-string x))))))) -(defun mastodon-tl--copy-image-caption () +(defun mastodon-tl-copy-image-caption () "Copy the caption of the image at point." (interactive) - (if-let ((desc (get-text-property (point) 'image-description))) + (if-let* ((desc (get-text-property (point) 'image-description))) (progn (kill-new desc) (message "Image caption copied.")) @@ -1811,7 +1918,7 @@ CW-EXPANDED means treat content warnings as unfolded." (filtered (mastodon-tl--field 'filtered toot)) (filters (when filtered (mastodon-tl--current-filters filtered))) - (spoiler-or-content (if-let ((match (assoc "warn" filters))) + (spoiler-or-content (if-let* ((match (assoc "warn" filters))) (mastodon-tl--spoiler toot (cadr match)) (if (mastodon-tl--has-spoiler toot) (mastodon-tl--spoiler toot) @@ -1839,9 +1946,8 @@ NO-BYLINE means just insert toot body, used for folding." (mastodon-tl--hide-replies-p current-prefix-arg)) (cl-remove-if-not #'mastodon-tl--is-reply toots) toots)))) - (mapc (lambda (toot) - (mastodon-tl--toot toot nil thread domain nil no-byline)) - toots) + (cl-loop for toot in toots + do (mastodon-tl--toot toot nil thread domain nil no-byline)) ;; media: (when mastodon-tl--display-media-p (mastodon-media--inline-images start-pos (point))) @@ -1876,14 +1982,14 @@ Folding decided by `mastodon-tl--fold-toots-at-length'." (propertize display 'read-more body))) -(defun mastodon-tl--unfold-post (&optional fold) +(defun mastodon-tl-unfold-post (&optional fold) "Unfold the toot at point if it is folded (read-more). FOLD means to fold it instead." (interactive) (let ((at-byline (mastodon-tl--property 'byline :no-move))) (if (save-excursion (when (not at-byline) - (mastodon-tl--goto-next-item)) + (mastodon-tl-goto-next-item)) (not (mastodon-tl--property 'toot-foldable :no-move))) (user-error "No foldable item at point?") (let* ((inhibit-read-only t) @@ -1922,23 +2028,23 @@ FOLD means to fold it instead." (cond ((or at-byline (and fold point-after-fold)) ;; point was in area now folded (ignore-errors (forward-line -1)) ;; in case we are between - (mastodon-tl--goto-next-item)) ;; goto byline + (mastodon-tl-goto-next-item)) ;; goto byline (t (goto-char last-point) (when point-after-fold ;; point was in READ MORE heading: (beginning-of-line)))) (message (format "%s toot" (if fold "Fold" "Unfold"))))))) -(defun mastodon-tl--fold-post () +(defun mastodon-tl-fold-post () "Fold post at point, if it is too long." (interactive) - (mastodon-tl--unfold-post :fold)) + (mastodon-tl-unfold-post :fold)) -(defun mastodon-tl--fold-post-toggle () +(defun mastodon-tl-fold-post-toggle () "Toggle the folding status of the toot at point." (interactive) (let* ((folded (mastodon-tl--property 'toot-folded :no-move))) - (mastodon-tl--unfold-post (not folded)))) + (mastodon-tl-unfold-post (not folded)))) ;;; TOOT STATS @@ -1976,19 +2082,19 @@ To disable showing the stats, customize 'favourited-p (eq t .favourited) 'favourites-field t 'help-echo (format "%s favourites" .favourites_count) - 'face 'font-lock-comment-face) - (propertize " | " 'face 'font-lock-comment-face) + 'face 'mastodon-toot-docs-face) + (propertize " | " 'face 'mastodon-toot-docs-face) (propertize boosts 'boosted-p (eq t .reblogged) 'boosts-field t 'help-echo (format "%s boosts" .reblogs_count) - 'face 'font-lock-comment-face) - (propertize " | " 'face 'font-lock-comment-face) + 'face 'mastodon-toot-docs-face) + (propertize " | " 'face 'mastodon-toot-docs-face) (propertize replies 'replies-field t 'replies-count .replies_count 'help-echo (format "%s replies" .replies_count) - 'face 'font-lock-comment-face))) + 'face 'mastodon-toot-docs-face))) (right-spacing (propertize " " 'display @@ -2101,6 +2207,8 @@ call this function after it is set or use something else." 'mentions) ((mastodon-tl--endpoint-str-= "notifications") 'notifications) + ((mastodon-tl--endpoint-str-= "notifications/requests") + 'notification-requests) ;; threads: ((mastodon-tl--endpoint-str-= "context" :suffix) 'thread) @@ -2110,7 +2218,7 @@ call this function after it is set or use something else." ((mastodon-tl--profile-buffer-p) (cond ;; an own profile option is needlessly confusing e.g. for - ;; `mastodon-profile--account-view-cycle' + ;; `mastodon-profile-account-view-cycle' ;; profile note: ((string-suffix-p "update-profile*" buffer-name) 'update-profile-note) @@ -2184,6 +2292,18 @@ call this function after it is set or use something else." This includes the update profile note buffer, but not the preferences one." (string-prefix-p "accounts" (mastodon-tl--endpoint nil :no-error))) +(defun mastodon-tl--own-profile-buffer-p () + "Return t if we are viewing our own profile buffer. +We check that our account credientials id matches the endpoint id in the +buffer spec, which if in a profile buffer is of the form +\"accounts/$id/statuses\"." + (and (mastodon-tl--profile-buffer-p) + (let ((endpoint-id + (nth 1 + (split-string (mastodon-tl--endpoint) "/")))) + (string= (mastodon-auth--get-account-id) + endpoint-id)))) + (defun mastodon-tl--search-buffer-p () "T if current buffer is a search buffer." (string-suffix-p "search" (mastodon-tl--endpoint nil :no-error))) @@ -2270,8 +2390,8 @@ BACKWARD means move backward (up) the timeline." (or (get-text-property (point) prop) (save-excursion (if backward - (mastodon-tl--goto-prev-item) - (mastodon-tl--goto-next-item)) + (mastodon-tl-goto-prev-item) + (mastodon-tl-goto-next-item)) (get-text-property (point) prop))))) (defun mastodon-tl--newest-id () @@ -2323,7 +2443,7 @@ webapp" ;;; THREADS -(defun mastodon-tl--single-toot (id) +(defun mastodon-tl-single-toot (id) "View toot at point in separate buffer. ID is that of the toot to view." (interactive) @@ -2342,14 +2462,14 @@ ID is that of the toot to view." (when mastodon-tl--display-media-p (mastodon-media--inline-images (point-min) (point-max))) - (mastodon-tl--goto-next-item :no-refresh))))) + (mastodon-tl-goto-next-item :no-refresh))))) (defun mastodon-tl--update-toot (json) - "Call `mastodon-tl--single-toot' on id found in JSON." + "Call `mastodon-tl-single-toot' on id found in JSON." (let ((id (alist-get 'id json))) - (mastodon-tl--single-toot id))) + (mastodon-tl-single-toot id))) -(defun mastodon-tl--view-whole-thread () +(defun mastodon-tl-view-whole-thread () "From a thread view, view entire thread. If you load a thread from a toot, only the branches containing are displayed by default. Call this if you subsequently want to @@ -2361,7 +2481,7 @@ view all branches of a thread." (let ((id (mastodon-tl--property 'base-item-id))) (mastodon-tl--thread-do id)))) -(defun mastodon-tl--thread () +(defun mastodon-tl-thread () "Open thread buffer for toot at point." (interactive) (if (not (eq 'toot (mastodon-tl--property 'item-type :no-move))) @@ -2404,7 +2524,7 @@ programmatically and not crash into (if (not (< 0 (+ (length (alist-get 'ancestors context)) (length (alist-get 'descendants context))))) ;; just print the lone toot: - (mastodon-tl--single-toot id) + (mastodon-tl-single-toot id) ;; we have a thread: (with-mastodon-buffer buffer #'mastodon-mode nil (let ((marker (make-marker))) @@ -2414,7 +2534,7 @@ programmatically and not crash into (when unfolded-state (plist-put mastodon-tl--buffer-spec 'thread-unfolded unfolded-state)) - (when-let ((ancestors (alist-get 'ancestors context))) + (when-let* ((ancestors (alist-get 'ancestors context))) (mastodon-tl--timeline ancestors :thread)) (goto-char (point-max)) (move-marker marker (point)) @@ -2425,19 +2545,19 @@ programmatically and not crash into (when mastodon-tl--display-media-p (mastodon-media--inline-images marker ;start-pos (point))) - (when-let ((descendants (alist-get 'descendants context))) + (when-let* ((descendants (alist-get 'descendants context))) (mastodon-tl--timeline descendants :thread)) ;; put point at the toot: (goto-char (marker-position marker)) - (mastodon-tl--goto-next-item :no-refresh))))))))) + (mastodon-tl-goto-next-item :no-refresh))))))))) -(defun mastodon-tl--mute-thread () +(defun mastodon-tl-mute-thread () "Mute the thread displayed in the current buffer. Note that you can only (un)mute threads you have posted in." (interactive) (mastodon-tl--mute-or-unmute-thread)) -(defun mastodon-tl--unmute-thread () +(defun mastodon-tl-unmute-thread () "Unmute the thread displayed in the current buffer. Note that you can only (un)mute threads you have posted in." (interactive) @@ -2494,8 +2614,8 @@ ID is that of the post the context is currently displayed for." ;;; FOLLOW/BLOCK/MUTE, ETC -(defun mastodon-tl--follow-user (user-handle - &optional notify langs reblogs json) +(defun mastodon-tl-follow-user (user-handle + &optional notify langs reblogs json) "Query for USER-HANDLE from current status and follow that user. If NOTIFY is \"true\", enable notifications when that user posts. If NOTIFY is \"false\", disable notifications when that user posts. @@ -2509,33 +2629,59 @@ JSON is a flag arg for `mastodon-http--post'." (mastodon-tl--do-user-action-and-response user-handle "follow" nil notify langs reblogs json))) +(defun mastodon-tl-follow-user-by-handle (user-handle) + "Prompt for a USER-HANDLE and follow that user. +USER-HANDLE can also be a URL to a user profile page." + ;; code adapted from sachac: + ;; https://sachachua.com/dotemacs/index.html#mastodon. thanks sachac! + (interactive "MHandle: ") + (when (string-match "https?://\\(.+?\\)/\\(@.+\\)" user-handle) + (setq user-handle + ;; sachac's model doesn't work with local user handles in URL, + ;; meaning the search below will fail, so we use our own + ;; URL-to-handle function, modified for the purpose: + ;; (concat (match-string 2 user-handle) "@" (match-string 1 user-handle)))) + (mastodon-tl--userhandle-from-url user-handle))) + (let* ((account (mastodon-profile--search-account-by-handle + user-handle)) + (user-id (alist-get 'id account)) + (name (if (not (string-empty-p + (alist-get 'display_name account))) + (alist-get 'display_name account) + (alist-get 'username account))) + (url (mastodon-http--api (format "accounts/%s/%s" user-id "follow")))) + (if account + (mastodon-tl--do-user-action-function url name + (substring user-handle 1) "follow") + (user-error "Cannot find a user with handle %S" user-handle)))) + ;; TODO: make this action "enable/disable notifications" -(defun mastodon-tl--enable-notify-user-posts (user-handle) +(defun mastodon-tl-enable-notify-user-posts (user-handle) "Query for USER-HANDLE and enable notifications when they post." (interactive (list (mastodon-tl--user-handles-get "enable"))) (mastodon-tl--do-if-item - (mastodon-tl--follow-user user-handle "true"))) + (mastodon-tl-follow-user user-handle "true"))) -(defun mastodon-tl--disable-notify-user-posts (user-handle) +(defun mastodon-tl-disable-notify-user-posts (user-handle) "Query for USER-HANDLE and disable notifications when they post." (interactive (list (mastodon-tl--user-handles-get "disable"))) - (mastodon-tl--follow-user user-handle "false")) + (mastodon-tl-follow-user user-handle "false")) -(defun mastodon-tl--follow-user-disable-boosts (user-handle) +(defun mastodon-tl-follow-user-disable-boosts (user-handle) "Prompt for a USER-HANDLE, and disable display of boosts in home timeline. If they are also not yet followed, follow them." (interactive (list (mastodon-tl--user-handles-get "disable boosts"))) - (mastodon-tl--follow-user user-handle nil nil "false")) + (mastodon-tl-follow-user user-handle nil nil "false")) -(defun mastodon-tl--follow-user-enable-boosts (user-handle) +(defun mastodon-tl-follow-user-enable-boosts (user-handle) "Prompt for a USER-HANDLE, and enable display of boosts in home timeline. If they are also not yet followed, follow them. You only need to call this if you have previously disabled display of boosts." (interactive (list (mastodon-tl--user-handles-get "enable boosts"))) - (mastodon-tl--follow-user user-handle nil nil "true")) + (mastodon-tl-follow-user user-handle nil nil "true")) -(defun mastodon-tl--filter-user-user-posts-by-language (user-handle) +(defun mastodon-tl-filter-user-user-posts-by-language (user-handle) "Query for USER-HANDLE and filter display of their posts by language. If they are not already followed, they will be too. To be filtered, a post has to be marked as in the language given. @@ -2545,10 +2691,10 @@ desired language if they are not marked as such (or as anything)." (let ((langs (mastodon-tl--read-filter-langs))) (mastodon-tl--do-if-item (if (string= "" (cdar langs)) - (mastodon-tl--unfilter-user-languages user-handle) - (mastodon-tl--follow-user user-handle nil langs))))) + (mastodon-tl-unfilter-user-languages user-handle) + (mastodon-tl-follow-user user-handle nil langs))))) -(defun mastodon-tl--unfilter-user-languages (user-handle) +(defun mastodon-tl-unfilter-user-languages (user-handle) "Remove any language filters for USER-HANDLE. This means you will receive posts of theirs marked as being in any or no language." @@ -2557,7 +2703,7 @@ any or no language." (mastodon-tl--do-if-item ;; we need "languages[]" as a param, with no "=" and not json-encoded as ;; a string - (mastodon-tl--follow-user user-handle nil langs nil :raw)))) + (mastodon-tl-follow-user user-handle nil langs nil :raw)))) (defun mastodon-tl--read-filter-langs (&optional langs) "Read language choices and return an alist array parameter. @@ -2576,39 +2722,39 @@ LANGS is the accumulated array param alist if we re-run recursively." (mastodon-tl--read-filter-langs langs-alist) langs-alist)))) -(defun mastodon-tl--unfollow-user (user-handle) +(defun mastodon-tl-unfollow-user (user-handle) "Query for USER-HANDLE from current status and unfollow that user." (interactive (list (mastodon-tl--user-handles-get "unfollow"))) (mastodon-tl--do-if-item (mastodon-tl--do-user-action-and-response user-handle "unfollow" t))) -(defun mastodon-tl--block-user (user-handle) +(defun mastodon-tl-block-user (user-handle) "Query for USER-HANDLE from current status and block that user." (interactive (list (mastodon-tl--user-handles-get "block"))) (mastodon-tl--do-if-item (mastodon-tl--do-user-action-and-response user-handle "block"))) -(defun mastodon-tl--unblock-user (user-handle) +(defun mastodon-tl-unblock-user (user-handle) "Query for USER-HANDLE from list of blocked users and unblock that user." (interactive (list (mastodon-tl--get-blocks-or-mutes-list "unblock"))) (if (not user-handle) (user-error "Looks like you have no blocks to unblock!") (mastodon-tl--do-user-action-and-response user-handle "unblock" t))) -(defun mastodon-tl--mute-user (user-handle) +(defun mastodon-tl-mute-user (user-handle) "Query for USER-HANDLE from current status and mute that user." (interactive (list (mastodon-tl--user-handles-get "mute"))) (mastodon-tl--do-if-item (mastodon-tl--do-user-action-and-response user-handle "mute"))) -(defun mastodon-tl--unmute-user (user-handle) +(defun mastodon-tl-unmute-user (user-handle) "Query for USER-HANDLE from list of muted users and unmute that user." (interactive (list (mastodon-tl--get-blocks-or-mutes-list "unmute"))) (if (not user-handle) (user-error "Looks like you have no mutes to unmute!") (mastodon-tl--do-user-action-and-response user-handle "unmute" t))) -(defun mastodon-tl--dm-user (user-handle) +(defun mastodon-tl-dm-user (user-handle) "Query for USER-HANDLE from current status and compose a message to that user." (interactive (list (mastodon-tl--user-handles-get "message"))) (mastodon-tl--do-if-item @@ -2679,7 +2825,7 @@ Action must be either \"unblock\" or \"unmute\"." NEGP is whether the action involves un-doing something. If NOTIFY is \"true\", enable notifications when that user posts. If NOTIFY is \"false\", disable notifications when that user posts. -NOTIFY is only non-nil when called by `mastodon-tl--follow-user'. +NOTIFY is only non-nil when called by `mastodon-tl-follow-user'. LANGS is an array parameters alist of languages to filer user's posts by. REBLOGS is a boolean string like NOTIFY, enabling or disabling display of the user's boosts in your timeline." @@ -2724,7 +2870,7 @@ display of the user's boosts in your timeline." (url name user-handle action &optional notify args reblogs json) "Post ACTION on user NAME/USER-HANDLE to URL. NOTIFY is either \"true\" or \"false\", and used when we have been called -by `mastodon-tl--follow-user' to enable or disable notifications. +by `mastodon-tl-follow-user' to enable or disable notifications. ARGS is an alist of any parameters to send with the request." (let ((response (mastodon-http--post url args nil nil json))) (mastodon-http--triage @@ -2768,7 +2914,7 @@ ARGS is an alist of any parameters to send with the request." (mastodon-http--get-json (mastodon-http--api "domain_blocks"))) -(defun mastodon-tl--block-domain () +(defun mastodon-tl-block-domain () "Read a domain and block it." (interactive) (let* ((domain (read-string "Block domain: ")) @@ -2780,7 +2926,7 @@ ARGS is an alist of any parameters to send with the request." (lambda (_) (message "Domain blocked!"))))) -(defun mastodon-tl--unblock-domain () +(defun mastodon-tl-unblock-domain () "Read a blocked domain and unblock it." (interactive) (let ((blocks (mastodon-tl--get-domain-blocks))) @@ -2805,7 +2951,7 @@ ARGS is an alist of any parameters to send with the request." (tags (mastodon-tl--field 'tags toot))) (mastodon-tl--map-alist 'name tags))) -(defun mastodon-tl--follow-tag (&optional tag) +(defun mastodon-tl-follow-tag (&optional tag) "Prompt for a tag (from post at point) and follow it. If TAG provided, follow it." (interactive) @@ -2829,7 +2975,7 @@ If TAG provided, follow it." (let ((url (mastodon-http--api (format "followed_tags")))) (mastodon-http--get-json url))) -(defun mastodon-tl--unfollow-tag (&optional tag) +(defun mastodon-tl-unfollow-tag (&optional tag) "Prompt for a followed tag, and unfollow it. If TAG is provided, unfollow it." (interactive) @@ -2842,18 +2988,18 @@ If TAG is provided, unfollow it." (lambda (_) (message "tag #%s unfollowed!" tag))))) -(defun mastodon-tl--list-followed-tags (&optional prefix) +(defun mastodon-tl-list-followed-tags (&optional prefix) "List followed tags. View timeline of tag user choses. -PREFIX is sent to `mastodon-tl--get-tag-timeline', which see." +PREFIX is sent to `mastodon-tl-get-tag-timeline', which see." (interactive "p") (let* ((followed-tags-json (mastodon-tl--followed-tags)) (tags (mastodon-tl--map-alist 'name followed-tags-json)) (tag (completing-read "Tag: " tags nil))) (if (null tag) (user-error "You have to follow some tags first") - (mastodon-tl--get-tag-timeline prefix tag)))) + (mastodon-tl-get-tag-timeline prefix tag)))) -(defun mastodon-tl--followed-tags-timeline (&optional prefix) +(defun mastodon-tl-followed-tags-timeline (&optional prefix) "Open a timeline of multiple tags. With a single PREFIX arg, only show posts with media. With a double PREFIX arg, limit results to your own instance. @@ -2865,7 +3011,7 @@ fetch followed tags and load the first four of them." (mastodon-tl--map-alist 'name followed-tags-json)))) (mastodon-tl--show-tag-timeline prefix tags))) -(defun mastodon-tl--some-followed-tags-timeline (&optional prefix) +(defun mastodon-tl-some-followed-tags-timeline (&optional prefix) "Prompt for some tags, and open a timeline for them. The suggestions are from followed tags, but any other tags are also allowed. PREFIX is for `mastodon-tl--show-tag-timeline', which see." @@ -2877,6 +3023,28 @@ PREFIX is for `mastodon-tl--show-tag-timeline', which see." tags))) (mastodon-tl--show-tag-timeline prefix selection))) +(defcustom mastodon-tl--tags-groups nil + "A list containing lists of up to four tags each. +You can load a tag timeline list with one of these by calling +`mastodon-tl-tag-group-timeline'." + :group 'mastodon-tl + :type '(repeat (list string string string string))) + +(defun mastodon-tl-tag-group-timeline (&optional prefix) + "Load a timeline of a tag group from `mastodon-tl--tags-groups'. +PREFIX is for `mastodon-tl--show-tag-timeline', which see." + (interactive "P") + (if (not mastodon-tl--tags-groups) + (user-error + "Set `mastodon-tl--tags-groups' to view tag group timelines") + (let* ((list-strs (mapcar (lambda (x) + ;; cons of list-as-string and list: + (cons (prin1-to-string x) x)) + mastodon-tl--tags-groups)) + (choice (completing-read "Tag group: " list-strs)) + (choice-list (cdr (assoc choice list-strs #'equal)))) + (mastodon-tl--show-tag-timeline prefix choice-list)))) + ;;; REPORT TO MODERATORS @@ -2904,20 +3072,19 @@ ACCOUNT and TOOT are the data to use." "Build the parameters alist based on user responses. ACCOUNT-ID, COMMENT, ITEM-ID, FORWARD-P, CAT, and RULES are all from `mastodon-tl--report-params', which see." - (let ((params (cl-remove - nil - `(("account_id" . ,account-id) - ,(when comment `("comment" . ,comment)) - ,(when item-id `("status_ids[]" . ,item-id)) - ,(when forward-p `("forward" . ,forward-p)) - ,(when cat `("category" . ,cat)))))) + (let ((params + `(("account_id" . ,account-id) + ,@(when comment `(("comment" . ,comment))) + ,@(when item-id `(("status_ids[]" . ,item-id))) + ,@(when forward-p `(("forward" . ,forward-p))) + ,@(when cat `(("category" . ,cat)))))) (if (not rules) params (let ((alist (mastodon-http--build-array-params-alist "rule_ids[]" rules))) (append alist params))))) -(defun mastodon-tl--report-to-mods () +(defun mastodon-tl-report-to-mods () "Report the author of the toot at point to your instance moderators. Optionally report the toot at point, add a comment, cite rules that have been broken, forward the report to the remove admin, @@ -2957,6 +3124,13 @@ report the account for spam." ;;; UPDATING, etc. +(defun mastodon-tl--no-json (json) + "Nil if JSON is nil or empty group notif data." + (if (and (mastodon-tl--buffer-type-eq 'notifications) + mastodon-group-notifications) + (mastodon-notifications--empty-group-json-p json) + (not json))) + (defun mastodon-tl--more-json (endpoint id) "Return JSON for timeline ENDPOINT before ID." (let* ((args `(("max_id" . ,(mastodon-tl--as-string id)))) @@ -3004,13 +3178,14 @@ Then run CALLBACK with arguments CBARGS." (setf (alist-get "offset" params nil nil #'string=) offset) (apply #'mastodon-http--get-json-async url params callback cbargs))) -(defun mastodon-tl--updated-json (endpoint id &optional params) +(defun mastodon-tl--updated-json (endpoint id &optional params version) "Return JSON for timeline ENDPOINT since ID. PARAMS is used to send any parameters needed to correctly update -the current view." +the current view. +VERSION is the API version to use, as grouped notifs use v2." (let* ((args `(("since_id" . ,(mastodon-tl--as-string id)))) (args (append args params)) - (url (mastodon-http--api endpoint))) + (url (mastodon-http--api endpoint version))) (mastodon-http--get-json url args))) ;; TODO: add this to new posts in some cases, e.g. in thread view. @@ -3022,23 +3197,23 @@ Aims to respect any pagination in effect." (let ((type (mastodon-tl--get-buffer-type)) (max-id (mastodon-tl--buffer-property 'max-id nil :no-error))) (cond ((eq type 'home) - (mastodon-tl--get-home-timeline nil max-id)) + (mastodon-tl-get-home-timeline nil max-id)) ((eq type 'federated) - (mastodon-tl--get-federated-timeline nil nil max-id)) + (mastodon-tl-get-federated-timeline nil nil max-id)) ((eq type 'local) - (mastodon-tl--get-local-timeline nil max-id)) + (mastodon-tl-get-local-timeline nil max-id)) ((eq type 'mentions) - (mastodon-notifications--get-mentions)) + (mastodon-notifications-get-mentions)) ((eq type 'notifications) (mastodon-notifications-get nil nil max-id)) ((eq type 'profile-statuses-no-boosts) ;; TODO: max-id arg needed here also - (mastodon-profile--open-statuses-no-reblogs)) + (mastodon-profile-open-statuses-no-reblogs)) ((eq type 'profile-statuses) (save-excursion (goto-char (point-min)) (mastodon-profile--make-author-buffer - ;; (mastodon-profile--get-toot-author max-id))) + ;; (mastodon-profile-get-toot-author max-id))) (mastodon-profile--profile-json)))) ((or (eq type 'single-status) (eq type 'thread)) @@ -3050,7 +3225,7 @@ Aims to respect any pagination in effect." ;; end up past where we were, etc. (when pos (goto-char pos) - (mastodon-tl--goto-prev-item :no-refresh)))) + (mastodon-tl-goto-prev-item :no-refresh)))) (defun mastodon-tl--build-link-header-url (str) "Return a URL from STR, an http Link header." @@ -3072,7 +3247,7 @@ and profile pages when showing followers or accounts followed." (defun mastodon-tl--get-link-header-from-response (headers) "Get http Link header from list of http HEADERS." ;; pleroma uses "link", so case-insensitive match required: - (when-let ((link-headers (alist-get "Link" headers nil nil #'cl-equalp))) + (when-let* ((link-headers (alist-get "Link" headers nil nil #'cl-equalp))) (split-string link-headers ", "))) (defun mastodon-tl--more () @@ -3148,11 +3323,11 @@ MAX-ID is the pagination parameter, a string." ;; if thread fully unfolded, respect it: ;; if thread view, call --thread-do with parent ID (progn (goto-char (point-min)) - (mastodon-tl--goto-next-item) + (mastodon-tl-goto-next-item) (mastodon-tl--thread-do) (goto-char point-before) (message "Loaded full thread.")) - (if (not json) + (if (mastodon-tl--no-json json) (user-error "No more results") (if notifs-p (mastodon-notifications--timeline json notif-type :update) @@ -3328,7 +3503,7 @@ This location is defined by a non-nil value of (set-marker-insertion-type marker t) (setq mastodon-tl--after-update-marker marker)))) -(defun mastodon-tl--update () +(defun mastodon-tl-update () "Update timeline with new toots." (interactive) ;; FIXME: actually these buffers should just reload by calling their own @@ -3344,27 +3519,34 @@ This location is defined by a non-nil value of (user-error "Update not available in this view") ;; FIXME: handle update for search and trending buffers (let* ((endpoint (mastodon-tl--endpoint)) - (update-function (mastodon-tl--update-function)) + (update-fun (mastodon-tl--update-function)) (id (mastodon-tl--newest-id))) ;; update a thread, without calling `mastodon-tl--updated-json': (if (mastodon-tl--buffer-type-eq 'thread) ;; load whole thread: (progn (mastodon-tl--thread-do id) (message "Loaded full thread.")) - ;; update other timelines: - (let* ((params (mastodon-tl--update-params)) - (json (mastodon-tl--updated-json endpoint id params))) - (if (not json) - (user-error "Nothing to update") - (let ((inhibit-read-only t)) - (mastodon-tl--set-after-update-marker) - (goto-char (or mastodon-tl--update-point (point-min))) - (if (eq update-function 'mastodon-notifications--timeline) - (funcall update-function json nil :update) - (funcall update-function json)) - (if mastodon-tl--after-update-marker - (goto-char mastodon-tl--after-update-marker) - (mastodon-tl--goto-next-item))))))))) + (if (not id) ;; if e.g. notifs all cleared: + (user-error "No last id") + ;; update other timelines: + (let* ((params (mastodon-tl--update-params)) + (notifs-p + (eq update-fun 'mastodon-notifications--timeline)) + (json (mastodon-tl--updated-json + endpoint id params + (when (and notifs-p mastodon-group-notifications) + "v2")))) + (if (mastodon-tl--no-json json) + (user-error "Nothing to update") + (let ((inhibit-read-only t)) + (mastodon-tl--set-after-update-marker) + (goto-char (or mastodon-tl--update-point (point-min))) + (if notifs-p + (funcall update-fun json nil :update) + (funcall update-fun json)) + (if mastodon-tl--after-update-marker + (goto-char mastodon-tl--after-update-marker) + (mastodon-tl-goto-next-item)))))))))) ;;; LOADING TIMELINES @@ -3414,7 +3596,7 @@ NO-BYLINE means just insert toot body, used for announcements." ;; FIXME: this could possibly be a fallback for all timelines not ;; just home? (when (string= endpoint "timelines/home") - (mastodon-search--trending-statuses))) + (mastodon-search-trending-statuses))) ((eq (caar json) 'error) (user-error "Looks like the server bugged out: \"%s\"" (cdar json))) (t @@ -3439,7 +3621,8 @@ Optional arg NOTE-TYPE means only get that type of notification. PARAMS is an alist of any params to include in the request. HEADERS are any headers to send in the request. VIEW-NAME is a string, to be used as a heading for the view. -BINDING-STR is a string explaining any bindins in the view. +BINDING-STR is a string explaining any bindins in the view, it can have +formatting for `substitute-command-keys'. ENDPOINT-VERSION is a string, format Vx, e.g. V2." ;; Used by `mastodon-notifications-get' and in views.el (let* ((notes-params (when note-type @@ -3458,8 +3641,10 @@ ENDPOINT-VERSION is a string, format Vx, e.g. V2." (when view-name (mastodon-search--insert-heading view-name)) (when binding-str - (insert (mastodon-tl--set-face (concat "[" binding-str "]\n\n") - 'font-lock-comment-face))) + (insert + (substitute-command-keys + (mastodon-tl--set-face (concat "[" binding-str "]\n\n") + 'mastodon-toot-docs-face)))) (mastodon-tl--set-buffer-spec buffer endpoint update-function link-header params nil diff --git a/elpa/mastodon-20241223.1040/mastodon-toot.el b/elpa/mastodon-20250330.1519/mastodon-toot.el similarity index 93% rename from elpa/mastodon-20241223.1040/mastodon-toot.el rename to elpa/mastodon-20250330.1519/mastodon-toot.el index b80dc468..9ebb1ef0 100644 --- a/elpa/mastodon-20241223.1040/mastodon-toot.el +++ b/elpa/mastodon-20250330.1519/mastodon-toot.el @@ -75,7 +75,7 @@ (autoload 'mastodon-profile--fetch-server-account-settings "mastodon-profile") (autoload 'mastodon-profile--fetch-server-account-settings-maybe "mastodon-profile") (autoload 'mastodon-profile--get-source-pref "mastodon-profile") -(autoload 'mastodon-profile--show-user "mastodon-profile") +(autoload 'mastodon-profile-show-user "mastodon-profile") (autoload 'mastodon-profile--update-preference "mastodon-profile") (autoload 'mastodon-search--search-accounts-query "mastodon-search") (autoload 'mastodon-search--search-tags-query "mastodon-search") @@ -85,7 +85,7 @@ (autoload 'mastodon-tl--field "mastodon-tl") (autoload 'mastodon-tl--find-property-range "mastodon-tl") (autoload 'mastodon-tl--find-property-range "mastodon-tl") -(autoload 'mastodon-tl--goto-next-item "mastodon-tl") +(autoload 'mastodon-tl-goto-next-item "mastodon-tl") (autoload 'mastodon-tl--map-alist "mastodon-tl") (autoload 'mastodon-tl--property "mastodon-tl") (autoload 'mastodon-tl--reload-timeline-or-profile "mastodon-tl") @@ -94,8 +94,8 @@ (autoload 'mastodon-tl--symbol "mastodon-tl") (autoload 'mastodon-tl--item-id "mastodon-tl") (autoload 'mastodon-toot "mastodon") -(autoload 'mastodon-views--cancel-scheduled-toot "mastodon-views") -(autoload 'mastodon-views--view-scheduled-toots "mastodon-views") +(autoload 'mastodon-views-cancel-scheduled-toot "mastodon-views") +(autoload 'mastodon-views-view-scheduled-toots "mastodon-views") (autoload 'org-read-date "org") (autoload 'mastodon-tl--toot-or-base "mastodon-tl") (autoload 'mastodon-profile--get-source-value "mastodon-toot") @@ -106,8 +106,9 @@ (autoload 'mastodon-tl--image-trans-check "mastodon-tl") (autoload 'mastodon-instance-data "mastodon") (autoload 'mastodon-create-poll "mastodon-transient") +(autoload 'mastodon-tl--own-profile-buffer-p "mastodon-tl") -;; for mastodon-toot--translate-toot-text +;; for mastodon-toot-translate-toot-text (autoload 'mastodon-tl--content "mastodon-tl") (when (require 'lingva nil :no-error) (declare-function lingva-translate "lingva")) @@ -200,7 +201,7 @@ Valid values are \"direct\", \"private\" (followers-only), This is determined by the account setting on the server. To change the setting on the server, see -`mastodon-toot--set-default-visibility'.") +`mastodon-toot-set-default-visibility'.") (defvar-local mastodon-toot--media-attachments nil "A list of the media attachments of the toot being composed.") @@ -320,22 +321,22 @@ property, and call BODY-FUN on them." (defvar mastodon-toot-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") #'mastodon-toot--send) - (define-key map (kbd "C-c C-k") #'mastodon-toot--cancel) - (define-key map (kbd "C-c C-w") #'mastodon-toot--set-content-warning) - (define-key map (kbd "C-c C-n") #'mastodon-toot--toggle-nsfw) - (define-key map (kbd "C-c C-v") #'mastodon-toot--change-visibility) - (define-key map (kbd "C-c C-e") #'mastodon-toot--insert-emoji) - (define-key map (kbd "C-c C-a") #'mastodon-toot--attach-media) - (define-key map (kbd "C-c !") #'mastodon-toot--clear-all-attachments) - (define-key map (kbd "C-c C-p") #'mastodon-toot--create-poll) - (define-key map (kbd "C-c C-o") #'mastodon-toot--clear-poll) - (define-key map (kbd "C-c C-l") #'mastodon-toot--set-toot-language) - (define-key map (kbd "C-c C-s") #'mastodon-toot--schedule-toot) + (define-key map (kbd "C-c C-c") #'mastodon-toot-send) + (define-key map (kbd "C-c C-k") #'mastodon-toot-cancel) + (define-key map (kbd "C-c C-w") #'mastodon-toot-set-content-warning) + (define-key map (kbd "C-c C-n") #'mastodon-toot-toggle-nsfw) + (define-key map (kbd "C-c C-v") #'mastodon-toot-change-visibility) + (define-key map (kbd "C-c C-e") #'mastodon-toot-insert-emoji) + (define-key map (kbd "C-c C-a") #'mastodon-toot-attach-media) + (define-key map (kbd "C-c !") #'mastodon-toot-clear-all-attachments) + (define-key map (kbd "C-c C-p") #'mastodon-toot-create-poll) + (define-key map (kbd "C-c C-o") #'mastodon-toot-clear-poll) + (define-key map (kbd "C-c C-l") #'mastodon-toot-set-toot-language) + (define-key map (kbd "C-c C-s") #'mastodon-toot-schedule-toot) map) "Keymap for `mastodon-toot'.") -(defun mastodon-toot--set-default-visibility () +(defun mastodon-toot-set-default-visibility () "Set the default visibility for toots on the server." (interactive) (let ((vis (completing-read "Set default visibility to:" @@ -399,7 +400,7 @@ JSON is added to the string as its item-json." ;; we don't move to the following toot: (beginning-of-line) (forward-line -1) - (mastodon-tl--goto-next-item))))) + (mastodon-tl-goto-next-item))))) (defun mastodon-toot--action (action callback) "Take ACTION, a string, on toot at point, then execute CALLBACK. @@ -420,7 +421,7 @@ ACTION is a symbol, either `favourite' or `boost.'" (boost-p (eq action 'boost)) (action-str (symbol-name action)) (item-json (mastodon-tl--property 'item-json)) - (vis (mastodon-tl--field 'vis item-json))) + (vis (mastodon-tl--field 'visibility item-json))) (cond ((not byline-region) (user-error "Nothing to %s here?!?" action-str)) @@ -440,9 +441,11 @@ ACTION is a symbol, either `favourite' or `boost.'" (faved (when byline-region (get-text-property (car byline-region) 'favourited-p))) (str-api (if boost-p "reblog" action-str)) - (action-str-api (mastodon-toot--str-negify str-api faved boosted)) + (action-str-api (mastodon-toot--str-negify str-api faved boosted + action)) (action-pp (concat - (mastodon-toot--str-negify action-str faved boosted) + (mastodon-toot--str-negify action-str faved boosted + action) (if boost-p "ed" "d"))) (remove-p (if boost-p boosted faved))) (mastodon-toot--action @@ -459,11 +462,14 @@ ACTION is a symbol, either `favourite' or `boost.'" byline-region remove-p item-json)) (message "%s #%s" action-pp id))))))))) -(defun mastodon-toot--str-negify (str faved boosted) - "Add \"un\" to STR if FAVED or BOOSTED is non-nil." - (if (or faved boosted) - (concat "un" str) - str)) +(defun mastodon-toot--str-negify (str faved boosted action) + "Add \"un\" to STR if item is already FAVED or BOOSTED. +ACTION is the action currently being taken." + (if (eq action 'boost) + (if boosted (concat "un" str) str) + (if faved + (concat "un" str) + str))) (defun mastodon-toot--update-stats-on-action (action &optional subtract) "Increment the toot stats display upon ACTION. @@ -489,18 +495,18 @@ SUBTRACT means we are un-favouriting or unboosting, so we decrement." count-prop (mastodon-toot--inc-or-dec count subtract)))))) -(defun mastodon-toot--toggle-boost () +(defun mastodon-toot-toggle-boost () "Boost/unboost toot at `point'." (interactive) (mastodon-toot--toggle-boost-or-favourite 'boost)) -(defun mastodon-toot--toggle-favourite () +(defun mastodon-toot-toggle-favourite () "Favourite/unfavourite toot at `point'." (interactive) (mastodon-toot--toggle-boost-or-favourite 'favourite)) ;; TODO maybe refactor into boost/fave fun -(defun mastodon-toot--toggle-bookmark () +(defun mastodon-toot-toggle-bookmark () "Bookmark or unbookmark toot at point." (interactive) (mastodon-toot--with-toot-item @@ -531,7 +537,7 @@ SUBTRACT means we are un-favouriting or unboosting, so we decrement." byline-region bookmarked-p item-json) (message "%s #%s" message id)))))))))) -(defun mastodon-toot--list-boosters () +(defun mastodon-toot-list-boosters () "List the boosters of toot at point." (interactive) ;; use grouped notifs data if present: @@ -543,7 +549,7 @@ SUBTRACT means we are un-favouriting or unboosting, so we decrement." (mastodon-tl--property 'notification-accounts :no-move)))) (mastodon-toot--list-boosters-or-favers nil accounts))) -(defun mastodon-toot--list-favouriters () +(defun mastodon-toot-list-favouriters () "List the favouriters of toot at point." (interactive) (let* ((type (mastodon-tl--property 'notification-type :no-move)) @@ -572,9 +578,9 @@ ACCOUNTS is notfications accounts if any." (let ((choice (completing-read (format "%s (enter to view profile): " type-string) handles nil t))) - (mastodon-profile--show-user choice)))))))) + (mastodon-profile-show-user choice)))))))) -(defun mastodon-toot--copy-toot-url () +(defun mastodon-toot-copy-toot-url () "Copy URL of toot at point. If the toot is a fave/boost notification, copy the URL of the base toot." @@ -583,7 +589,7 @@ base toot." (kill-new url) (message "Toot URL copied to the clipboard."))) -(defun mastodon-toot--browse-toot-url () +(defun mastodon-toot-browse-toot-url () "Browse URL of toot at point. Calls `browse-url'." (interactive) @@ -597,7 +603,7 @@ Calls `browse-url'." (alist-get 'url (alist-get 'reblog toot)) (alist-get 'url toot)))) -(defun mastodon-toot--copy-toot-text () +(defun mastodon-toot-copy-toot-text () "Copy text of toot at point. If the toot is a fave/boost notification, copy the text of the base toot." @@ -606,12 +612,12 @@ base toot." (kill-new (mastodon-tl--content toot)) (message "Toot content copied to the clipboard."))) -(defun mastodon-toot--translate-toot-text () +(defun mastodon-toot-translate-toot-text () "Translate text of toot at point. Uses `lingva.el'." (interactive) (if mastodon-tl--buffer-spec - (if-let ((toot (mastodon-tl--property 'item-json))) + (if-let* ((toot (mastodon-tl--property 'item-json))) (condition-case x (lingva-translate nil (mastodon-tl--content toot) @@ -631,7 +637,7 @@ Uses `lingva.el'." (string= (alist-get 'acct (alist-get 'account json)) (mastodon-auth--user-acct)))) -(defun mastodon-toot--pin-toot-toggle () +(defun mastodon-toot-pin-toot-toggle () "Pin or unpin user's toot at point." (interactive) (let* ((toot (mastodon-toot--base-toot-or-item-json)) @@ -642,22 +648,24 @@ Uses `lingva.el'." (if (not pinnable-p) (user-error "You can only pin your own toots") (when (y-or-n-p (format "%s this toot? " (capitalize action))) - (mastodon-toot--action action - (lambda (_) - (when mastodon-tl--buffer-spec - (mastodon-tl--reload-timeline-or-profile)) - (message "Toot %s!" msg))))))) + (mastodon-toot--action + action + (lambda (_) + ;; let's only reload when in own profile view: + (when (mastodon-tl--own-profile-buffer-p) + (mastodon-tl--reload-timeline-or-profile)) + (message "Toot %s!" msg))))))) ;;; DELETE, DRAFT, REDRAFT -(defun mastodon-toot--delete-toot () +(defun mastodon-toot-delete-toot () "Delete user's toot at point synchronously." (interactive) - (mastodon-toot--delete-and-redraft-toot t)) + (mastodon-toot-delete-and-redraft-toot t)) ;; TODO: handle media/poll for redrafting toots -(defun mastodon-toot--delete-and-redraft-toot (&optional no-redraft) +(defun mastodon-toot-delete-and-redraft-toot (&optional no-redraft) "Delete and redraft user's toot at point synchronously. NO-REDRAFT means delete toot only." (interactive) @@ -687,7 +695,7 @@ NO-REDRAFT means delete toot only." (defun mastodon-toot--set-cw (&optional cw) "Set content warning to CW if it is non-nil." - (unless (or (null cw) ; cw is nil for `mastodon-tl--dm-user' + (unless (or (null cw) ; cw is nil for `mastodon-tl-dm-user' (string-empty-p cw)) (setq mastodon-toot--content-warning cw))) @@ -756,16 +764,16 @@ CANCEL means the toot was not sent, so we save the toot text as a draft." (quit-window 'kill) (mastodon-toot--restore-previous-window-config prev-window-config))) -(defun mastodon-toot--cancel () +(defun mastodon-toot-cancel () "Kill new-toot buffer/window. Does not POST content. If toot is not empty, prompt to save text as a draft." (interactive) (when (and (not (mastodon-toot--empty-p)) (y-or-n-p "Save draft toot?")) - (mastodon-toot--save-draft)) + (mastodon-toot-save-draft)) (mastodon-toot--kill)) -(defun mastodon-toot--save-draft () +(defun mastodon-toot-save-draft () "Save the current compose toot text as a draft. Pushes `mastodon-toot-current-toot-text' to `mastodon-toot-draft-toots-list'." @@ -788,7 +796,7 @@ TEXT-ONLY means don't check for attachments or polls." ;;; EMOJIS -(defun mastodon-toot--insert-emoji () +(defun mastodon-toot-insert-emoji () "Prompt to insert an emoji." (interactive) (if mastodon-use-emojify @@ -800,10 +808,10 @@ TEXT-ONLY means don't check for attachments or polls." (concat (expand-file-name emojify-emojis-dir) "/mastodon-custom-emojis/")) -(defun mastodon-toot--download-custom-emoji () +(defun mastodon-toot-download-custom-emoji () "Download `mastodon-instance-url's custom emoji. Emoji images are stored in a subdir of `emojify-emojis-dir'. -To use the downloaded emoji, run `mastodon-toot--enable-custom-emoji'." +To use the downloaded emoji, run `mastodon-toot-enable-custom-emoji'." (interactive) (let* ((url (mastodon-http--api "custom_emojis")) (custom-emoji (mastodon-http--get-json url)) @@ -812,20 +820,19 @@ To use the downloaded emoji, run `mastodon-toot--enable-custom-emoji'." (user-error "Looks like you need to set up emojify first") (unless (file-directory-p mastodon-custom-emoji-dir) (make-directory mastodon-custom-emoji-dir nil)) ; no add parent - (mapc (lambda (x) - (let ((url (alist-get 'url x)) - (shortcode (alist-get 'shortcode x))) - ;; skip anything that contains unexpected characters - (when (and url shortcode - (string-match-p "^[a-zA-Z0-9-_]+$" shortcode) - (string-match-p "^[a-zA-Z]+$" (file-name-extension url))) - (url-copy-file url - (concat mastodon-custom-emoji-dir - shortcode - "." - (file-name-extension url)) - t)))) - custom-emoji) + (cl-loop for x in custom-emoji + do (let ((url (alist-get 'url x)) + (shortcode (alist-get 'shortcode x))) + ;; skip anything that contains unexpected characters + (when (and url shortcode + (string-match-p "^[a-zA-Z0-9-_]+$" shortcode) + (string-match-p "^[a-zA-Z]+$" (file-name-extension url))) + (url-copy-file url + (concat mastodon-custom-emoji-dir + shortcode + "." + (file-name-extension url)) + t)))) (message "Custom emoji for %s downloaded to %s" mastodon-instance-url mastodon-custom-emoji-dir)))) @@ -838,27 +845,26 @@ The list is formatted for `emojify-user-emojis', which see." nil ; not full path "^[^.]")) ; no dot files mastodon-emojify-user-emojis) - (mapc (lambda (x) - (push - `(,(concat ":" - (file-name-base x) ":") - . (("name" . ,(file-name-base x)) - ("image" . ,(concat mastodon-custom-emojis-dir x)) - ("style" . "github"))) - mastodon-emojify-user-emojis)) - custom-emoji-files) + (cl-loop for x in custom-emoji-files + do (push + `(,(concat ":" + (file-name-base x) ":") + . (("name" . ,(file-name-base x)) + ("image" . ,(concat mastodon-custom-emojis-dir x)) + ("style" . "github"))) + mastodon-emojify-user-emojis)) (reverse mastodon-emojify-user-emojis))) -(defun mastodon-toot--enable-custom-emoji () +(defun mastodon-toot-enable-custom-emoji () "Add `mastodon-instance-url's custom emoji to `emojify'. Custom emoji must first be downloaded with -`mastodon-toot--download-custom-emoji'. Custom emoji are appended +`mastodon-toot-download-custom-emoji'. Custom emoji are appended to `emojify-user-emojis', and the emoji data is updated." (interactive) (unless (file-exists-p (mastodon-toot--emoji-dir)) (when (y-or-n-p "Looks like you haven't downloaded your instance's custom emoji yet. Download now? ") - (mastodon-toot--download-custom-emoji))) + (mastodon-toot-download-custom-emoji))) (let ((masto-emojis (mastodon-toot--collect-custom-emoji))) (unless (cl-find (car masto-emojis) emojify-user-emojis @@ -901,10 +907,10 @@ to `emojify-user-emojis', and the emoji data is updated." ;;; SEND TOOT FUNCTION -(defun mastodon-toot--send () +(defun mastodon-toot-send () "POST contents of new-toot buffer to fediverse instance and kill buffer. If media items have been attached and uploaded with -`mastodon-toot--attach-media', they are attached to the toot. +`mastodon-toot-attach-media', they are attached to the toot. If `mastodon-toot--edit-item-id' is non-nil, PUT contents to instance to edit a toot." (interactive) @@ -970,7 +976,7 @@ instance to edit a toot." (message "Toot %s!" (if scheduled "scheduled" "toot")) ;; cancel scheduled toot if we were editing it: (when scheduled-id - (mastodon-views--cancel-scheduled-toot + (mastodon-views-cancel-scheduled-toot scheduled-id :no-confirm)) ;; window config: (mastodon-toot--restore-previous-window-config prev-window-config) @@ -988,7 +994,7 @@ instance to edit a toot." ;;; EDITING TOOTS: -(defun mastodon-toot--edit-toot-at-point () +(defun mastodon-toot-edit-toot-at-point () "Edit the user's toot at point." (interactive) (mastodon-toot--with-toot-item @@ -1006,7 +1012,8 @@ instance to edit a toot." ;; adopt reply-to-id, visibility, CW, language, and media: (mastodon-toot--set-toot-properties .in_reply_to_id .visibility source-cw .language nil nil - .media_attachments .poll) + ;; maintain media order: + (reverse .media_attachments) .poll) (setq mastodon-toot--edit-item-id id)))))))) (defun mastodon-toot--get-toot-source (id) @@ -1019,7 +1026,7 @@ instance to edit a toot." (let* ((url (mastodon-http--api (format "statuses/%s/history" id)))) (mastodon-http--get-json url))) -(defun mastodon-toot--view-toot-edits () +(defun mastodon-toot-view-toot-edits () "View editing history of the toot at point in a popup buffer." (interactive) (let ((id (mastodon-tl--property 'base-item-id)) @@ -1033,7 +1040,7 @@ instance to edit a toot." do (insert (propertize (if (= count 1) (format "%s [original]:\n" count) (format "%s:\n" count)) - 'face 'font-lock-comment-face) + 'face 'mastodon-toot-docs-face) (mastodon-toot--insert-toot-iter x) "\n")) (goto-char (point-min)) @@ -1042,7 +1049,7 @@ instance to edit a toot." (format "Edits to toot by %s:" (alist-get 'username (alist-get 'account (car history)))) - 'face 'font-lock-comment-face)) + 'face 'mastodon-toot-docs-face)) (mastodon-tl--set-buffer-spec (buffer-name (current-buffer)) (format "statuses/%s/history" id) nil))))) @@ -1204,7 +1211,7 @@ arg, a candidate." ;;; REPLY -(defun mastodon-toot--reply () +(defun mastodon-toot-reply () "Reply to toot at `point'. Customize `mastodon-toot-display-orig-in-reply-buffer' to display text of the toot being replied to in the compose buffer. @@ -1240,14 +1247,14 @@ prefixed by >." ;;; COMPOSE TOOT SETTINGS -(defun mastodon-toot--set-content-warning () +(defun mastodon-toot-set-content-warning () "Set a content warning for the current toot." (interactive) (setq mastodon-toot--content-warning (read-string "Warning: " mastodon-toot--content-warning)) (mastodon-toot--update-status-fields)) -(defun mastodon-toot--toggle-nsfw () +(defun mastodon-toot-toggle-nsfw () "Toggle `mastodon-toot--content-nsfw'." (interactive) (setq mastodon-toot--content-nsfw @@ -1255,7 +1262,7 @@ prefixed by >." (message "NSFW flag is now %s" (if mastodon-toot--content-nsfw "on" "off")) (mastodon-toot--update-status-fields)) -(defun mastodon-toot--change-visibility (&optional arg) +(defun mastodon-toot-change-visibility (&optional arg) "Change the current visibility to the next valid value. With prefix ARG, read a visibility type in the minibuffer." (interactive "P") @@ -1275,7 +1282,7 @@ With prefix ARG, read a visibility type in the minibuffer." "public")))) (mastodon-toot--update-status-fields))) -(defun mastodon-toot--set-toot-language () +(defun mastodon-toot-set-toot-language () "Prompt for a language and set `mastodon-toot--language'. Return its two letter ISO 639 1 code." (interactive) @@ -1289,7 +1296,7 @@ Return its two letter ISO 639 1 code." ;;; ATTACHMENTS -(defun mastodon-toot--clear-all-attachments () +(defun mastodon-toot-clear-all-attachments () "Remove all attachments from a toot draft." (interactive) (setq mastodon-toot--media-attachments nil) @@ -1310,7 +1317,7 @@ If that fails, return 4 as a fallback" (alist-get 'max_media_attachments config)) 4)) ; mastodon default as fallback -(defun mastodon-toot--attach-media (file description) +(defun mastodon-toot-attach-media (file description) "Prompt for an attachment FILE with DESCRIPTION. A preview is displayed in the new toot buffer, and the file is uploaded asynchronously using `mastodon-toot--upload-attached-media'. @@ -1347,7 +1354,7 @@ File is actually attached to the toot upon posting." (rassoc desc x)) mastodon-toot--media-attachments))) -(defun mastodon-toot--edit-media-description () +(defun mastodon-toot-edit-media-description () "Prompt for an attachment, and update its description." (interactive) (let* ((descs (mastodon-toot--attachment-descriptions)) @@ -1431,7 +1438,7 @@ MAX is the maximum number set by their instance." (user-error "You need to choose a number between 2 and %s" max) number))) -(defun mastodon-toot--create-poll () +(defun mastodon-toot-create-poll () "Prompt for new poll options and return as a list." (interactive) (if mastodon-toot-poll-use-transient @@ -1494,7 +1501,7 @@ Return a cons of a human readable string, and a seconds-from-now string." ("14 days" . ,(number-to-string (* 60 60 24 14))) ("30 days" . ,(number-to-string (* 60 60 24 30))))) -(defun mastodon-toot--clear-poll (&optional transient) +(defun mastodon-toot-clear-poll (&optional transient) "Remove poll from toot compose buffer. Sets `mastodon-toot-poll' to nil. If TRANSIENT, we are called from a transient, so nil @@ -1540,7 +1547,7 @@ If TRANSIENT, we are called from a transient, so nil ;;; SCHEDULE -(defun mastodon-toot--schedule-toot (&optional reschedule) +(defun mastodon-toot-schedule-toot (&optional reschedule) "Read a date (+ time) in the minibuffer and schedule the current toot. With RESCHEDULE, reschedule the scheduled toot at point without editing." ;; original idea by christian tietze, thanks! @@ -1577,7 +1584,7 @@ With RESCHEDULE, reschedule the scheduled toot at point without editing." response (lambda (_) ;; reschedule means we are in scheduled toots view: - (mastodon-views--view-scheduled-toots) + (mastodon-views-view-scheduled-toots) (message "Toot rescheduled for %s." msg-str))))))))) (defun mastodon-toot--iso-to-human (ts) @@ -1610,9 +1617,9 @@ With RESCHEDULE, reschedule the scheduled toot at point without editing." (defun mastodon-toot--format-kbind-command (cmd) "Format CMD to be more readable. -e.g. `mastodon-toot--send' -> Send." +e.g. `mastodon-toot-send' -> Send." (let* ((str (symbol-name cmd)) - (re "--\\(.*\\)$") + (re "mastodon-toot-\\(.*\\)$") (str2 (save-match-data (string-match re str) (match-string 1 str)))) @@ -1627,7 +1634,7 @@ e.g. `mastodon-toot--send' -> Send." (substitute-command-keys (format (concat (mastodon-toot--comment " ") - "%s" + "%-10s" (mastodon-toot--comment " - %s")) key command)))) @@ -1893,7 +1900,7 @@ Added to `after-change-functions' in new toot buffers." (unless (string-empty-p text) (setq mastodon-toot-current-toot-text text)))) -(defun mastodon-toot--open-draft-toot () +(defun mastodon-toot-open-draft-toot () "Prompt for a draft and compose a toot with it." (interactive) (if mastodon-toot-draft-toots-list @@ -1917,7 +1924,7 @@ Added to `after-change-functions' in new toot buffers." (mastodon-toot--compose-buffer)) (message "No drafts available."))) -(defun mastodon-toot--delete-draft-toot () +(defun mastodon-toot-delete-draft-toot () "Prompt for a draft toot and delete it." (interactive) (if (not mastodon-toot-draft-toots-list) @@ -1929,7 +1936,7 @@ Added to `after-change-functions' in new toot buffers." (cl-delete draft mastodon-toot-draft-toots-list :test #'equal)) (message "Draft deleted!")))) -(defun mastodon-toot--delete-all-drafts () +(defun mastodon-toot-delete-all-drafts () "Delete all drafts." (interactive) (setq mastodon-toot-draft-toots-list nil) @@ -1979,7 +1986,7 @@ Added to `after-change-functions'." (save-match-data (let* ((fill-column 67)) (goto-char (point-min)) - (when-let ((prop (text-property-search-forward 'toot-reply))) + (when-let* ((prop (text-property-search-forward 'toot-reply))) (fill-region (prop-match-beginning prop) (point))))))) @@ -2033,7 +2040,7 @@ EDIT means we are editing an existing toot, not composing a new one." (mastodon-toot--display-docs-and-status-fields reply-text) (mastodon-toot--fill-reply-in-compose)) (mastodon-toot--display-docs-and-status-fields)) - ;; `reply-to-user' (alone) is also used by `mastodon-tl--dm-user', so + ;; `reply-to-user' (alone) is also used by `mastodon-tl-dm-user', so ;; perhaps we should not always call --setup-as-reply, or make its ;; workings conditional on reply-to-id. currently it only checks for ;; reply-to-user. diff --git a/elpa/mastodon-20241223.1040/mastodon-transient.el b/elpa/mastodon-20250330.1519/mastodon-transient.el similarity index 83% rename from elpa/mastodon-20241223.1040/mastodon-transient.el rename to elpa/mastodon-20250330.1519/mastodon-transient.el index 3e8ba5f8..3c8b369e 100644 --- a/elpa/mastodon-20241223.1040/mastodon-transient.el +++ b/elpa/mastodon-20250330.1519/mastodon-transient.el @@ -24,8 +24,8 @@ ;;; Code: -(require 'tp) (require 'transient) +(require 'tp) (defvar mastodon-active-user) (defvar mastodon-toot-visibility-settings-list) @@ -37,14 +37,15 @@ (autoload 'mastodon-http--api "mastodon-http") (autoload 'mastodon-http--triage "mastodon-http") (autoload 'mastodon-http--patch "mastodon-http") -(autoload 'mastodon-profile--update-user-profile-note "mastodon-profile") +(autoload 'mastodon-profile-update-user-profile-note "mastodon-profile") (autoload 'mastodon-toot--fetch-max-poll-options "mastodon-toot") (autoload 'mastodon-toot--fetch-max-poll-option-chars "mastodon-toot") (autoload 'mastodon-instance-data "mastodon") (autoload 'mastodon-toot--update-status-fields "mastodon-toot") (autoload 'mastodon-toot--read-poll-expiry "mastodon-toot") (autoload 'mastodon-toot--poll-expiry-options-alist "mastodon-toot") -(autoload 'mastodon-toot--clear-poll "mastodon-toot") +(autoload 'mastodon-toot-clear-poll "mastodon-toot") +(autoload 'mastodon-notifications-get-policy "mastodon-notifications") ;;; UTILS @@ -131,7 +132,7 @@ the format fields.X.keyname." (resp (mastodon-http--patch url strs))) ;; :json fails (mastodon-http--triage resp - (lambda (_) + (lambda (_resp) (message "Settings updated!\n%s" (pp-to-string strs)))))) (transient-define-prefix mastodon-user-settings () @@ -155,19 +156,17 @@ the format fields.X.keyname." ("d" "discoverable" "discoverable" :alist-key discoverable :class tp-bool) ("c" "hide follower/following lists" "source.hide_collections" :alist-key source.hide_collections :class tp-bool) - ("i" "indexable" "source.indexable" :alist-key source.indexable :class tp-bool) - ] + ("i" "indexable" "source.indexable" :alist-key source.indexable :class tp-bool)] ["Tooting options" ("p" "default privacy" "source.privacy" :alist-key source.privacy :class tp-option :choices (lambda () mastodon-toot-visibility-settings-list)) ("s" "mark sensitive" "source.sensitive" :alist-key source.sensitive :class tp-bool) ("g" "default language" "source.language" :alist-key source.language :class tp-option - :choices (lambda () mastodon-iso-639-regional)) - ] + :choices (lambda () mastodon-iso-639-regional))] ["Update" ("C-c C-c" "Save settings" mastodon-user-settings-update) - ("C-c C-k" :info "Revert all changes")] + ("C-x C-k" :info "Revert all changes")] (interactive) (if (or (not (boundp 'mastodon-active-user)) (not mastodon-active-user)) @@ -178,7 +177,7 @@ the format fields.X.keyname." "Update current user profile note." :transient 'transient--do-exit (interactive) - (mastodon-profile--update-user-profile-note)) + (mastodon-profile-update-user-profile-note)) (transient-define-suffix mastodon-profile-fields-update (args) "Update current user profile fields." @@ -192,7 +191,7 @@ the format fields.X.keyname." (url (mastodon-http--api endpoint)) (resp (mastodon-http--patch url arrays))) ; :json))) (mastodon-http--triage - resp (lambda (_) (message "Fields updated!"))))) + resp (lambda (_resp) (message "Fields updated!"))))) (defun mastodon-transient-fetch-fields () "Fetch profile fields (metadata)." @@ -217,7 +216,7 @@ the format fields.X.keyname." ("4 v" "" "fields.4.value" :alist-key fields.4.value :class mastodon-transient-field)]] ["Update" ("C-c C-c" "Save settings" mastodon-profile-fields-update) - ("C-c C-k" :info "Revert all changes")] + ("C-x C-k" :info "Revert all changes")] (interactive) (if (not mastodon-active-user) (user-error "User not set") @@ -233,7 +232,7 @@ the format fields.X.keyname." (let ((instance (mastodon-instance-data))) (mastodon-toot--fetch-max-poll-option-chars instance))) -(transient-define-suffix mastodon-transient--choice-add () +(transient-define-suffix mastodon-transient-choice-add () "Add another poll choice if possible. Do not add more than 9 choices. Do not add more than the server's maximum setting." @@ -295,7 +294,7 @@ Do not add more than the server's maximum setting." ("4" "" "4" :alist-key four :class mastodon-transient-poll-choice)] ;; TODO: display the max number of options or add options cmd ["Update" - ("C-c C-s" "Add another poll choice" mastodon-transient--choice-add + ("C-c C-s" "Add another poll choice" mastodon-transient-choice-add :if (lambda () (< 4 (mastodon-transient-max-poll-opts)))) ("C-c C-c" "Save and done" mastodon-create-poll-done) ("C-x C-k" :info "Revert all") @@ -309,7 +308,7 @@ Do not add more than the server's maximum setting." "Clear current poll data." :transient 'transient--do-stay (interactive) - (mastodon-toot--clear-poll :transient) + (mastodon-toot-clear-poll :transient) (transient-reset)) (transient-define-suffix mastodon-create-poll-done (args) @@ -345,8 +344,72 @@ Do not add more than the server's maximum setting." (tp-bools-to-strs args))) (mastodon-toot--update-status-fields)))) +(defvar mastodon-notifications-policy-vals) +(declare-function mastodon-notifications-get-policy "mastodon-notifications") +(declare-function mastodon-notifications--update-policy "mastodon-notifications") + +(transient-define-prefix mastodon-notifications-policy () + "A transient to set notifications policy options." + ;; https://docs.joinmastodon.org/methods/notifications/#get-policy + :value (lambda () (tp-return-data #'mastodon-notifications-get-policy)) + ["Notification policy options" + ("f" "people you don't follow" "for_not_following" + :alist-key for_not_following :class mastodon-transient-policy) + ("F" "people not following you" "for_not_followers" + :alist-key for_not_followers :class mastodon-transient-policy) + ("n" "New accounts" "for_new_accounts" + :alist-key for_new_accounts :class mastodon-transient-policy) + ("p" "Unsolicited private mentions" "for_private_mentions" + :alist-key for_private_mentions :class mastodon-transient-policy) + ("l" "Moderated accounts" "for_limited_accounts" + :alist-key for_limited_accounts :class mastodon-transient-policy) + (:info "") + (:info "\"accept\" = receive notifications") + (:info "\"filter\" = mark as filtered") + (:info "\"drop\" = do not receive any notifications")] + ["Notification requests" + (:info #'mastodon-notifications-requests-count) + (:info #'mastodon-notifications-filtered-count)] + ["Update" + ("C-c C-c" "Save settings" mastodon-notifications-policy-update) + ("C-x C-k" :info "Revert all changes")]) + +(defun mastodon-notifications-requests-count () + "Format a string for pending requests." + (let ((val (oref transient--prefix value))) + (format "Pending requests: %d" + (or (map-nested-elt + val + '(summary pending_requests_count)) + 0)))) + +(defun mastodon-notifications-filtered-count () + "Format a string for pending notifications." + (let ((val (oref transient--prefix value))) + (format "Pending notifications: %d" + (or (map-nested-elt + val + '(summary pending_notifications_count)) + 0)))) + +(transient-define-suffix mastodon-notifications-policy-update (args) + "Send updated notification policy settings." + :transient 'transient--do-exit + ;; TODO: + (interactive (list (transient-args 'mastodon-notifications-policy))) + (let* ((parsed (tp-parse-args-for-send args)) + (resp (mastodon-notifications--update-policy parsed))) + (mastodon-http--triage + resp + (lambda (_resp) + (message "Settings updated!\n%s" (pp-to-string parsed)))))) + ;;; CLASSES +(defclass mastodon-transient-policy (tp-cycle) + ((choices :initarg :choices :initform 'mastodon-notifications-policy-vals)) + "An option class for mastodon notification policy options.") + (defclass mastodon-transient-field (tp-option-str) ((always-read :initarg :always-read :initform t)) "An infix option class for our options. diff --git a/elpa/mastodon-20241223.1040/mastodon-views.el b/elpa/mastodon-20250330.1519/mastodon-views.el similarity index 85% rename from elpa/mastodon-20241223.1040/mastodon-views.el rename to elpa/mastodon-20250330.1519/mastodon-views.el index 8d356fb1..ab4ccaac 100644 --- a/elpa/mastodon-20241223.1040/mastodon-views.el +++ b/elpa/mastodon-20250330.1519/mastodon-views.el @@ -54,11 +54,11 @@ (autoload 'mastodon-tl--do-if-item "mastodon-tl") (autoload 'mastodon-tl--set-buffer-spec "mastodon-tl") (autoload 'mastodon-tl--render-text "mastodon-tl") -(autoload 'mastodon-notifications--follow-request-accept "mastodon-notifications") -(autoload 'mastodon-notifications--follow-request-reject "mastodon-notifications") +(autoload 'mastodon-notifications-follow-request-accept "mastodon-notifications") +(autoload 'mastodon-notifications-follow-request-reject "mastodon-notifications") (autoload 'mastodon-auth--get-account-id "mastodon-auth") (autoload 'mastodon-toot--iso-to-human "mastodon-toot") -(autoload 'mastodon-toot--schedule-toot "mastodon-toot") +(autoload 'mastodon-toot-schedule-toot "mastodon-toot") (autoload 'mastodon-toot--compose-buffer "mastodon-toot") (autoload 'mastodon-toot--set-toot-properties "mastodon-toot") (autoload 'mastodon-search--propertize-user "mastodon-search") @@ -89,55 +89,55 @@ (defvar mastodon-views--view-filters-keymap (let ((map (make-sparse-keymap))) (set-keymap-parent map mastodon-views-map) - (define-key map (kbd "d") #'mastodon-views--delete-filter) - (define-key map (kbd "c") #'mastodon-views--create-filter) - (define-key map (kbd "g") #'mastodon-views--view-filters) - (define-key map (kbd "u") #'mastodon-views--update-filter) - (define-key map (kbd "k") #'mastodon-views--delete-filter) - (define-key map (kbd "a") #'mastodon-views--add-filter-kw) - (define-key map (kbd "r") #'mastodon-views--remove-filter-kw) - (define-key map (kbd "U") #'mastodon-views--update-filter-kw) + (define-key map (kbd "d") #'mastodon-views-delete-filter) + (define-key map (kbd "c") #'mastodon-views-create-filter) + (define-key map (kbd "g") #'mastodon-views-view-filters) + (define-key map (kbd "u") #'mastodon-views-update-filter) + (define-key map (kbd "k") #'mastodon-views-delete-filter) + (define-key map (kbd "a") #'mastodon-views-add-filter-kw) + (define-key map (kbd "r") #'mastodon-views-remove-filter-kw) + (define-key map (kbd "U") #'mastodon-views-update-filter-kw) map) "Keymap for viewing filters.") (defvar mastodon-views--follow-suggestions-map (let ((map (make-sparse-keymap))) (set-keymap-parent map mastodon-views-map) - (define-key map (kbd "g") #'mastodon-views--view-follow-suggestions) + (define-key map (kbd "g") #'mastodon-views-view-follow-suggestions) map) "Keymap for viewing follow suggestions.") (defvar mastodon-views--view-lists-keymap (let ((map (make-sparse-keymap))) (set-keymap-parent map mastodon-views-map) - (define-key map (kbd "D") #'mastodon-views--delete-list) - (define-key map (kbd "C") #'mastodon-views--create-list) - (define-key map (kbd "A") #'mastodon-views--add-account-to-list) - (define-key map (kbd "R") #'mastodon-views--remove-account-from-list) - (define-key map (kbd "E") #'mastodon-views--edit-list) - (define-key map (kbd "g") #'mastodon-views--view-lists) + (define-key map (kbd "D") #'mastodon-views-delete-list) + (define-key map (kbd "C") #'mastodon-views-create-list) + (define-key map (kbd "A") #'mastodon-views-add-account-to-list) + (define-key map (kbd "R") #'mastodon-views-remove-account-from-list) + (define-key map (kbd "E") #'mastodon-views-edit-list) + (define-key map (kbd "g") #'mastodon-views-view-lists) map) "Keymap for viewing lists.") (defvar mastodon-views--list-name-keymap (let ((map (make-sparse-keymap))) - (define-key map (kbd "RET") #'mastodon-views--view-timeline-list-at-point) - (define-key map (kbd "d") #'mastodon-views--delete-list-at-point) - (define-key map (kbd "a") #'mastodon-views--add-account-to-list-at-point) - (define-key map (kbd "r") #'mastodon-views--remove-account-from-list-at-point) - (define-key map (kbd "e") #'mastodon-views--edit-list-at-point) - (define-key map (kbd "g") #'mastodon-views--view-lists) + (define-key map (kbd "RET") #'mastodon-views-view-timeline-list-at-point) + (define-key map (kbd "d") #'mastodon-views-delete-list-at-point) + (define-key map (kbd "a") #'mastodon-views-add-account-to-list-at-point) + (define-key map (kbd "r") #'mastodon-views-remove-account-from-list-at-point) + (define-key map (kbd "e") #'mastodon-views-edit-list-at-point) + (define-key map (kbd "g") #'mastodon-views-view-lists) map) "Keymap for when point is on list name.") (defvar mastodon-views--scheduled-map (let ((map (make-sparse-keymap))) (set-keymap-parent map mastodon-views-map) - (define-key map (kbd "r") #'mastodon-views--reschedule-toot) - (define-key map (kbd "c") #'mastodon-views--cancel-scheduled-toot) - (define-key map (kbd "e") #'mastodon-views--edit-scheduled-as-new) - (define-key map (kbd "RET") #'mastodon-views--edit-scheduled-as-new) - (define-key map (kbd "g") #'mastodon-views--view-scheduled-toots) + (define-key map (kbd "r") #'mastodon-views-reschedule-toot) + (define-key map (kbd "c") #'mastodon-views-cancel-scheduled-toot) + (define-key map (kbd "e") #'mastodon-views-edit-scheduled-as-new) + (define-key map (kbd "RET") #'mastodon-views-edit-scheduled-as-new) + (define-key map (kbd "g") #'mastodon-views-view-scheduled-toots) map) "Keymap for when point is on a scheduled toot.") @@ -147,9 +147,9 @@ ;; make reject binding match the binding in notifs view ;; 'r' is then reserved for replying, even tho it is not avail ;; in foll-reqs view - (define-key map (kbd "j") #'mastodon-notifications--follow-request-reject) - (define-key map (kbd "a") #'mastodon-notifications--follow-request-accept) - (define-key map (kbd "g") #'mastodon-views--view-follow-requests) + (define-key map (kbd "j") #'mastodon-notifications-follow-request-reject) + (define-key map (kbd "a") #'mastodon-notifications-follow-request-accept) + (define-key map (kbd "g") #'mastodon-views-view-follow-requests) map) "Keymap for viewing follow requests.") @@ -171,7 +171,7 @@ provides the JSON data." (if (seq-empty-p data) (insert (propertize (format "Looks like you have no %s for now." view-name) - 'face 'font-lock-comment-face + 'face 'mastodon-toot-docs-face 'byline t 'item-type 'no-item ; for nav 'item-id "0")) ; so point can move here when no item @@ -179,13 +179,13 @@ provides the JSON data." (goto-char (point-min))) ;; (when data ;; FIXME: this seems to trigger a new request, but ideally would run. - ;; (mastodon-tl--goto-next-item)) + ;; (mastodon-tl-goto-next-item)) ) ;;; LISTS -(defun mastodon-views--view-lists () +(defun mastodon-views-view-lists () "Show the user's lists in a new buffer." (interactive) (mastodon-tl--init-sync "lists" "lists" @@ -207,11 +207,11 @@ provides the JSON data." (defun mastodon-views--print-list-set (lists) "Print each account plus a separator for each list in LISTS." - (mapc (lambda (x) - (mastodon-views--print-list-accounts x) - (insert (propertize (concat " " mastodon-tl--horiz-bar "\n\n") - 'face 'success))) - lists)) + (cl-loop for x in lists + do (progn + (mastodon-views--print-list-accounts x) + (insert (propertize (concat " " mastodon-tl--horiz-bar "\n\n") + 'face 'success))))) (defun mastodon-views--print-list-accounts (list) "Insert the accounts in list named LIST, an alist." @@ -232,7 +232,7 @@ a: add account to this list, r: remove account from this list" (propertize (format " [replies: %s, exclusive %s]" .replies_policy (when (eq t .exclusive) "true")) - 'face 'font-lock-comment-face) + 'face 'mastodon-toot-docs-face) (propertize "\n\n" 'list t 'keymap mastodon-views--list-name-keymap @@ -274,13 +274,13 @@ a: add account to this list, r: remove account from this list" (response (mastodon-http--get-json url))) (alist-get 'title response))) -(defun mastodon-views--edit-list-at-point () +(defun mastodon-views-edit-list-at-point () "Edit list at point." (interactive) (let ((id (mastodon-tl--property 'list-id :no-move))) - (mastodon-views--edit-list id))) + (mastodon-views-edit-list id))) -(defun mastodon-views--edit-list (&optional id) +(defun mastodon-views-edit-list (&optional id) "Prompt for a list and edit the name and replies policy. If ID is provided, use that list." (interactive) @@ -308,15 +308,15 @@ If ID is provided, use that list." (name-new (alist-get 'title json))) (message "list %s edited to %s!" name-old name-new))) (when (mastodon-tl--buffer-type-eq 'lists) - (mastodon-views--view-lists)))))) + (mastodon-views-view-lists)))))) -(defun mastodon-views--view-timeline-list-at-point () +(defun mastodon-views-view-timeline-list-at-point () "View timeline of list at point." (interactive) (let ((list-id (mastodon-tl--property 'list-id :no-move))) - (mastodon-views--view-list-timeline list-id))) + (mastodon-views-view-list-timeline list-id))) -(defun mastodon-views--view-list-timeline (&optional id) +(defun mastodon-views-view-list-timeline (&optional id) "Prompt for a list and view its timeline. If ID is provided, use that list." (interactive) @@ -330,30 +330,32 @@ If ID is provided, use that list." 'mastodon-tl--timeline nil `(("limit" . ,mastodon-tl--timeline-posts-count))))) -(defun mastodon-views--create-list () +(defun mastodon-views-create-list () "Create a new list. Prompt for name and replies policy." (interactive) (let* ((title (read-string "New list name: ")) - (replies-policy (completing-read "Replies policy: " ; give this a proper name - '("followed" "list" "none") - nil t nil nil "list")) ; default + (replies-policy + (completing-read "Replies policy: " ; give this a proper name + '("followed" "list" "none") + nil t nil nil "list")) ; default (exclusive (when (y-or-n-p "Exclude items from home timeline? ") "true")) - (response (mastodon-http--post (mastodon-http--api "lists") - `(("title" . ,title) - ("replies_policy" . ,replies-policy) - ("exclusive" . ,exclusive))))) + (response (mastodon-http--post + (mastodon-http--api "lists") + `(("title" . ,title) + ("replies_policy" . ,replies-policy) + ("exclusive" . ,exclusive))))) (mastodon-views--list-action-triage response "list %s created!" title))) -(defun mastodon-views--delete-list-at-point () +(defun mastodon-views-delete-list-at-point () "Delete list at point." (interactive) (let ((id (mastodon-tl--property 'list-id :no-move))) - (mastodon-views--delete-list id))) + (mastodon-views-delete-list id))) -(defun mastodon-views--delete-list (&optional id) +(defun mastodon-views-delete-list (&optional id) "Prompt for a list and delete it. If ID is provided, delete that list." (interactive) @@ -374,13 +376,13 @@ If ID is provided, delete that list." (url (mastodon-http--api (format "accounts/%s/following" id)))) (mastodon-http--get-json url '(("limit" . "80"))))) ; max 80 accounts -(defun mastodon-views--add-account-to-list-at-point () +(defun mastodon-views-add-account-to-list-at-point () "Prompt for account and add to list at point." (interactive) (let ((id (mastodon-tl--property 'list-id :no-move))) - (mastodon-views--add-account-to-list id))) + (mastodon-views-add-account-to-list id))) -(defun mastodon-views--add-account-to-list (&optional id account-id handle) +(defun mastodon-views-add-account-to-list (&optional id account-id handle) "Prompt for a list and for an account, add account to list. If ID is provided, use that list. If ACCOUNT-ID and HANDLE are provided use them rather than prompting." @@ -406,22 +408,22 @@ If ACCOUNT-ID and HANDLE are provided use them rather than prompting." (mastodon-views--list-action-triage response "%s added to list %s!" account list-name))) -(defun mastodon-views--add-toot-account-at-point-to-list () +(defun mastodon-views-add-toot-account-at-point-to-list () "Prompt for a list, and add the account of the toot at point to it." (interactive) (let* ((toot (mastodon-tl--property 'item-json)) (account (mastodon-tl--field 'account toot)) (account-id (mastodon-tl--field 'id account)) (handle (mastodon-tl--field 'acct account))) - (mastodon-views--add-account-to-list nil account-id handle))) + (mastodon-views-add-account-to-list nil account-id handle))) -(defun mastodon-views--remove-account-from-list-at-point () +(defun mastodon-views-remove-account-from-list-at-point () "Prompt for account and remove from list at point." (interactive) (let ((id (mastodon-tl--property 'list-id :no-move))) - (mastodon-views--remove-account-from-list id))) + (mastodon-views-remove-account-from-list id))) -(defun mastodon-views--remove-account-from-list (&optional id) +(defun mastodon-views-remove-account-from-list (&optional id) "Prompt for a list, select an account and remove from list. If ID is provided, use that list." (interactive) @@ -445,7 +447,7 @@ If ID is provided, use that list." (mastodon-http--triage response (lambda (_) (when (mastodon-tl--buffer-type-eq 'lists) - (mastodon-views--view-lists)) + (mastodon-views-view-lists)) (apply #'message args)))) (defun mastodon-views--accounts-in-list (list-id) @@ -464,7 +466,7 @@ JSON is the data returned by the server." #'mastodon-views--insert-users-propertized-note json)) -(defun mastodon-views--view-follow-requests () +(defun mastodon-views-view-follow-requests () "Open a new buffer displaying the user's follow requests." (interactive) (mastodon-tl--init-sync "follow-requests" @@ -483,7 +485,8 @@ JSON is the data returned by the server." ;;; SCHEDULED TOOTS -(defun mastodon-views--view-scheduled-toots () +;;;###autoload +(defun mastodon-views-view-scheduled-toots () "Show the user's scheduled toots in a new buffer." (interactive) (mastodon-tl--init-sync "scheduled-toots" @@ -516,7 +519,7 @@ JSON is the data returned by the server." (mastodon-toot--iso-to-human .scheduled_at)) 'byline t ; so we nav here 'item-type 'scheduled ; so we nav here - 'face 'font-lock-comment-face + 'face 'mastodon-toot-docs-face 'keymap mastodon-views--scheduled-map 'item-json toot 'id .id) @@ -531,13 +534,13 @@ If ID, just return that toot." (url (mastodon-http--api endpoint))) (mastodon-http--get-json url))) -(defun mastodon-views--reschedule-toot () +(defun mastodon-views-reschedule-toot () "Reschedule the scheduled toot at point." (interactive) (mastodon-tl--do-if-item - (mastodon-toot--schedule-toot :reschedule))) + (mastodon-toot-schedule-toot :reschedule))) -(defun mastodon-views--copy-scheduled-toot-text () +(defun mastodon-views-copy-scheduled-toot-text () "Copy the text of the scheduled toot at point." (interactive) (let* ((toot (mastodon-tl--property 'toot :no-move)) @@ -545,7 +548,7 @@ If ID, just return that toot." (text (alist-get 'text params))) (kill-new text))) -(defun mastodon-views--cancel-scheduled-toot (&optional id no-confirm) +(defun mastodon-views-cancel-scheduled-toot (&optional id no-confirm) "Cancel the scheduled toot at point. ID is that of the scheduled toot to cancel. NO-CONFIRM means there is no ask or message, there is only do." @@ -558,11 +561,11 @@ NO-CONFIRM means there is no ask or message, there is only do." (response (mastodon-http--delete url))) (mastodon-http--triage response (lambda (_) - (mastodon-views--view-scheduled-toots) + (mastodon-views-view-scheduled-toots) (unless no-confirm (message "Toot cancelled!")))))))) -(defun mastodon-views--edit-scheduled-as-new () +(defun mastodon-views-edit-scheduled-as-new () "Edit scheduled status as new toot." (interactive) (mastodon-tl--do-if-item @@ -582,7 +585,8 @@ NO-CONFIRM means there is no ask or message, there is only do." ;;; FILTERS -(defun mastodon-views--view-filters () +;;;###autoload +(defun mastodon-views-view-filters () "View the user's filters in a new buffer." (interactive) (mastodon-tl--init-sync "filters" "filters" @@ -611,19 +615,18 @@ JSON is the filters data." "-")) (whole-str "whole words only:")) (insert (concat "Keywords: | " whole-str "\n")) - (mapc (lambda (kw) - (let ((whole (if (eq :json-false (alist-get 'whole_word kw)) - "nil" - "t"))) - (insert - (propertize (concat - (format "\"%s\" | %s\n" - (alist-get 'keyword kw) whole)) - 'kw-id (alist-get 'id kw) - 'item-json kw - 'mastodon-tab-stop t - 'whole-word whole)))) - kws) + (cl-loop for kw in kws + do (let ((whole (if (eq :json-false (alist-get 'whole_word kw)) + "nil" + "t"))) + (insert + (propertize (concat + (format "\"%s\" | %s\n" + (alist-get 'keyword kw) whole)) + 'kw-id (alist-get 'id kw) + 'item-json kw + 'mastodon-tab-stop t + 'whole-word whole)))) ;; table display of kws: (table-capture beg (point) "|" "\n" nil (+ 2 (length whole-str))) (table-justify-column 'center) @@ -669,7 +672,7 @@ JSON is the filters data." (defvar mastodon-views--filter-types '("home" "notifications" "public" "thread" "profile")) -(defun mastodon-views--create-filter (&optional id title context type terms) +(defun mastodon-views-create-filter (&optional id title context type terms) "Create a filter for a word. Prompt for a context, must be a list containting at least one of \"home\", \"notifications\", \"public\", \"thread\". @@ -710,7 +713,7 @@ Optionally, provide ID, TITLE, CONTEXT, TYPE, and TERMS to update a filter." resp (message "Filter %s %s!" title (if id "updated" "created"))))) -(defun mastodon-views--update-filter () +(defun mastodon-views-update-filter () "Update filter at point." (interactive) (if (not (eq 'filter (mastodon-tl--property 'item-type))) @@ -727,9 +730,9 @@ Optionally, provide ID, TITLE, CONTEXT, TYPE, and TERMS to update a filter." '("warn" "hide") nil :match (alist-get 'type filter))) (terms (read-string "Terms to add (comma or space separated): "))) - (mastodon-views--create-filter id name contexts type terms)))) + (mastodon-views-create-filter id name contexts type terms)))) -(defun mastodon-views--delete-filter () +(defun mastodon-views-delete-filter () "Delete filter at point." (interactive) (let* ((id (mastodon-tl--property 'item-id :no-move)) @@ -750,7 +753,7 @@ Optionally, provide ID, TITLE, CONTEXT, TYPE, and TERMS to update a filter." (resp (mastodon-http--get-json url))) resp)) -(defun mastodon-views--update-filter-kw () +(defun mastodon-views-update-filter-kw () "Update filter keyword. Prmopt to change the term, and the whole words option. When t, whole words means only match whole words." @@ -779,10 +782,10 @@ When t, whole words means only match whole words." resp (lambda (_resp) (when (mastodon-tl--buffer-type-eq 'filters) - (mastodon-views--view-filters)) + (mastodon-views-view-filters)) (message msg-str)))) -(defun mastodon-views--add-filter-kw () +(defun mastodon-views-add-filter-kw () "Add a keyword to filter at point." (interactive) (if (not (eq 'filter (mastodon-tl--property 'item-type))) @@ -799,7 +802,7 @@ When t, whole words means only match whole words." (mastodon-views--filters-triage resp (format "Keyword %s added!" kw))))) -(defun mastodon-views--remove-filter-kw () +(defun mastodon-views-remove-filter-kw () "Remove keyword from filter at point." (interactive) (if (not (eq 'filter (mastodon-tl--property 'item-type))) @@ -817,7 +820,7 @@ When t, whole words means only match whole words." ;;; FOLLOW SUGGESTIONS ;; No pagination: max 80 results -(defun mastodon-views--view-follow-suggestions () +(defun mastodon-views-view-follow-suggestions () "Display a buffer of suggested accounts to follow." (interactive) (mastodon-tl--init-sync "follow-suggestions" @@ -846,21 +849,21 @@ JSON is the users list data." ;;; INSTANCES -(defun mastodon-views--view-own-instance (&optional brief) +(defun mastodon-views-view-own-instance (&optional brief) "View details of your own instance. BRIEF means show fewer details." (interactive) - (mastodon-views--view-instance-description :user brief)) + (mastodon-views-view-instance-description :user brief)) -(defun mastodon-views--view-own-instance-brief () +(defun mastodon-views-view-own-instance-brief () "View brief details of your own instance." (interactive) - (mastodon-views--view-instance-description :user :brief)) + (mastodon-views-view-instance-description :user :brief)) -(defun mastodon-views--view-instance-description-brief () +(defun mastodon-views-view-instance-description-brief () "View brief details of the instance the current post's author is on." (interactive) - (mastodon-views--view-instance-description nil :brief)) + (mastodon-views-view-instance-description nil :brief)) (defun mastodon-views--get-instance-url (url username &optional instance) "Return an instance base url from a user account URL. @@ -875,6 +878,10 @@ If INSTANCE is given, use that." ((string-suffix-p "profile/" (url-basepath url)) (string-remove-suffix "/profile/" (url-basepath url))) + ;; snac is https://instance.com/user + ((not (string-match-p "@" url)) + ;; cull trailing slash: + (string-trim-right (url-basepath url) "/")) ;; mastodon is https://instance.com/@user (t (string-remove-suffix (concat "/@" username) @@ -885,7 +892,7 @@ If INSTANCE is given, use that." (mastodon-http--get-json (mastodon-http--api "instance" "v2") nil nil :vector)) -(defun mastodon-views--view-instance-description +(defun mastodon-views-view-instance-description (&optional user brief instance misskey) "View the details of the instance the current post's author is on. USER means to show the instance details for the logged in user. @@ -930,15 +937,15 @@ MISSKEY means the instance is a Misskey or derived server." ;; if non-misskey attempt errors, try misskey instance: ;; akkoma i guess should not error here. (if (eq 'error (caar response)) - (mastodon-views--instance-desc-misskey) + (mastodon-views-instance-desc-misskey) (mastodon-views--instance-response-fun response brief instance)))))))) -(defun mastodon-views--instance-desc-misskey (&optional user brief instance) +(defun mastodon-views-instance-desc-misskey (&optional user brief instance) "Show instance description for a misskey/firefish server. USER, BRIEF, and INSTANCE are all for -`mastodon-views--view-instance-description', which see." +`mastodon-views-view-instance-description', which see." (interactive) - (mastodon-views--view-instance-description user brief instance :miskey)) + (mastodon-views-view-instance-description user brief instance :miskey)) (defun mastodon-views--instance-response-fun (response brief instance &optional misskey) diff --git a/elpa/mastodon-20241223.1040/mastodon-widget.el b/elpa/mastodon-20250330.1519/mastodon-widget.el similarity index 100% rename from elpa/mastodon-20241223.1040/mastodon-widget.el rename to elpa/mastodon-20250330.1519/mastodon-widget.el diff --git a/elpa/mastodon-20241223.1040/mastodon.el b/elpa/mastodon-20250330.1519/mastodon.el similarity index 69% rename from elpa/mastodon-20241223.1040/mastodon.el rename to elpa/mastodon-20250330.1519/mastodon.el index 6f911f15..767c48ac 100644 --- a/elpa/mastodon-20241223.1040/mastodon.el +++ b/elpa/mastodon-20250330.1519/mastodon.el @@ -6,9 +6,9 @@ ;; Author: Johnson Denen ;; Marty Hiatt ;; Maintainer: Marty Hiatt -;; Package-Version: 20241223.1040 -;; Package-Revision: e2443f1cd425 -;; Package-Requires: ((emacs "28.1") (request "0.3.0") (persist "0.4") (tp "0.6")) +;; Package-Version: 20250330.1519 +;; Package-Revision: 163ba2b0b89a +;; Package-Requires: ((emacs "28.1") (persist "0.4") (tp "0.7")) ;; Homepage: https://codeberg.org/martianh/mastodon.el ;; This file is not part of GNU Emacs. @@ -47,6 +47,7 @@ (require 'mastodon-toot) (require 'mastodon-search) (require 'mastodon-transient) +(require 'mastodon-tl) (declare-function discover-add-context-menu "discover") (declare-function emojify-mode "emojify") @@ -55,58 +56,62 @@ (autoload 'mastodon-auth--get-account-name "mastodon-auth") (autoload 'mastodon-auth--user-acct "mastodon-auth") (autoload 'mastodon-discover "mastodon-discover") -(autoload 'mastodon-notifications--follow-request-accept "mastodon-notifications") -(autoload 'mastodon-notifications--follow-request-reject "mastodon-notifications") -(autoload 'mastodon-notifications--get-mentions "mastodon-notifications") +(autoload 'mastodon-notifications-follow-request-accept "mastodon-notifications") +(autoload 'mastodon-notifications-follow-request-reject "mastodon-notifications") +(autoload 'mastodon-notifications-get-mentions "mastodon-notifications") (autoload 'mastodon-notifications--timeline "mastodon-notifications") +(autoload 'mastodon-notifications-policy "mastodon-notifications") +(autoload 'mastodon-notifications-requests "mastodon-notifications") (autoload 'mastodon-profile--fetch-server-account-settings "mastodon-profile") -(autoload 'mastodon-profile--get-toot-author "mastodon-profile") +(autoload 'mastodon-profile-get-toot-author "mastodon-profile") (autoload 'mastodon-profile--make-author-buffer "mastodon-profile") -(autoload 'mastodon-profile--my-profile "mastodon-profile") -(autoload 'mastodon-profile--show-user "mastodon-profile") -(autoload 'mastodon-profile--update-user-profile-note "mastodon-profile") -(autoload 'mastodon-profile--view-bookmarks "mastodon-profile") -(autoload 'mastodon-profile--view-favourites "mastodon-profile") -(autoload 'mastodon-tl--block-user "mastodon-tl") -(autoload 'mastodon-tl--follow-user "mastodon-tl") -(autoload 'mastodon-tl--followed-tags-timeline "mastodon-tl") -(autoload 'mastodon-tl--get-buffer-type "mastodon-tl") -(autoload 'mastodon-tl--get-federated-timeline "mastodon-tl") -(autoload 'mastodon-tl--get-home-timeline "mastodon-tl") -(autoload 'mastodon-tl--get-local-timeline "mastodon-tl") -(autoload 'mastodon-tl--get-tag-timeline "mastodon-tl") -(autoload 'mastodon-tl--goto-next-item "mastodon-tl") -(autoload 'mastodon-tl--goto-prev-item "mastodon-tl") -(autoload 'mastodon-tl--init-sync "mastodon-tl") -(autoload 'mastodon-tl--list-followed-tags "mastodon-tl") -(autoload 'mastodon-tl--mute-user "mastodon-tl") -(autoload 'mastodon-tl--next-tab-item "mastodon-tl") -(autoload 'mastodon-tl--poll-vote "mastodon-http") -(autoload 'mastodon-tl--previous-tab-item "mastodon-tl") -(autoload 'mastodon-tl--thread "mastodon-tl") -(autoload 'mastodon-tl--toggle-spoiler-text-in-toot "mastodon-tl") -(autoload 'mastodon-tl--unblock-user "mastodon-tl") -(autoload 'mastodon-tl--unfollow-user "mastodon-tl") -(autoload 'mastodon-tl--unmute-user "mastodon-tl") -(autoload 'mastodon-tl--report-to-mods "mastodon-tl") -(autoload 'mastodon-tl--update "mastodon-tl") -(autoload 'mastodon-toot--edit-toot-at-point "mastodon-toot") +(autoload 'mastodon-profile-my-profile "mastodon-profile") +(autoload 'mastodon-profile-show-user "mastodon-profile") +(autoload 'mastodon-profile-update-user-profile-note "mastodon-profile") +(autoload 'mastodon-profile-view-bookmarks "mastodon-profile") +(autoload 'mastodon-profile-view-favourites "mastodon-profile") +(autoload 'mastodon-toot-edit-toot-at-point "mastodon-toot") (when (require 'lingva nil :no-error) - (autoload 'mastodon-toot--translate-toot-text "mastodon-toot")) + (autoload 'mastodon-toot-translate-toot-text "mastodon-toot")) (autoload 'mastodon-toot--view-toot-history "mastodon-tl") -(autoload 'mastodon-views--view-follow-suggestions "mastodon-views") -(autoload 'mastodon-views--view-filters "mastodon-views") -(autoload 'mastodon-views--view-follow-requests "mastodon-views") -(autoload 'mastodon-views--view-instance-description "mastodon-views") -(autoload 'mastodon-views--view-lists "mastodon-views") -(autoload 'mastodon-views--view-scheduled-toots "mastodon-views") -(autoload 'mastodon-tl--dm-user "mastodon-tl") -(autoload 'mastodon-tl--scroll-up-command "mastodon-tl") + +;; for M-x visibility +;; (views.el uses `mastodon-mode-map', so we can't easily require it) +(autoload 'mastodon-views-view-follow-suggestions "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-filters "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-follow-requests "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-own-instance "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-instance-description "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-lists "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-scheduled-toots "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-add-account-to-list "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-add-toot-account-at-point-to-list "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-create-list "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-create-filter "mastodon-views" + nil :interactive) +(autoload 'mastodon-views-view-list-timeline "mastodon-views" + nil :interactive) + (autoload 'special-mode "simple") -(autoload 'mastodon-tl--thread-do "mastodon-tl") (defvar mastodon-tl--highlight-current-toot) (defvar mastodon-notifications--map) +(defvar mastodon-client--token-file) + +(defvar mastodon-notifications-grouped-types + '("reblog" "favourite") ;; TODO: implement follow! + "List of notification types for which grouping is implemented. +Used in `mastodon-notifications-get'") (defgroup mastodon nil "Interface with Mastodon." @@ -192,87 +197,108 @@ and X others...\"." (interactive) (quit-window 'kill)) +(defvar mastodon-client--active-user-details-plist) +(defvar mastodon-auth--token-alist) + +;;;###autoload +(defun mastodon-forget-all-logins () + "Delete `mastodon-client--token-file'. +Also nil `mastodon-auth--token-alist'." + (interactive) + (when (y-or-n-p "Remove all saved login data?") + (if (not (file-exists-p mastodon-client--token-file)) + (message "No plstore file") + (delete-file mastodon-client--token-file) + (message "File %s deleted." mastodon-client--token-file)) + ;; nil some vars too: + (setq mastodon-client--active-user-details-plist nil) + (setq mastodon-auth--token-alist nil))) + (defvar mastodon-mode-map (let ((map (make-sparse-keymap))) ;; navigation inside a timeline - (define-key map (kbd "n") #'mastodon-tl--goto-next-item) - (define-key map (kbd "p") #'mastodon-tl--goto-prev-item) - (define-key map (kbd "M-n") #'mastodon-tl--next-tab-item) - (define-key map (kbd "M-p") #'mastodon-tl--previous-tab-item) - (define-key map [?\t] #'mastodon-tl--next-tab-item) - (define-key map [backtab] #'mastodon-tl--previous-tab-item) - (define-key map [?\S-\t] #'mastodon-tl--previous-tab-item) - (define-key map [?\M-\t] #'mastodon-tl--previous-tab-item) + (define-key map (kbd "n") #'mastodon-tl-goto-next-item) + (define-key map (kbd "p") #'mastodon-tl-goto-prev-item) + (define-key map (kbd "M-n") #'mastodon-tl-next-tab-item) + (define-key map (kbd "M-p") #'mastodon-tl-previous-tab-item) + (define-key map [?\t] #'mastodon-tl-next-tab-item) + (define-key map [backtab] #'mastodon-tl-previous-tab-item) + (define-key map [?\S-\t] #'mastodon-tl-previous-tab-item) + (define-key map [?\M-\t] #'mastodon-tl-previous-tab-item) (define-key map (kbd "l") #'recenter-top-bottom) ;; navigation between timelines - (define-key map (kbd "#") #'mastodon-tl--get-tag-timeline) - (define-key map (kbd "\"") #'mastodon-tl--list-followed-tags) - (define-key map (kbd "'") #'mastodon-tl--followed-tags-timeline) - (define-key map (kbd "A") #'mastodon-profile--get-toot-author) - (define-key map (kbd "F") #'mastodon-tl--get-federated-timeline) - (define-key map (kbd "H") #'mastodon-tl--get-home-timeline) - (define-key map (kbd "L") #'mastodon-tl--get-local-timeline) + (define-key map (kbd "#") #'mastodon-tl-get-tag-timeline) + (define-key map (kbd "\"") #'mastodon-tl-list-followed-tags) + (define-key map (kbd "'") #'mastodon-tl-followed-tags-timeline) + (define-key map (kbd "C-'") #'mastodon-tl-tag-group-timeline) + (define-key map (kbd "A") #'mastodon-profile-get-toot-author) + (define-key map (kbd "F") #'mastodon-tl-get-federated-timeline) + (define-key map (kbd "H") #'mastodon-tl-get-home-timeline) + (define-key map (kbd "L") #'mastodon-tl-get-local-timeline) (define-key map (kbd "N") #'mastodon-notifications-get) - (define-key map (kbd "@") #'mastodon-notifications--get-mentions) - (define-key map (kbd "P") #'mastodon-profile--show-user) - (define-key map (kbd "s") #'mastodon-search--query) + (define-key map (kbd "S-C-n") #'mastodon-notifications-requests) + (define-key map (kbd "@") #'mastodon-notifications-get-mentions) + (define-key map (kbd "P") #'mastodon-profile-show-user) + (define-key map (kbd "s") #'mastodon-search-query) (define-key map (kbd "/") #'mastodon-switch-to-buffer) - (define-key map (kbd "\\") #'mastodon-tl--get-remote-local-timeline) + (define-key map (kbd "\\") #'mastodon-tl-get-remote-local-timeline) ;; quitting mastodon (define-key map (kbd "q") #'kill-current-buffer) (define-key map (kbd "Q") #'mastodon-kill-window) (define-key map (kbd "M-C-q") #'mastodon-kill-all-buffers) ;; toot actions - (define-key map (kbd "c") #'mastodon-tl--toggle-spoiler-text-in-toot) - (define-key map (kbd "b") #'mastodon-toot--toggle-boost) - (define-key map (kbd "f") #'mastodon-toot--toggle-favourite) - (define-key map (kbd "k") #'mastodon-toot--toggle-bookmark) - (define-key map (kbd "r") #'mastodon-toot--reply) - (define-key map (kbd "C") #'mastodon-toot--copy-toot-url) - (define-key map (kbd "o") #'mastodon-toot--browse-toot-url) - (define-key map (kbd "v") #'mastodon-tl--poll-vote) - (define-key map (kbd "E") #'mastodon-toot--view-toot-edits) - (define-key map (kbd "T") #'mastodon-tl--thread) - (define-key map (kbd "RET") #'mastodon-tl--thread) - (define-key map (kbd "m") #'mastodon-tl--dm-user) + (define-key map (kbd "c") #'mastodon-tl-toggle-spoiler-text-in-toot) + (define-key map (kbd "b") #'mastodon-toot-toggle-boost) + (define-key map (kbd "f") #'mastodon-toot-toggle-favourite) + (define-key map (kbd "k") #'mastodon-toot-toggle-bookmark) + (define-key map (kbd "r") #'mastodon-toot-reply) + (define-key map (kbd "C") #'mastodon-toot-copy-toot-url) + (define-key map (kbd "o") #'mastodon-toot-browse-toot-url) + (define-key map (kbd "v") #'mastodon-tl-poll-vote) + (define-key map (kbd "E") #'mastodon-toot-view-toot-edits) + (define-key map (kbd "T") #'mastodon-tl-thread) + (define-key map (kbd "RET") #'mastodon-tl-thread) + (define-key map (kbd "m") #'mastodon-tl-dm-user) + (define-key map (kbd "=") #'mastodon-tl-view-first-full-image) (when (require 'lingva nil :no-error) - (define-key map (kbd "a") #'mastodon-toot--translate-toot-text)) - (define-key map (kbd ",") #'mastodon-toot--list-favouriters) - (define-key map (kbd ".") #'mastodon-toot--list-boosters) - (define-key map (kbd ";") #'mastodon-views--view-instance-description) + (define-key map (kbd "a") #'mastodon-toot-translate-toot-text)) + (define-key map (kbd ",") #'mastodon-toot-list-favouriters) + (define-key map (kbd ".") #'mastodon-toot-list-boosters) + (define-key map (kbd ";") #'mastodon-views-view-instance-description) ;; override special mode binding (define-key map (kbd "g") #'undefined) - (define-key map (kbd "g") #'mastodon-tl--update) + (define-key map (kbd "g") #'mastodon-tl-update) ;; this is now duplicated by 'g', cd remove/use for else: - (define-key map (kbd "u") #'mastodon-tl--update) + (define-key map (kbd "u") #'mastodon-tl-update) ;; own toot actions: (define-key map (kbd "t") #'mastodon-toot) - (define-key map (kbd "d") #'mastodon-toot--delete-toot) - (define-key map (kbd "D") #'mastodon-toot--delete-and-redraft-toot) - (define-key map (kbd "i") #'mastodon-toot--pin-toot-toggle) - (define-key map (kbd "e") #'mastodon-toot--edit-toot-at-point) + (define-key map (kbd "d") #'mastodon-toot-delete-toot) + (define-key map (kbd "D") #'mastodon-toot-delete-and-redraft-toot) + (define-key map (kbd "i") #'mastodon-toot-pin-toot-toggle) + (define-key map (kbd "e") #'mastodon-toot-edit-toot-at-point) ;; user actions - (define-key map (kbd "W") #'mastodon-tl--follow-user) - (define-key map (kbd "C-S-W") #'mastodon-tl--unfollow-user) - (define-key map (kbd "B") #'mastodon-tl--block-user) - (define-key map (kbd "C-S-B") #'mastodon-tl--unblock-user) - (define-key map (kbd "M") #'mastodon-tl--mute-user) - (define-key map (kbd "C-S-M") #'mastodon-tl--unmute-user) - (define-key map (kbd "Z") #'mastodon-tl--report-to-mods) + (define-key map (kbd "W") #'mastodon-tl-follow-user) + (define-key map (kbd "C-S-W") #'mastodon-tl-unfollow-user) + (define-key map (kbd "B") #'mastodon-tl-block-user) + (define-key map (kbd "C-S-B") #'mastodon-tl-unblock-user) + (define-key map (kbd "M") #'mastodon-tl-mute-user) + (define-key map (kbd "C-S-M") #'mastodon-tl-unmute-user) + (define-key map (kbd "Z") #'mastodon-tl-report-to-mods) ;; own profile - (define-key map (kbd "O") #'mastodon-profile--my-profile) - (define-key map (kbd "U") #'mastodon-profile--update-user-profile-note) - (define-key map (kbd "V") #'mastodon-profile--view-favourites) - (define-key map (kbd "K") #'mastodon-profile--view-bookmarks) + (define-key map (kbd "O") #'mastodon-profile-my-profile) + (define-key map (kbd "U") #'mastodon-profile-update-user-profile-note) + (define-key map (kbd "V") #'mastodon-profile-view-favourites) + (define-key map (kbd "K") #'mastodon-profile-view-bookmarks) (define-key map (kbd ":") #'mastodon-user-settings) + (define-key map (kbd "C-:") #'mastodon-notifications-policy) ;; minor views - (define-key map (kbd "R") #'mastodon-views--view-follow-requests) - (define-key map (kbd "S") #'mastodon-views--view-scheduled-toots) - (define-key map (kbd "I") #'mastodon-views--view-filters) - (define-key map (kbd "G") #'mastodon-views--view-follow-suggestions) - (define-key map (kbd "X") #'mastodon-views--view-lists) - (define-key map (kbd "SPC") #'mastodon-tl--scroll-up-command) - (define-key map (kbd "!") #'mastodon-tl--fold-post-toggle) + (define-key map (kbd "R") #'mastodon-views-view-follow-requests) + (define-key map (kbd "S") #'mastodon-views-view-scheduled-toots) + (define-key map (kbd "I") #'mastodon-views-view-filters) + (define-key map (kbd "G") #'mastodon-views-view-follow-suggestions) + (define-key map (kbd "X") #'mastodon-views-view-lists) + (define-key map (kbd "SPC") #'mastodon-tl-scroll-up-command) + (define-key map (kbd "!") #'mastodon-tl-fold-post-toggle) (define-key map (kbd "z") #'bury-buffer) map) "Keymap for `mastodon-mode'.") @@ -303,7 +329,7 @@ and X others...\"." "Face used for content warning.") (defface mastodon-toot-docs-face - `((t :inherit font-lock-comment-face)) + `((t :inherit shadow)) "Face used for documentation in toot compose buffer. If `mastodon-tl--enable-proportional-fonts' is changed, mastodon.el needs to be re-loaded for this to be correctly set.") @@ -343,7 +369,7 @@ Prority in switching is given to timeline views." ;; we need to update credential-account in case setting have been changed ;; outside mastodon.el in the meantime: (mastodon-return-credential-account :force) - (mastodon-tl--get-home-timeline) + (mastodon-tl-get-home-timeline) (message "Loading fediverse account %s on %s..." (mastodon-auth--user-acct) mastodon-instance-url)))) @@ -408,8 +434,11 @@ MAX-ID is a request parameter for pagination." "notifications" 'mastodon-notifications--timeline type - (when max-id - `(("max_id" . ,(mastodon-tl--buffer-property 'max-id)))) + `(,@(when mastodon-group-notifications + (mastodon-http--build-array-params-alist + "grouped_types[]" mastodon-notifications-grouped-types)) + ,@(when max-id + `(("max_id" . ,(mastodon-tl--buffer-property 'max-id))))) nil nil nil (if (or (not mastodon-group-notifications) ;; if version less than 1st grouped notifs release: @@ -530,7 +559,7 @@ Calls `mastodon-tl--get-buffer-type', which see." (require 'emojify nil :noerror)) (emojify-mode t) (when mastodon-toot--enable-custom-instance-emoji - (mastodon-toot--enable-custom-emoji))) + (mastodon-toot-enable-custom-emoji))) (mastodon-profile--fetch-server-account-settings) (when (and mastodon-tl--highlight-current-toot (fboundp #'cursor-face-highlight-mode)) diff --git a/elpa/mastodon-20241223.1040/mastodon.info b/elpa/mastodon-20250330.1519/mastodon.info similarity index 77% rename from elpa/mastodon-20241223.1040/mastodon.info rename to elpa/mastodon-20250330.1519/mastodon.info index 6d44f1d5..993ce508 100644 --- a/elpa/mastodon-20241223.1040/mastodon.info +++ b/elpa/mastodon-20250330.1519/mastodon.info @@ -9,6 +9,9 @@ END-INFO-DIR-ENTRY  File: mastodon.info, Node: Top, Next: README, Up: (dir) +A Mastodon client +***************** + * Menu: * README:: @@ -24,7 +27,7 @@ README * Contributing:: * Supporting ‘mastodon.el’: Supporting mastodonel. * Contributors:: -* screenshots:: +* Screenshots:: Installation @@ -40,10 +43,10 @@ Usage * Timelines:: * Composing toots:: * Other commands and account settings:: +* Notifications:: * Customization:: * Commands and variables index:: -* Alternative timeline layout:: -* mastodon hydra:: +* Packages related to ‘mastodon.el’: Packages related to mastodonel. * Live-updating timelines mastodon-async-mode:: * Translating toots:: * Bookmarks and ‘mastodon.el’: Bookmarks and mastodonel. @@ -61,7 +64,7 @@ File: mastodon.info, Node: README, Prev: Top, Up: Top 1 README ******** -‘mastodon.el’ is an Emacs client for the AcitivityPub social networks +‘mastodon.el’ is an Emacs client for the ActivityPub social networks that implement the Mastodon API. For info see joinmastodon.org (https://joinmastodon.org/). @@ -78,7 +81,7 @@ have it installed you should be able to browse this readme inside emacs. * Contributing:: * Supporting ‘mastodon.el’: Supporting mastodonel. * Contributors:: -* screenshots:: +* Screenshots::  File: mastodon.info, Node: Installation, Next: Usage, Up: README @@ -196,10 +199,10 @@ File: mastodon.info, Node: Usage, Next: Dependencies, Prev: Installation, Up * Timelines:: * Composing toots:: * Other commands and account settings:: +* Notifications:: * Customization:: * Commands and variables index:: -* Alternative timeline layout:: -* mastodon hydra:: +* Packages related to ‘mastodon.el’: Packages related to mastodonel. * Live-updating timelines mastodon-async-mode:: * Translating toots:: * Bookmarks and ‘mastodon.el’: Bookmarks and mastodonel. @@ -228,9 +231,38 @@ accounts you can activate one at a time by changing those two variables and restarting Emacs. If you were using mastodon.el before 2FA was implemented and the -above steps do not work, delete the old file specified by -‘mastodon-client--token-file’ and restart Emacs and follow the steps -again. +above steps do not work, call ‘(mastodon-forget-all-logins)’, restart +Emacs and follow the steps again. + + 1. encrypted access tokens (from 2.0.0) + + By default, user access tokens are now stored in the user’s auth + source file (typically ‘~/.authinfo.gpg’, check the value of + ‘auth-sources’). When you first update to 2.0.0, or if you + encounter issues due to old credentials, call + ‘(mastodon-forget-all-logins)’ to remove the old mastodon.el + plstore, and then authenticate again. If you don’t want to use the + auth source file, set ‘mastodon-auth-use-auth-source’ to nil. + Entries will instead be stored encrypted in + ‘mastodon-client--token-file’, a plstore. + + If for some reason you reauthenticate, you’ll need to either remove + the entry in your auth sources file, or manually update the token + in it after doing so, as mastodon.el is unable to reliably update + (or even remove) entires. + + The format for a mastodon.el auth source entry is as follows: + + ‘machine INSTANCE login USERNAME password AUTHTOKEN’ + + with the token being what you copy from the browser when + authenticating. If you have ‘auth-source-save-behavior’ set to + nil, you’ll also need to add such an entry manually. + + Finally, if you find you’re asked for your key passphrase too often + while authenticating, consider setting ‘epa-file-encrypt-to’ (for + auth-source encryption) and ‘plstore-encrypt-to’ (for plstore + encryption) to your preferred key ID.  File: mastodon.info, Node: Timelines, Next: Composing toots, Prev: Logging in to your instance, Up: Usage @@ -258,8 +290,8 @@ your ‘mastodon-token-file’ does not contain ‘:client_id’ and *Timeline actions* ‘n’ Go to next item (toot, notification, user) ‘p’ Go to previous item (toot, notification, user) - ‘M-n=/=’ Go to the next interesting thing that has an action - ‘M-p=/=’ Go to the previous interesting thing that has an action + ‘M-n/’ Go to the next interesting thing that has an action + ‘M-p/’ Go to the previous interesting thing that has an action ‘F’ Open federated timeline (1 prefix arg: hide-replies, 2 prefix args: media only) ‘H’ Open home timeline (1 prefix arg: hide-replies) ‘L’ Open local timeline (1 prefix arg: hide-replies, 2 prefix args: media only) @@ -289,6 +321,7 @@ your ‘mastodon-token-file’ does not contain ‘:client_id’ and ‘K’ view bookmarked toots ‘X’ view/edit/create/delete lists ‘S’ view your scheduled toots + ‘S-:’ view profile/account settings transient menu *Toot actions* ‘t’ Compose a new toot ‘c’ Toggle content warning content @@ -308,10 +341,11 @@ your ‘mastodon-token-file’ does not contain ‘:client_id’ and (‘S-C-’) ‘W’, ‘M’, ‘B’ (un)follow, (un)mute, (un)block author of toot at point *Profile view* ‘C-c C-c’ cycle between statuses, statuses without boosts, followers, and following - ‘mastodon-profile--account-account-to-list’ (see lists view) + ‘mastodon-profile--add-account-to-list’ (see lists view) *Notifications view* ‘a’, ‘j’ accept/reject follow request ‘C-k’ clear notification at point + ‘C-c C-c’ cycle between notification types see ‘mastodon-notifications--get-*’ functions for filtered views *Quitting* ‘q’ Quit mastodon buffer, leave window open @@ -327,7 +361,7 @@ your ‘mastodon-token-file’ does not contain ‘:client_id’ and ‘(⭐)’ (or I favourited this toot ‘(F)’) ‘(🔖)’ (or I bookmarked this toot - (‘K’)) + ‘(K)’)  File: mastodon.info, Node: Composing toots, Next: Other commands and account settings, Prev: Timelines, Up: Usage @@ -368,19 +402,20 @@ is as follows: 1. Keybindings - Key Action - --------------------------------------------- - ‘C-c C-c’ Send toot - ‘C-c C-k’ Cancel toot - ‘C-c C-w’ Add content warning - ‘C-c C-v’ Change toot visibility - ‘C-c C-n’ Add sensitive media/nsfw flag - ‘C-c C-a’ Upload attachment(s) - ‘C-c !’ Remove all attachments - ‘C-c C-e’ Insert emoji - ‘C-c C-p’ Create a poll - ‘C-c C-l’ Set toot language - ‘-C-c C-s’ Schedule toot + Key Action + -------------------------------------------- + ‘C-c C-c’ Send toot + ‘C-c C-k’ Cancel toot + ‘C-c C-w’ Add content warning + ‘C-c C-v’ Change toot visibility + ‘C-c C-n’ Add sensitive media/nsfw flag + ‘C-c C-a’ Upload attachment(s) + ‘C-c !’ Remove all attachments + ‘C-c C-e’ Insert emoji + ‘C-c C-p’ Create a poll + ‘C-c C-o’ Cancel poll + ‘C-c C-l’ Set toot language + ‘C-c C-s’ Schedule toot 2. Autocompletion of mentions, tags and emoji @@ -412,7 +447,7 @@ is as follows: • ‘mastodon-toot--delete-all-drafts’: Delete all your drafts.  -File: mastodon.info, Node: Other commands and account settings, Next: Customization, Prev: Composing toots, Up: Usage +File: mastodon.info, Node: Other commands and account settings, Next: Notifications, Prev: Composing toots, Up: Usage 1.2.4 Other commands and account settings: ------------------------------------------ @@ -432,6 +467,9 @@ and should work without first loading a ‘mastodon.el’ buffer: on your instance. • ‘mastodon-search--trending-statuses’: View a list of trending statuses on your instance. + • ‘mastodon-search--trending-links’: View a list of trending links on + your instance (+ click through to a timeline of posts featuring a + given link) • ‘mastodon-tl--add-toot-account-at-point-to-list’: Add the account of the toot at point to a list. @@ -465,28 +503,25 @@ and should work without first loading a ‘mastodon.el’ buffer: • ‘mastodon-tl--remote-tag-timeline’: View a tag timeline on a remote instance. - • ‘mastodon-profile--update-display-name’: Update the display name - for your account. - • ‘mastodon-profile--update-user-profile-note’: Update your bio note. - • ‘mastodon-profile--update-meta-fields’: Update your metadata - fields. - • ‘mastodon-profile--set-default-toot-visibility’: Set the default - visibility for your toots. - • ‘mastodon-profile--account-locked-toggle’: Toggle the locked status - of your account. Locked accounts have to manually approve follow - requests. - • ‘mastodon-profile--account-discoverable-toggle’: Toggle the - discoverable status of your account. Non-discoverable accounts are - not listed in the profile directory. - • ‘mastodon-profile--account-bot-toggle’: Toggle whether your account - is flagged as a bot. - • ‘mastodon-profile--account-sensitive-toggle’: Toggle whether your - posts are marked as sensitive (nsfw) by default. - - -File: mastodon.info, Node: Customization, Next: Commands and variables index, Prev: Other commands and account settings, Up: Usage - -1.2.5 Customization + • ‘mastodon-user-settings’: Launch a transient menu to update various + account settings. + + +File: mastodon.info, Node: Notifications, Next: Customization, Prev: Other commands and account settings, Up: Usage + +1.2.5 Notifications +------------------- + +Mastodon from 4.3 supports grouped notifications. These are implemented +by ‘mastodon.el’ but disabled by default out of consideration to users +on instances that don’t support them. If you are on an instance that +implements grouped notifications, set ‘mastodon-group-notifications’ to +‘t’ to enable them. + + +File: mastodon.info, Node: Customization, Next: Commands and variables index, Prev: Notifications, Up: Usage + +1.2.6 Customization ------------------- See ‘M-x customize-group RET mastodon’ to view all customize options. @@ -510,11 +545,12 @@ See ‘M-x customize-group RET mastodon’ to view all customize options. • Nofitication options: • Display user’s profile note in follow requests + • Group notifications  -File: mastodon.info, Node: Commands and variables index, Next: Alternative timeline layout, Prev: Customization, Up: Usage +File: mastodon.info, Node: Commands and variables index, Next: Packages related to mastodonel, Prev: Customization, Up: Usage -1.2.6 Commands and variables index +1.2.7 Commands and variables index ---------------------------------- An index of all user-facing commands and custom variables is available @@ -525,28 +561,45 @@ bindings, or run ‘M-X’ (upper-case ‘X’) to view all commands in the buffer with completion, and call one.  -File: mastodon.info, Node: Alternative timeline layout, Next: mastodon hydra, Prev: Commands and variables index, Up: Usage +File: mastodon.info, Node: Packages related to mastodonel, Next: Live-updating timelines mastodon-async-mode, Prev: Commands and variables index, Up: Usage -1.2.7 Alternative timeline layout ---------------------------------- +1.2.8 Packages related to ‘mastodon.el’ +--------------------------------------- -The incomparable Nicholas Rougier has written an alternative timeline -layout for ‘mastodon.el’. + 1. Alternative timeline layout - The repo is at mastodon-alt -(https://github.com/rougier/mastodon-alt). + The incomparable Nicholas Rougier has written an alternative + timeline layout for ‘mastodon.el’. - -File: mastodon.info, Node: mastodon hydra, Next: Live-updating timelines mastodon-async-mode, Prev: Alternative timeline layout, Up: Usage + The repo is at mastodon-alt + (https://github.com/rougier/mastodon-alt). + + 2. Org links, archive search + + toot-suite (https://codeberg.org/chrmoe/toot-suite) implements an + org link type for fediverse posts, and also provides a way to + browse an offline archive of your account. + + 3. Mastodon hydra + + A user made a hydra for handling basic ‘mastodon.el’ commands. + It’s available at + . + + 4. Narrow to timeline item + + A simple code snippet to enable narrowing to current item in + timelines: + -1.2.8 mastodon hydra --------------------- + 5. Sachac’s config goodies -A user made a hydra for handling basic mastodon.el commands. It’s -available at . + The incomparable sachac has a bunch of ‘mastodon.el’ extensions and + goodies in their literate config, available here: + .  -File: mastodon.info, Node: Live-updating timelines mastodon-async-mode, Next: Translating toots, Prev: mastodon hydra, Up: Usage +File: mastodon.info, Node: Live-updating timelines mastodon-async-mode, Next: Translating toots, Prev: Packages related to mastodonel, Up: Usage 1.2.9 Live-updating timelines: ‘mastodon-async-mode’ ---------------------------------------------------- @@ -582,7 +635,7 @@ translator of choice (‘libretrans.el’ , ‘google-translate’, ‘babel’, its text argument. Here’s what ‘mastodon-toot--translate-toot-text’ looks like: - (defun mastodon-toot--translate-toot-text () + (defun mastodon-toot-translate-toot-text () "Translate text of toot at point. Uses `lingva.el'." (interactive) @@ -611,9 +664,12 @@ File: mastodon.info, Node: Dependencies, Next: Network compatibility, Prev: U ================ Hard dependencies (should all install with ‘mastodon.el’): - • ‘request’ (for uploading attachments), emacs-request - (https://github.com/tkf/emacs-request) - • ‘persist’ for storing some settings across sessions + • ‘request’ (for uploading attachments, emacs-request + (https://github.com/tkf/emacs-request)) + • ‘persist’ (for storing some settings across sessions, persist + (https://elpa.gnu.org/packages/persist.html)) + • ‘tp.el’ (for transient menus, tp.el + (https://codeberg.org/martianh/tp.el)) Optional dependencies (install yourself, ‘mastodon.el’ can use them): • ‘emojify’ to use custom emoji (else we use builtin ‘emoji.el’) @@ -633,6 +689,7 @@ Mastodon API. • Pleroma (pleroma.social (https://pleroma.social/)) • Akkoma (akkoma.social (https://akkoma.social/)) • Gotosocial (gotosocial.org (https://gotosocial.org/)) + • Sharkey (joinsharkey.org (https://joinsharkey.org)) It does not support the non-Mastodon API servers Misskey (misskey.io (https://misskey.io/)), Firefish (joinfirefish.org @@ -718,14 +775,14 @@ File: mastodon.info, Node: Supporting mastodonel, Next: Contributors, Prev: C If you’d like to support continued development of ‘mastodon.el’, I accept donations via paypal: paypal.me/martianh (https://paypal.me/martianh). If you would prefer a different payment -method, please write to me at and I can provide -IBAN or other bank account details. +method, please write to me at and I can +provide IBAN or other bank account details. I don’t have a tech worker’s income, so even a small tip would help out.  -File: mastodon.info, Node: Contributors, Next: screenshots, Prev: Supporting mastodonel, Up: README +File: mastodon.info, Node: Contributors, Next: Screenshots, Prev: Supporting mastodonel, Up: README 1.7 Contributors ================ @@ -741,9 +798,9 @@ File: mastodon.info, Node: Contributors, Next: screenshots, Prev: Supporting •  -File: mastodon.info, Node: screenshots, Prev: Contributors, Up: README +File: mastodon.info, Node: Screenshots, Prev: Contributors, Up: README -1.8 screenshots +1.8 Screenshots =============== Here’s a (federated) timeline: @@ -772,39 +829,45 @@ server values commented):  Tag Table: Node: Top219 -Node: README1006 -Node: Installation1656 -Node: ELPA1945 -Node: MELPA2173 -Node: Repo2553 -Node: Emoji3046 -Node: Discover3640 -Node: Usage4192 -Node: Logging in to your instance4654 -Node: Timelines5651 -Ref: Keybindings6126 -Ref: Toot byline legend10966 -Node: Composing toots11275 -Ref: Keybindings (1)12827 -Ref: Autocompletion of mentions tags and emoji13362 -Ref: Draft toots14287 -Node: Other commands and account settings14758 -Node: Customization18122 -Node: Commands and variables index19000 -Node: Alternative timeline layout19516 -Node: mastodon hydra19892 -Node: Live-updating timelines mastodon-async-mode20224 -Node: Translating toots21063 -Node: Bookmarks and mastodonel22247 -Node: Dependencies22789 -Node: Network compatibility23423 -Node: Contributing24305 -Node: Bug reports24801 -Node: Fixes and features25712 -Node: Coding style26213 -Node: Supporting mastodonel26837 -Node: Contributors27389 -Node: screenshots27824 +Node: README1083 +Node: Installation1732 +Node: ELPA2021 +Node: MELPA2249 +Node: Repo2629 +Node: Emoji3122 +Node: Discover3716 +Node: Usage4268 +Node: Logging in to your instance4770 +Ref: encrypted access tokens (from 200)5742 +Node: Timelines7200 +Ref: Keybindings7675 +Ref: Toot byline legend12657 +Node: Composing toots12966 +Ref: Keybindings (1)14518 +Ref: Autocompletion of mentions tags and emoji15073 +Ref: Draft toots15998 +Node: Other commands and account settings16469 +Node: Notifications19144 +Node: Customization19629 +Node: Commands and variables index20517 +Node: Packages related to mastodonel21036 +Ref: Alternative timeline layout21289 +Ref: Org links archive search21516 +Ref: Mastodon hydra21735 +Ref: Narrow to timeline item21919 +Ref: Sachac's config goodies22126 +Node: Live-updating timelines mastodon-async-mode22345 +Node: Translating toots23200 +Node: Bookmarks and mastodonel24383 +Node: Dependencies24925 +Node: Network compatibility25711 +Node: Contributing26652 +Node: Bug reports27148 +Node: Fixes and features28059 +Node: Coding style28560 +Node: Supporting mastodonel29184 +Node: Contributors29741 +Node: Screenshots30176  End Tag Table -- 2.47.3