mirror of
https://github.com/tanrax/org-social.el
synced 2026-01-07 05:43:32 +01:00
813 lines
50 KiB
EmacsLisp
813 lines
50 KiB
EmacsLisp
;;; org-social-relay.el --- Relay functionality for Org-social -*- lexical-binding: t -*-
|
|
|
|
;; SPDX-License-Identifier: GPL-3.0
|
|
|
|
;; Author: Andros Fenollosa <hi@andros.dev>
|
|
;; Version: 2.10
|
|
;; URL: https://github.com/tanrax/org-social.el
|
|
|
|
;;; Commentary:
|
|
|
|
;; Relay integration for Org-social, providing automatic feed registration
|
|
;; and endpoint discovery for enhanced social features.
|
|
|
|
;;; Code:
|
|
|
|
(require 'json)
|
|
(require 'cl-lib)
|
|
(require 'org-social-variables)
|
|
|
|
;; Declare request function
|
|
(declare-function request "request" (url &rest args))
|
|
|
|
;; Forward declarations
|
|
(declare-function url-hexify-string "url-util" (string))
|
|
|
|
(defun org-social-relay--discover-endpoints (relay-url callback)
|
|
"Discover relay endpoints by exploring the root API.
|
|
RELAY-URL is the base URL of the relay server.
|
|
Call CALLBACK with discovered endpoints."
|
|
(request relay-url
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(links (cdr (assoc '_links response)))
|
|
(endpoints (make-hash-table :test 'equal)))
|
|
;; Parse _links structure - can be either object (new format) or array (old format)
|
|
(cond
|
|
;; Check if this is the new object format by looking for 'href key in first element
|
|
((and (listp links)
|
|
(listp (car links))
|
|
(assoc 'href (cdr (car links))))
|
|
;; New format: _links is an alist with endpoint names as keys
|
|
(dolist (endpoint-pair links)
|
|
(let* ((endpoint-name (symbol-name (car endpoint-pair)))
|
|
(endpoint-data (cdr endpoint-pair))
|
|
(href (cdr (assoc 'href endpoint-data)))
|
|
(method (cdr (assoc 'method endpoint-data))))
|
|
(when (and href method)
|
|
(puthash endpoint-name (list :href href :method method) endpoints)))))
|
|
;; Old format: _links is an array of objects with rel, href, method
|
|
(t
|
|
(let ((links-list (if (vectorp links)
|
|
(append links nil)
|
|
links)))
|
|
(dolist (link links-list)
|
|
(let ((rel (cdr (assoc 'rel link)))
|
|
(href (cdr (assoc 'href link)))
|
|
(method (cdr (assoc 'method link))))
|
|
(when (and rel href method)
|
|
(puthash rel (list :href href :method method) endpoints)))))))
|
|
(funcall callback endpoints))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay API response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to discover relay endpoints: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))))))
|
|
|
|
(defun org-social-relay--register-feed ()
|
|
"Register the user's feed in the configured relay server."
|
|
(when (and org-social-relay
|
|
org-social-my-public-url
|
|
(not (string-empty-p org-social-relay))
|
|
(not (string-empty-p org-social-my-public-url)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/"))
|
|
(feed-url org-social-my-public-url))
|
|
(message "Discovering relay endpoints: %s" relay-url)
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((add-feed-endpoint (gethash "add-feed" endpoints)))
|
|
(if add-feed-endpoint
|
|
(let ((href (plist-get add-feed-endpoint :href))
|
|
(method (plist-get add-feed-endpoint :method)))
|
|
(message "Registering feed using endpoint: %s %s" method href)
|
|
(request (concat relay-url href)
|
|
:type method
|
|
:data (json-encode `(("feed" . ,feed-url)))
|
|
:headers '(("Content-Type" . "application/json"))
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key _data &allow-other-keys)
|
|
(message "Feed successfully registered in relay")))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to register feed in relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))))))
|
|
(message "add-feed endpoint not found in relay API"))))))))
|
|
|
|
(defun org-social-relay--fetch-feeds (callback)
|
|
"Fetch list of feeds from the relay server.
|
|
Call CALLBACK with the list of feed URLs."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((feeds-endpoint (gethash "feeds" endpoints)))
|
|
(if feeds-endpoint
|
|
(let ((href (plist-get feeds-endpoint :href))
|
|
(method (plist-get feeds-endpoint :method)))
|
|
(request (concat relay-url href)
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(feeds-data (cdr (assoc 'data response))))
|
|
;; Handle both vectors and lists
|
|
(let ((feeds-list (if (vectorp feeds-data)
|
|
(append feeds-data nil)
|
|
feeds-data)))
|
|
(funcall callback feeds-list)))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay feeds response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch feeds from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil)))))
|
|
(message "feeds endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
|
|
(defun org-social-relay--fetch-notifications (callback &optional type)
|
|
"Fetch notifications for the user's feed from the relay server.
|
|
Call CALLBACK with the list of notification objects
|
|
\(mentions, reactions, replies).
|
|
Optional TYPE filters by notification type:
|
|
\\='mention, \\='reaction, or \\='reply."
|
|
(when (and org-social-relay
|
|
org-social-my-public-url
|
|
(not (string-empty-p org-social-relay))
|
|
(not (string-empty-p org-social-my-public-url)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/"))
|
|
(feed-url org-social-my-public-url))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((notifications-endpoint (gethash "notifications" endpoints)))
|
|
(if notifications-endpoint
|
|
(let ((href (plist-get notifications-endpoint :href))
|
|
(method (plist-get notifications-endpoint :method)))
|
|
(let ((url (if type
|
|
(concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(feed_url\\|url feed\\)}"
|
|
(url-hexify-string feed-url)
|
|
href)
|
|
(format "&type=%s" (symbol-name type)))
|
|
(concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(feed_url\\|url feed\\)}"
|
|
(url-hexify-string feed-url)
|
|
href)))))
|
|
(request url
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(notifications-data (cdr (assoc 'data response))))
|
|
(if (string= response-type "Success")
|
|
(let ((notifications-list (if (vectorp notifications-data)
|
|
(append notifications-data nil)
|
|
notifications-data)))
|
|
(funcall callback notifications-list))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay notifications response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch notifications from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil))))))
|
|
(message "notifications endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
(defun org-social-relay--fetch-mentions (callback)
|
|
"Fetch mentions for the user's feed from the relay server.
|
|
Call CALLBACK with the list of post URLs that mention the user."
|
|
(when (and org-social-relay
|
|
org-social-my-public-url
|
|
(not (string-empty-p org-social-relay))
|
|
(not (string-empty-p org-social-my-public-url)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/"))
|
|
(feed-url org-social-my-public-url))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((mentions-endpoint (gethash "mentions" endpoints)))
|
|
(if mentions-endpoint
|
|
(let ((href (plist-get mentions-endpoint :href))
|
|
(method (plist-get mentions-endpoint :method)))
|
|
;; Replace {feed_url} or {url feed} placeholder with actual feed URL
|
|
(let ((url (concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(feed_url\\|url feed\\)}"
|
|
(url-hexify-string feed-url)
|
|
href))))
|
|
(request url
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(mentions-data (cdr (assoc 'data response))))
|
|
;; Check if response is successful
|
|
(if (string= response-type "Success")
|
|
(progn
|
|
;; Handle both vectors and lists
|
|
(let ((mentions-list (if (vectorp mentions-data)
|
|
(append mentions-data nil)
|
|
mentions-data)))
|
|
(funcall callback mentions-list)))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay mentions response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch mentions from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil))))))
|
|
(message "mentions endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
(defun org-social-relay--fetch-replies (post-url callback)
|
|
"Fetch replies for POST-URL from the relay server.
|
|
Call CALLBACK with the thread structure."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((replies-endpoint (gethash "replies" endpoints)))
|
|
(if replies-endpoint
|
|
(let ((href (plist-get replies-endpoint :href))
|
|
(method (plist-get replies-endpoint :method)))
|
|
(let ((url (concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(post_url\\|url post\\)}"
|
|
(url-hexify-string post-url)
|
|
href))))
|
|
(request url
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(replies-data (cdr (assoc 'data response))))
|
|
(if (string= response-type "Success")
|
|
(let ((replies-list (if (vectorp replies-data)
|
|
(append replies-data nil)
|
|
replies-data)))
|
|
(funcall callback replies-list))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay replies response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch replies from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil))))))
|
|
(message "replies endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
(defun org-social-relay--fetch-interactions (post-url callback)
|
|
"Fetch all interactions (reactions, replies, boosts) for POST-URL.
|
|
Fetches from the relay server.
|
|
Call CALLBACK with the interactions data structure."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((interactions-endpoint (gethash "interactions" endpoints)))
|
|
(if interactions-endpoint
|
|
(let ((href (plist-get interactions-endpoint :href))
|
|
(method (plist-get interactions-endpoint :method)))
|
|
(let ((url (concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(post_url\\|url post\\|post\\)}"
|
|
(url-hexify-string post-url)
|
|
href))))
|
|
(request url
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(interactions-data (cdr (assoc 'data response))))
|
|
(if (string= response-type "Success")
|
|
(funcall callback interactions-data)
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay interactions response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch interactions from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil))))))
|
|
(message "interactions endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
(defun org-social-relay--search-posts (query callback &optional search-type page per-page)
|
|
"Search posts by QUERY from the relay server.
|
|
SEARCH-TYPE can be \\='tag to search by tag, defaults to text search.
|
|
PAGE is the page number (default 1).
|
|
PER-PAGE is results per page (default 10, max 50).
|
|
Call CALLBACK with the list of matching post URLs and metadata."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((search-endpoint (gethash "search" endpoints)))
|
|
(if search-endpoint
|
|
(let ((href (plist-get search-endpoint :href))
|
|
(method (plist-get search-endpoint :method)))
|
|
;; Replace {query} placeholder in href with actual query
|
|
(let* ((base-url (concat relay-url
|
|
(replace-regexp-in-string
|
|
"{query}"
|
|
(url-hexify-string query)
|
|
;; Use correct parameter name based on search type
|
|
(replace-regexp-in-string
|
|
"\\?q="
|
|
(if (eq search-type 'tag) "?tag=" "?q=")
|
|
href))))
|
|
;; Add pagination parameters
|
|
(url (concat base-url
|
|
(when page (format "&page=%d" page))
|
|
(when per-page (format "&perPage=%d" per-page)))))
|
|
(request url
|
|
:type method
|
|
:timeout 15
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(search-data (cdr (assoc 'data response)))
|
|
(meta-data (cdr (assoc 'meta response))))
|
|
(if (string= response-type "Success")
|
|
(let ((search-list (if (vectorp search-data)
|
|
(append search-data nil)
|
|
search-data)))
|
|
;; Call callback with both data and metadata
|
|
(funcall callback search-list meta-data))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil nil)))
|
|
(error
|
|
(message "Failed to parse relay search response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to search posts from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil nil))))))
|
|
(message "search endpoint not found in relay API")
|
|
(funcall callback nil nil))))))))
|
|
|
|
|
|
(defun org-social-relay--fetch-groups-from-url (relay-url callback)
|
|
"Fetch list of groups from RELAY-URL.
|
|
Call CALLBACK with a list of alists, each containing (name . NAME),
|
|
\(href . HREF), (method . METHOD), and (relay-url . RELAY-URL).
|
|
NAME is the display name of the group (e.g., \\='Org Mode\\=').
|
|
HREF is the relay endpoint for the group (e.g., \\='/groups/org-mode/\\=').
|
|
METHOD is the HTTP method to use (e.g., \\='GET\\=').
|
|
RELAY-URL is the base URL of the relay server."
|
|
(when (and relay-url (not (string-empty-p relay-url)))
|
|
(let ((relay-url (string-trim-right relay-url "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((groups-endpoint (gethash "groups" endpoints)))
|
|
(if groups-endpoint
|
|
(let ((href (plist-get groups-endpoint :href))
|
|
(method (plist-get groups-endpoint :method)))
|
|
(request (concat relay-url href)
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(links (cdr (assoc '_links response)))
|
|
(groups-links (cdr (assoc 'groups links))))
|
|
(if (string= response-type "Success")
|
|
;; Extract name and href from _links.groups
|
|
(let* ((groups-array (if (vectorp groups-links)
|
|
(append groups-links nil)
|
|
groups-links))
|
|
(groups-list
|
|
(mapcar (lambda (group-obj)
|
|
(let ((name (cdr (assoc 'name group-obj)))
|
|
(href (cdr (assoc 'href group-obj)))
|
|
(method (cdr (assoc 'method group-obj))))
|
|
`((name . ,name)
|
|
(href . ,href)
|
|
(method . ,method)
|
|
(relay-url . ,relay-url))))
|
|
groups-array)))
|
|
(funcall callback groups-list))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay groups response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch groups from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil)))))
|
|
(message "groups endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
(defun org-social-relay--fetch-groups (callback)
|
|
"Fetch list of groups from the default relay server (org-social-relay).
|
|
Call CALLBACK with a list of alists, each containing (name . NAME),
|
|
\(href . HREF), (method . METHOD), and (relay-url . RELAY-URL).
|
|
This is a wrapper around `org-social-relay--fetch-groups-from-url\\=`
|
|
for backward compatibility."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(org-social-relay--fetch-groups-from-url org-social-relay callback)))
|
|
|
|
(defun org-social-relay--fetch-group-posts (group-href group-method callback)
|
|
"Fetch posts from GROUP-HREF from the relay server.
|
|
GROUP-HREF is the endpoint path for the group (e.g., \\='/groups/org-mode/\\=').
|
|
GROUP-METHOD is the HTTP method to use (e.g., \\='GET\\=').
|
|
Call CALLBACK with the list of group posts."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(request (concat relay-url group-href)
|
|
:type group-method
|
|
:timeout 15
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(posts-data (cdr (assoc 'data response))))
|
|
(if (string= response-type "Success")
|
|
(let ((posts-list (if (vectorp posts-data)
|
|
(append posts-data nil)
|
|
posts-data)))
|
|
(funcall callback posts-list))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay group posts response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch group posts from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil)))))))
|
|
|
|
(defun org-social-relay--fetch-polls (callback)
|
|
"Fetch polls from the relay server.
|
|
Call CALLBACK with the list of poll URLs."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((polls-endpoint (gethash "polls" endpoints)))
|
|
(if polls-endpoint
|
|
(let ((href (plist-get polls-endpoint :href))
|
|
(method (plist-get polls-endpoint :method)))
|
|
(request (concat relay-url href)
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(polls-data (cdr (assoc 'data response))))
|
|
(if (string= response-type "Success")
|
|
(let ((polls-list (if (vectorp polls-data)
|
|
(append polls-data nil)
|
|
polls-data)))
|
|
(funcall callback polls-list))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay polls response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch polls from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil)))))
|
|
(message "polls endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
(defun org-social-relay--fetch-poll-votes (post-url callback)
|
|
"Fetch votes for poll at POST-URL from the relay server.
|
|
Call CALLBACK with the poll votes data."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((poll-votes-endpoint (gethash "poll-votes" endpoints)))
|
|
(if poll-votes-endpoint
|
|
(let ((href (plist-get poll-votes-endpoint :href))
|
|
(method (plist-get poll-votes-endpoint :method)))
|
|
(let ((url (concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(post_url\\|url post\\)}"
|
|
(url-hexify-string post-url)
|
|
href))))
|
|
(request url
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(votes-data (cdr (assoc 'data response))))
|
|
(if (string= response-type "Success")
|
|
(let ((votes-list (if (vectorp votes-data)
|
|
(append votes-data nil)
|
|
votes-data)))
|
|
(funcall callback votes-list))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay poll votes response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch poll votes from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil))))))
|
|
(message "poll-votes endpoint not found in relay API")
|
|
(funcall callback nil))))))))
|
|
|
|
;; Utility functions for getting data synchronously (for compatibility)
|
|
(defun org-social-relay--get-timeline ()
|
|
"Get timeline from relay (fallback to local feeds if relay unavailable)."
|
|
(if (and org-social-relay (not (string-empty-p org-social-relay)))
|
|
;; For now, use feed-based approach until we implement full timeline endpoint
|
|
(when (fboundp 'org-social-feed--get-timeline)
|
|
(org-social-feed--get-timeline))
|
|
;; Fallback to local feeds
|
|
(when (fboundp 'org-social-feed--get-timeline)
|
|
(org-social-feed--get-timeline))))
|
|
|
|
(defun org-social-relay--get-thread (post-url)
|
|
"Get thread for POST-URL from relay."
|
|
;; This will be implemented when we create the UI
|
|
(message "Getting thread for %s" post-url))
|
|
|
|
(defun org-social-relay--get-mentions (feed-url)
|
|
"Get mentions for FEED-URL from relay."
|
|
;; This will be implemented when we create the UI
|
|
(message "Getting mentions for %s" feed-url))
|
|
|
|
(defun org-social-relay--get-groups ()
|
|
"Get groups from relay."
|
|
;; This will be implemented when we create the UI
|
|
(message "Getting groups from relay"))
|
|
|
|
(defun org-social-relay--get-group-posts (group-name)
|
|
"Get posts for GROUP-NAME from relay."
|
|
;; This will be implemented when we create the UI
|
|
(message "Getting posts for group %s" group-name))
|
|
|
|
(defun org-social-relay--check-posts-for-replies (post-urls callback)
|
|
"Check multiple POST-URLS for replies and call CALLBACK with results.
|
|
CALLBACK receives an alist where each entry is (post-url . has-replies).
|
|
Designed for small batches of posts (e.g., current page)."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay))
|
|
post-urls)
|
|
(let ((relay-url (string-trim-right org-social-relay "/"))
|
|
(results '())
|
|
(completed 0)
|
|
(total (length post-urls)))
|
|
(org-social-relay--discover-endpoints
|
|
relay-url
|
|
(lambda (endpoints)
|
|
(let ((replies-endpoint (gethash "replies" endpoints)))
|
|
(if replies-endpoint
|
|
(let ((href (plist-get replies-endpoint :href))
|
|
(method (plist-get replies-endpoint :method)))
|
|
(dolist (post-url post-urls)
|
|
(let ((url (concat relay-url
|
|
(replace-regexp-in-string
|
|
"{\\(post_url\\|url post\\)}"
|
|
(url-hexify-string post-url)
|
|
href))))
|
|
(request url
|
|
:type method
|
|
:timeout 10
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case _err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(replies-data (cdr (assoc 'data response))))
|
|
(let ((has-replies
|
|
(and (string= response-type "Success")
|
|
replies-data
|
|
(or (and (listp replies-data) (> (length replies-data) 0))
|
|
(and (vectorp replies-data) (> (length replies-data) 0))))))
|
|
(push (cons post-url has-replies) results)
|
|
(setq completed (1+ completed))
|
|
(when (= completed total)
|
|
(funcall callback results))))
|
|
(progn
|
|
(push (cons post-url nil) results)
|
|
(setq completed (1+ completed))
|
|
(when (= completed total)
|
|
(funcall callback results))))
|
|
(error
|
|
(push (cons post-url nil) results)
|
|
(setq completed (1+ completed))
|
|
(when (= completed total)
|
|
(funcall callback results))))))
|
|
:error (cl-function
|
|
(lambda (&key &allow-other-keys)
|
|
(push (cons post-url nil) results)
|
|
(setq completed (1+ completed))
|
|
(when (= completed total)
|
|
(funcall callback results))))))))
|
|
(progn
|
|
(message "replies endpoint not found in relay API")
|
|
(funcall callback nil)))))))))
|
|
|
|
(defun org-social-relay--check-post-has-replies (post-url callback)
|
|
"Check if POST-URL has replies from the relay server.
|
|
Call CALLBACK with t if post has replies, nil otherwise."
|
|
(org-social-relay--check-posts-for-replies
|
|
(list post-url)
|
|
(lambda (results)
|
|
(let ((result (alist-get post-url results nil nil 'string=)))
|
|
(funcall callback result)))))
|
|
|
|
(defun org-social-relay--fetch-group-details (group-href group-method callback)
|
|
"Fetch complete group details including posts and metadata from GROUP-HREF.
|
|
GROUP-HREF is the endpoint path for the group (e.g., \\='/groups/org-mode/\\=').
|
|
GROUP-METHOD is the HTTP method to use (e.g., \\='GET\\=').
|
|
Call CALLBACK with an alist containing \\='posts and \\='members keys."
|
|
(when (and org-social-relay
|
|
(not (string-empty-p org-social-relay)))
|
|
(let ((relay-url (string-trim-right org-social-relay "/")))
|
|
(request (concat relay-url group-href)
|
|
:type group-method
|
|
:timeout 15
|
|
:success (cl-function
|
|
(lambda (&key data &allow-other-keys)
|
|
(condition-case err
|
|
(if (and data (stringp data) (not (string-empty-p data)))
|
|
(let* ((response (json-read-from-string data))
|
|
(response-type (cdr (assoc 'type response)))
|
|
(posts-data (cdr (assoc 'data response)))
|
|
(meta-data (cdr (assoc 'meta response))))
|
|
(if (string= response-type "Success")
|
|
(let ((posts-list (if (vectorp posts-data)
|
|
(append posts-data nil)
|
|
posts-data))
|
|
(members-list (if meta-data
|
|
(let ((members (cdr (assoc 'members meta-data))))
|
|
(if (vectorp members)
|
|
(append members nil)
|
|
members))
|
|
'())))
|
|
(funcall callback `((posts . ,posts-list)
|
|
(members . ,members-list))))
|
|
(progn
|
|
(message "Relay returned error response: %s" response-type)
|
|
(funcall callback nil))))
|
|
(progn
|
|
(message "Received empty, nil, or non-string response from relay: %S" data)
|
|
(funcall callback nil)))
|
|
(error
|
|
(message "Failed to parse relay group details response: %s (data: %S)" (error-message-string err) data)
|
|
(funcall callback nil)))))
|
|
:error (cl-function
|
|
(lambda (&key error-thrown &allow-other-keys)
|
|
(message "Failed to fetch group details from relay: %s"
|
|
(if error-thrown
|
|
(error-message-string error-thrown)
|
|
"Unknown error"))
|
|
(funcall callback nil)))))))
|
|
|
|
(provide 'org-social-relay)
|
|
;;; org-social-relay.el ends here
|