From d47e55c48417da0e822c82f7104af9a3d426ddfd Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Fri, 2 Sep 2022 16:56:24 +0200 Subject: start on loading masto URL links in mastodon.el --- lisp/mastodon-tl.el | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lisp') diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 8e3ab30..816829e 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -1174,6 +1174,13 @@ webapp" (reblog (alist-get 'reblog json))) (if reblog (alist-get 'id reblog) id))) +(defun mastodon-tl--single-toot-from-url (url) + "Open the toot at URL in `mastodon.el'." + ;; TODO: test if URL is masto + ;; FIXME: this only works 1/2 the time + (let ((id (url-file-nondirectory url))) + (mastodon-tl--single-toot id))) + (defun mastodon-tl--single-toot (&optional id) "View toot at point in separate buffer. ID is that of the toot to view." -- cgit v1.2.3 From e7436ad93df6f489eaba4d0a29685e3cf618bb59 Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Fri, 2 Sep 2022 17:14:35 +0200 Subject: simple error handling in single-toot and --thread --- lisp/mastodon-tl.el | 66 ++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 31 deletions(-) (limited to 'lisp') diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 816829e..6a8b0ba 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -1197,16 +1197,18 @@ ID is that of the toot to view." (buffer (format "*mastodon-toot-%s*" id)) (toot (mastodon-http--get-json (mastodon-http--api (concat "statuses/" id))))) - (with-output-to-temp-buffer buffer - (switch-to-buffer buffer) - (mastodon-mode) - (setq mastodon-tl--buffer-spec - `(buffer-name ,buffer - endpoint ,(format "statuses/%s" id) - update-function - (lambda (toot) (message "END of thread.")))) - (let ((inhibit-read-only t)) - (mastodon-tl--toot toot :detailed-p))))) + (if (equal (caar toot) 'error) + (message "Error: %s" (cdar toot)) + (with-output-to-temp-buffer buffer + (switch-to-buffer buffer) + (mastodon-mode) + (setq mastodon-tl--buffer-spec + `(buffer-name ,buffer + endpoint ,(format "statuses/%s" id) + update-function + (lambda (toot) (message "END of thread.")))) + (let ((inhibit-read-only t)) + (mastodon-tl--toot toot :detailed-p)))))) (defun mastodon-tl--thread () "Open thread buffer for toot under `point'." @@ -1226,27 +1228,29 @@ ID is that of the toot to view." (mastodon-http--get-json (mastodon-http--api (concat "statuses/" id)))) (context (mastodon-http--get-json url))) - (when (member (alist-get 'type toot) '("reblog" "favourite")) - (setq toot (alist-get 'status toot))) - (if (> (+ (length (alist-get 'ancestors context)) - (length (alist-get 'descendants context))) - 0) - (progn - (with-output-to-temp-buffer buffer - (switch-to-buffer buffer) - (mastodon-mode) - (setq mastodon-tl--buffer-spec - `(buffer-name ,buffer - endpoint ,(format "statuses/%s/context" id) - update-function - (lambda (toot) (message "END of thread.")))) - (let ((inhibit-read-only t)) - (mastodon-tl--timeline (alist-get 'ancestors context)) - (goto-char (point-max)) - (mastodon-tl--toot toot :detailed-p) - (mastodon-tl--timeline (alist-get 'descendants context)))) - (mastodon-tl--goto-next-toot)) - (mastodon-tl--single-toot id)))) + (if (equal (caar toot) 'error) + (message "Error: %s" (cdar toot)) + (when (member (alist-get 'type toot) '("reblog" "favourite")) + (setq toot (alist-get 'status toot))) + (if (> (+ (length (alist-get 'ancestors context)) + (length (alist-get 'descendants context))) + 0) + (progn + (with-output-to-temp-buffer buffer + (switch-to-buffer buffer) + (mastodon-mode) + (setq mastodon-tl--buffer-spec + `(buffer-name ,buffer + endpoint ,(format "statuses/%s/context" id) + update-function + (lambda (toot) (message "END of thread.")))) + (let ((inhibit-read-only t)) + (mastodon-tl--timeline (alist-get 'ancestors context)) + (goto-char (point-max)) + (mastodon-tl--toot toot :detailed-p) + (mastodon-tl--timeline (alist-get 'descendants context)))) + (mastodon-tl--goto-next-toot)) + (mastodon-tl--single-toot id))))) (defun mastodon-tl--create-filter () "Create a filter for a word. -- cgit v1.2.3 From 525898871f014120e1dc34ef9bb44787d3443ac6 Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 09:40:14 +0200 Subject: search--url-lookup --- lisp/mastodon-search.el | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'lisp') diff --git a/lisp/mastodon-search.el b/lisp/mastodon-search.el index 49b5367..4d1a2d3 100644 --- a/lisp/mastodon-search.el +++ b/lisp/mastodon-search.el @@ -77,6 +77,26 @@ QUERY is the string to search." (tags (alist-get 'hashtags response))) (mapcar #'mastodon-search--get-hashtag-info tags))) +(defun mastodon-search--url-lookup (&optional query-url) + "Do a WebFinger lookup for QUERY-URL. +If a status or account is found, load it in `mastodon.el', if not, just browse the URL in the normal fashion." + (interactive) + (let* ((query (or query-url (url-get-url-at-point))) + (url (format "%s/api/v2/search" mastodon-instance-url)) + (param (concat "resolve=t")) ; webfinger + (response (mastodon-http--get-search-json url query param))) + (if (equal response '((accounts . #1=[]) (statuses . #1#) (hashtags . #1#))) + ;; no results + ;; browse URL here + () + (let ((statuses (assoc 'statuses response)) + (status-1 (seq-first (cdr statuses))) + (status-1-id (alist-get 'id status-1)) + (accounts (assoc 'accounts response))) + ;; TODO: test for a masto URL first? + (when status-1 + (mastodon-tl--single-toot status-1-id)))))) + ;; trending tags -- cgit v1.2.3 From 8842e7797a4c1edafadd45231ddea7d436efc5e9 Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:18:44 +0200 Subject: tl--url-lookup command, FIX #240 --- lisp/mastodon-search.el | 21 --------------------- lisp/mastodon-tl.el | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 21 deletions(-) (limited to 'lisp') diff --git a/lisp/mastodon-search.el b/lisp/mastodon-search.el index 4d1a2d3..c7658ba 100644 --- a/lisp/mastodon-search.el +++ b/lisp/mastodon-search.el @@ -77,27 +77,6 @@ QUERY is the string to search." (tags (alist-get 'hashtags response))) (mapcar #'mastodon-search--get-hashtag-info tags))) -(defun mastodon-search--url-lookup (&optional query-url) - "Do a WebFinger lookup for QUERY-URL. -If a status or account is found, load it in `mastodon.el', if not, just browse the URL in the normal fashion." - (interactive) - (let* ((query (or query-url (url-get-url-at-point))) - (url (format "%s/api/v2/search" mastodon-instance-url)) - (param (concat "resolve=t")) ; webfinger - (response (mastodon-http--get-search-json url query param))) - (if (equal response '((accounts . #1=[]) (statuses . #1#) (hashtags . #1#))) - ;; no results - ;; browse URL here - () - (let ((statuses (assoc 'statuses response)) - (status-1 (seq-first (cdr statuses))) - (status-1-id (alist-get 'id status-1)) - (accounts (assoc 'accounts response))) - ;; TODO: test for a masto URL first? - (when status-1 - (mastodon-tl--single-toot status-1-id)))))) - - ;; trending tags (defun mastodon-search--trending-tags () diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 6a8b0ba..ccc092f 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -688,6 +688,31 @@ START and END are the boundaries of the link in the toot." 'help-echo help-echo) extra-properties)))) +(defun mastodon-tl--url-lookup (&optional query-url) + "Do a WebFinger lookup for QUERY-URL, or the URL at point. +If a status or account is found, load it in `mastodon.el', if +not, just browse the URL in the normal fashion." + (interactive) + (let* ((query (or query-url (url-get-url-at-point))) + (url (format "%s/api/v2/search" mastodon-instance-url)) + (param (concat "resolve=t")) ; webfinger + (response (mastodon-http--get-search-json url query param))) + (if (equal response '((accounts . #1=[]) (statuses . #1#) (hashtags . #1#))) + (shr-browse-url query-url) + (cond ((not (equal '[] + (alist-get 'statuses response))) + (let* ((statuses (assoc 'statuses response)) + (status (seq-first (cdr statuses))) + (status-id (alist-get 'id status))) + (mastodon-tl--thread status-id))) + ((not (equal '[] + (alist-get 'accounts response))) + (let* ((accounts (assoc 'accounts response)) + (account (seq-first (cdr accounts))) + (account-id (alist-get 'id account))) + (mastodon-profile--account-from-id account-id))))))) + + (defun mastodon-tl--extract-userid-toot (toot acct) "Extract a user id for an ACCT from mentions in a TOOT." (let* ((mentions (append (alist-get 'mentions toot) nil)) -- cgit v1.2.3 From 586cb850fd1edfcf5851aa5c32487235c53eac8d Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:19:47 +0200 Subject: --thread edits to work with --url-lookup: id arg, point at toot --- lisp/mastodon-tl.el | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'lisp') diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index ccc092f..14e74c6 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -1235,24 +1235,26 @@ ID is that of the toot to view." (let ((inhibit-read-only t)) (mastodon-tl--toot toot :detailed-p)))))) -(defun mastodon-tl--thread () +(defun mastodon-tl--thread (&optional id) "Open thread buffer for toot under `point'." (interactive) (let* ((id - (if (equal (mastodon-tl--get-endpoint) "notifications") - ;; for boosts/faves: - (if (mastodon-tl--property 'parent-toot) - (mastodon-tl--as-string (mastodon-tl--toot-id - (mastodon-tl--property 'parent-toot))) - (mastodon-tl--property 'base-toot-id)) - (mastodon-tl--property 'base-toot-id))) + (or id + (if (equal (mastodon-tl--get-endpoint) "notifications") + ;; for boosts/faves: + (if (mastodon-tl--property 'parent-toot) + (mastodon-tl--as-string (mastodon-tl--toot-id + (mastodon-tl--property 'parent-toot))) + (mastodon-tl--property 'base-toot-id)) + (mastodon-tl--property 'base-toot-id)))) (url (mastodon-http--api (format "statuses/%s/context" id))) (buffer (format "*mastodon-thread-%s*" id)) (toot ;; refetch current toot in case we just faved/boosted: (mastodon-http--get-json (mastodon-http--api (concat "statuses/" id)))) - (context (mastodon-http--get-json url))) + (context (mastodon-http--get-json url)) + (marker (make-marker))) (if (equal (caar toot) 'error) (message "Error: %s" (cdar toot)) (when (member (alist-get 'type toot) '("reblog" "favourite")) @@ -1260,6 +1262,7 @@ ID is that of the toot to view." (if (> (+ (length (alist-get 'ancestors context)) (length (alist-get 'descendants context))) 0) + ;; if we have a thread: (progn (with-output-to-temp-buffer buffer (switch-to-buffer buffer) @@ -1272,9 +1275,14 @@ ID is that of the toot to view." (let ((inhibit-read-only t)) (mastodon-tl--timeline (alist-get 'ancestors context)) (goto-char (point-max)) + (move-marker marker (point)) + ;; print re-fetched toot: (mastodon-tl--toot toot :detailed-p) (mastodon-tl--timeline (alist-get 'descendants context)))) + ;; put point at the toot: + (goto-char (marker-position marker)) (mastodon-tl--goto-next-toot)) + ;; else just print the lone toot: (mastodon-tl--single-toot id))))) (defun mastodon-tl--create-filter () -- cgit v1.2.3 From 75fe75911725ad2ef38b9ee112588ba4a877048f Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:34:02 +0200 Subject: -http: optional silent requests so we can actually message --- lisp/mastodon-http.el | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'lisp') diff --git a/lisp/mastodon-http.el b/lisp/mastodon-http.el index 9904232..af1a9da 100644 --- a/lisp/mastodon-http.el +++ b/lisp/mastodon-http.el @@ -69,7 +69,7 @@ (string-match "[0-9][0-9][0-9]" status-line) (match-string 0 status-line))) -(defun mastodon-http--url-retrieve-synchronously (url) +(defun mastodon-http--url-retrieve-synchronously (url &optional silent) "Retrieve URL asynchronously. This is a thin abstraction over the system @@ -77,7 +77,7 @@ This is a thin abstraction over the system is available we will call it with or without a timeout." (if (< (cdr (func-arity 'url-retrieve-synchronously)) 4) (url-retrieve-synchronously url) - (url-retrieve-synchronously url nil nil mastodon-http--timeout))) + (url-retrieve-synchronously url (or silent nil) nil mastodon-http--timeout))) (defun mastodon-http--triage (response success) "Determine if RESPONSE was successful. Call SUCCESS if successful. @@ -131,17 +131,17 @@ Authorization header is included by default unless UNAUTHENTICATED-P is non-nil. (mastodon-http--url-retrieve-synchronously url))) unauthenticated-p)) -(defun mastodon-http--get (url) +(defun mastodon-http--get (url &optional silent) "Make synchronous GET request to URL. Pass response buffer to CALLBACK function." (mastodon-http--authorized-request "GET" - (mastodon-http--url-retrieve-synchronously url))) + (mastodon-http--url-retrieve-synchronously url silent))) -(defun mastodon-http--get-json (url) +(defun mastodon-http--get-json (url &optional silent) "Make synchronous GET request to URL. Return JSON response." - (with-current-buffer (mastodon-http--get url) + (with-current-buffer (mastodon-http--get url silent) (mastodon-http--process-json))) (defun mastodon-http--process-json () @@ -184,14 +184,14 @@ PARAMS should be an alist as required by `url-build-query-string'." (kill-buffer) (json-read-from-string json-string))) -(defun mastodon-http--get-search-json (url query &optional param) +(defun mastodon-http--get-search-json (url query &optional param silent) "Make GET request to URL, searching for QUERY and return JSON response. PARAM is any extra parameters to send with the request." - (let ((buffer (mastodon-http--get-search url query param))) + (let ((buffer (mastodon-http--get-search url query param silent))) (with-current-buffer buffer (mastodon-http--process-json-search)))) -(defun mastodon-http--get-search (base-url query &optional param) +(defun mastodon-http--get-search (base-url query &optional param silent) "Make GET request to BASE-URL, searching for QUERY. Pass response buffer to CALLBACK function. PARAM is a formatted request parameter, eg 'following=true'." @@ -200,7 +200,7 @@ PARAM is a formatted request parameter, eg 'following=true'." (let ((url (if param (concat base-url "?" param "&q=" (url-hexify-string query)) (concat base-url "?q=" (url-hexify-string query))))) - (mastodon-http--url-retrieve-synchronously url)))) + (mastodon-http--url-retrieve-synchronously url silent)))) ;; profile update functions -- cgit v1.2.3 From 12b41726b50d537a4771ba3ca262d589cdf4a820 Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:34:28 +0200 Subject: url-lookup: keymap and silent call / message --- lisp/mastodon-tl.el | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'lisp') diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 14e74c6..73b786f 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -143,6 +143,7 @@ etc.") (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 [remap shr-browse-url] 'mastodon-tl--url-lookup) (keymap-canonicalize map)) "The keymap to be set for shr.el generated links that are not images. @@ -688,15 +689,19 @@ START and END are the boundaries of the link in the toot." 'help-echo help-echo) extra-properties)))) +;; URL lookup: should be available even if `mastodon.el' not loaded: + +;;;###autoload (defun mastodon-tl--url-lookup (&optional query-url) "Do a WebFinger lookup for QUERY-URL, or the URL at point. If a status or account is found, load it in `mastodon.el', if not, just browse the URL in the normal fashion." (interactive) + (message "Performing lookup...") (let* ((query (or query-url (url-get-url-at-point))) (url (format "%s/api/v2/search" mastodon-instance-url)) (param (concat "resolve=t")) ; webfinger - (response (mastodon-http--get-search-json url query param))) + (response (mastodon-http--get-search-json url query param :silent))) (if (equal response '((accounts . #1=[]) (statuses . #1#) (hashtags . #1#))) (shr-browse-url query-url) (cond ((not (equal '[] @@ -1252,8 +1257,9 @@ ID is that of the toot to view." (toot ;; refetch current toot in case we just faved/boosted: (mastodon-http--get-json - (mastodon-http--api (concat "statuses/" id)))) - (context (mastodon-http--get-json url)) + (mastodon-http--api (concat "statuses/" id)) + :silent)) + (context (mastodon-http--get-json url :silent)) (marker (make-marker))) (if (equal (caar toot) 'error) (message "Error: %s" (cdar toot)) -- cgit v1.2.3 From 6d20d245a2791fbaf4cc673c091ed6315f673579 Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:41:52 +0200 Subject: docstring --- lisp/mastodon-tl.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp') diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 73b786f..113bd11 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -1241,7 +1241,7 @@ ID is that of the toot to view." (mastodon-tl--toot toot :detailed-p)))))) (defun mastodon-tl--thread (&optional id) - "Open thread buffer for toot under `point'." + "Open thread buffer for toot at point or with ID." (interactive) (let* ((id (or id -- cgit v1.2.3 From f43dd731e9d6b882cf687c44ef1b5631309bd3b9 Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:42:31 +0200 Subject: move url-lookup to mastodon.el --- lisp/mastodon-tl.el | 31 +------------------------------ lisp/mastodon.el | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 30 deletions(-) (limited to 'lisp') diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 113bd11..4b0bd9f 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -143,7 +143,7 @@ etc.") (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 [remap shr-browse-url] 'mastodon-tl--url-lookup) + (define-key map [remap shr-browse-url] 'mastodon-url-lookup) (keymap-canonicalize map)) "The keymap to be set for shr.el generated links that are not images. @@ -689,35 +689,6 @@ START and END are the boundaries of the link in the toot." 'help-echo help-echo) extra-properties)))) -;; URL lookup: should be available even if `mastodon.el' not loaded: - -;;;###autoload -(defun mastodon-tl--url-lookup (&optional query-url) - "Do a WebFinger lookup for QUERY-URL, or the URL at point. -If a status or account is found, load it in `mastodon.el', if -not, just browse the URL in the normal fashion." - (interactive) - (message "Performing lookup...") - (let* ((query (or query-url (url-get-url-at-point))) - (url (format "%s/api/v2/search" mastodon-instance-url)) - (param (concat "resolve=t")) ; webfinger - (response (mastodon-http--get-search-json url query param :silent))) - (if (equal response '((accounts . #1=[]) (statuses . #1#) (hashtags . #1#))) - (shr-browse-url query-url) - (cond ((not (equal '[] - (alist-get 'statuses response))) - (let* ((statuses (assoc 'statuses response)) - (status (seq-first (cdr statuses))) - (status-id (alist-get 'id status))) - (mastodon-tl--thread status-id))) - ((not (equal '[] - (alist-get 'accounts response))) - (let* ((accounts (assoc 'accounts response)) - (account (seq-first (cdr accounts))) - (account-id (alist-get 'id account))) - (mastodon-profile--account-from-id account-id))))))) - - (defun mastodon-tl--extract-userid-toot (toot acct) "Extract a user id for an ACCT from mentions in a TOOT." (let* ((mentions (append (alist-get 'mentions toot) nil)) diff --git a/lisp/mastodon.el b/lisp/mastodon.el index 96faf56..5c2bc34 100644 --- a/lisp/mastodon.el +++ b/lisp/mastodon.el @@ -264,6 +264,34 @@ If REPLY-JSON is the json of the toot being replied to." (interactive) (mastodon-toot--compose-buffer user reply-to-id reply-json)) +;; URL lookup: should be available even if `mastodon.el' not loaded: + +;;;###autoload +(defun mastodon-url-lookup (&optional query-url) + "Do a WebFinger lookup for QUERY-URL, or the URL at point. +If a status or account is found, load it in `mastodon.el', if +not, just browse the URL in the normal fashion." + (interactive) + (message "Performing lookup...") + (let* ((query (or query-url (url-get-url-at-point))) + (url (format "%s/api/v2/search" mastodon-instance-url)) + (param (concat "resolve=t")) ; webfinger + (response (mastodon-http--get-search-json url query param :silent))) + (if (equal response '((accounts . #1=[]) (statuses . #1#) (hashtags . #1#))) + (shr-browse-url query-url) + (cond ((not (seq-empty-p + (alist-get 'statuses response))) + (let* ((statuses (assoc 'statuses response)) + (status (seq-first (cdr statuses))) + (status-id (alist-get 'id status))) + (mastodon-tl--thread status-id))) + ((not (seq-empty-p + (alist-get 'accounts response))) + (let* ((accounts (assoc 'accounts response)) + (account (seq-first (cdr accounts))) + (account-id (alist-get 'id account))) + (mastodon-profile--account-from-id account-id))))))) + ;;;###autoload (add-hook 'mastodon-mode-hook (lambda () (when (require 'emojify nil :noerror) -- cgit v1.2.3 From 26ec5d7076e47bc7240e3e36aa516909f8c0424c Mon Sep 17 00:00:00 2001 From: marty hiatt Date: Sat, 3 Sep 2022 12:54:19 +0200 Subject: mastodon.el - require mastodon-http --- lisp/mastodon.el | 1 + 1 file changed, 1 insertion(+) (limited to 'lisp') diff --git a/lisp/mastodon.el b/lisp/mastodon.el index 5c2bc34..a85a7f7 100644 --- a/lisp/mastodon.el +++ b/lisp/mastodon.el @@ -33,6 +33,7 @@ ;;; Code: (require 'cl-lib) ; for `cl-some' call in mastodon +(require 'mastodon-http) (require 'mastodon-toot) (declare-function discover-add-context-menu "discover") -- cgit v1.2.3