Files
org-social.el/org-social-lib.md
2026-04-18 16:02:30 +02:00

44 KiB

org-social-lib.el — API Reference

A hypothetical standalone Emacs Lisp library extracted from org-social.el that exposes the core logic for working with Org Social feeds. Other packages or configurations can (require 'org-social-lib) to parse feeds, create posts, validate files, interact with a relay, or sync with a host — without depending on the full org-social UI.


Installation

(use-package org-social-lib
  :after org)

Dependencies: emacs >= 30.1, org >= 9.0, async-http-queue >= 0.1


Table of Contents


Configuration Variables

These variables configure the library's behavior. All have sensible defaults.


org-social-lib-file

Path to the user's social.org file.

(setq org-social-lib-file "~/social.org")

Type: string (file path or http(s):// URL for hosted files) Default: "~/social.org"


org-social-lib-relay

URL of the relay server. Set to nil to disable relay features.

(setq org-social-lib-relay "https://relay.org-social.org")

Type: string | nil Default: "https://relay.org-social.org"


org-social-lib-my-public-url

The public HTTP URL where your social.org is accessible. Required for relay registration and notifications.

(setq org-social-lib-my-public-url "https://example.com/social.org")

Type: string | nil Default: nil


org-social-lib-max-post-age-days

Only fetch posts newer than this many days. Set to nil to fetch all posts.

(setq org-social-lib-max-post-age-days 14)

Type: integer | nil Default: 14


org-social-lib-max-concurrent-downloads

Maximum number of feeds downloaded in parallel.

(setq org-social-lib-max-concurrent-downloads 20)

Type: integer Default: 20


org-social-lib-language-filter

List of BCP 47 language codes (2-5 lowercase letters, optionally followed by a subtag, e.g. "en", "pt-br", "zh-hans"). When set, only posts with a matching :LANG: property are included in the timeline. nil shows all posts.

(setq org-social-lib-language-filter '("en" "es"))

Type: (list string) | nil Default: nil


Parsing

Functions for extracting structured data from the raw text of a social.org file. All parsing functions are pure: they take a string and return an alist.


org-social-lib-parse-feed

(org-social-lib-parse-feed CONTENT) → profile-alist

Parses the full content of a social.org file. Returns a profile alist with all header metadata and a posts key containing the list of parsed posts.

Arguments:

  • CONTENTstring. Raw text of a social.org file.

Returns: A profile alist.

Example:

(let* ((content (with-temp-buffer
                  (insert-file-contents "~/social.org")
                  (buffer-string)))
       (profile (org-social-lib-parse-feed content)))
  (message "Nick: %s" (alist-get 'nick profile))
  (message "Posts: %d" (length (alist-get 'posts profile))))

org-social-lib-parse-posts

(org-social-lib-parse-posts CONTENT) → (list post-alist)

Extracts only the posts from a feed string, skipping profile headers. Useful when you only need to process posts.

Arguments:

  • CONTENTstring. Raw text of a social.org file.

Returns: List of post alists, ordered as they appear in the file (newest last unless manually sorted).

Example:

(let ((posts (org-social-lib-parse-posts content)))
  (dolist (post posts)
    (message "[%s] %s"
             (alist-get 'timestamp post)
             (alist-get 'text post))))

org-social-lib-get-header

(org-social-lib-get-header CONTENT KEYWORD) → string | (list string) | nil

Extracts a #+KEYWORD: value from a feed string. Returns nil if the keyword is not present. For keywords that can appear multiple times (FOLLOW, GROUP), returns a list of strings instead of a single string.

Arguments:

  • CONTENTstring. Raw feed text.
  • KEYWORDstring. Keyword name, e.g. "NICK", "TITLE", "AVATAR".

Returns: string | (list string) | nil

Example:

(org-social-lib-get-header content "NICK")      ; => "andros"
(org-social-lib-get-header content "AVATAR")    ; => "https://example.com/avatar.jpg"
(org-social-lib-get-header content "NONEXISTENT") ; => nil

org-social-lib-parse-follow-list

(org-social-lib-parse-follow-list CONTENT) → (list follow-alist)

Extracts all #+FOLLOW: entries from a feed string.

Returns: List of alists with 'name (optional) and 'url keys.

Example:

(dolist (follow (org-social-lib-parse-follow-list content))
  (message "Following %s at %s"
           (or (alist-get 'name follow) "unknown")
           (alist-get 'url follow)))

org-social-lib-parse-timestamp

(org-social-lib-parse-timestamp TIMESTAMP) → float-time | nil

Converts an RFC 3339 timestamp string to a float (Unix time). Returns nil if the string cannot be parsed.

Arguments:

  • TIMESTAMPstring. RFC 3339 format, e.g. "2025-03-10T09:00:00+01:00".

Returns: float | nil

Example:

(org-social-lib-parse-timestamp "2025-03-10T09:00:00+01:00") ; => 1741597200.0

org-social-lib-extract-mentioned-urls

(org-social-lib-extract-mentioned-urls TEXT) → (list string)

Finds all [[org-social:URL][...]] mention links in a post body and returns the list of feed URLs.

Arguments:

  • TEXTstring. Post body text.

Returns: List of feed URL strings.

Example:

(org-social-lib-extract-mentioned-urls
 "Hey [[org-social:https://shom.dev/social.org][shom]], what do you think?")
; => ("https://shom.dev/social.org")

Profile

High-level functions for reading and modifying your own social.org profile. All write operations persist changes to org-social-lib-file immediately and upload to the host if it is a vfile.


org-social-lib-get-my-profile

(org-social-lib-get-my-profile) → profile-alist

Reads and parses your own social.org file. Returns the full profile alist including the posts list.

This is a convenience wrapper around reading org-social-lib-file and calling org-social-lib-parse-feed. It always reads from disk, so the result reflects the current saved state.

Returns: profile alist

Example:

(let ((me (org-social-lib-get-my-profile)))
  (message "You are @%s with %d posts"
           (alist-get 'nick me)
           (length (alist-get 'posts me))))
; => "You are @andros with 42 posts"

org-social-lib-update-profile

(org-social-lib-update-profile &key nick title description avatar location
                                    birthday language link) → t

Updates one or more profile header keywords in org-social-lib-file. Only the keywords you pass are modified; the rest remain unchanged. Saves the file after editing.

Keyword Arguments: All optional. Any keyword not provided is left as-is.

  • :nickstring. New username (no spaces).
  • :titlestring. Feed title.
  • :descriptionstring. Bio.
  • :avatarstring. URL to profile image (JPG or PNG).
  • :locationstring. Geographic location.
  • :birthdaystring. Birth date.
  • :languagestring. Default language code.
  • :linkstring. Personal website URI.

Returns: t

Example:

(org-social-lib-update-profile
 :description "Software developer and Emacs enthusiast."
 :avatar      "https://example.com/new-avatar.jpg")
; => t

org-social-lib-follow

(org-social-lib-follow URL &optional nick) → t | error

Adds a #+FOLLOW: entry to org-social-lib-file and saves. Signals an error if URL is already in the follow list.

Arguments:

  • URLstring. Feed URL to follow (http(s)://...).
  • NICKstring | nil. Optional display name for the entry.

Returns: t

Example:

(org-social-lib-follow "https://shom.dev/social.org" "shom")
; Adds: #+FOLLOW: shom https://shom.dev/social.org
; => t

(org-social-lib-follow "https://shom.dev/social.org")
; Adds: #+FOLLOW: https://shom.dev/social.org
; => t

(org-social-lib-follow "https://shom.dev/social.org")
; => error: "Already following https://shom.dev/social.org"

org-social-lib-unfollow

(org-social-lib-unfollow URL) → t | error

Removes the #+FOLLOW: entry matching URL from org-social-lib-file and saves. Signals an error if the URL is not in the follow list.

Arguments:

  • URLstring. Feed URL to remove.

Returns: t

Example:

(org-social-lib-unfollow "https://shom.dev/social.org")
; Removes the matching #+FOLLOW: line
; => t

(org-social-lib-unfollow "https://unknown.example.com/social.org")
; => error: "Not following https://unknown.example.com/social.org"

org-social-lib-pin-post

(org-social-lib-pin-post TIMESTAMP) → t

Sets (or replaces) the #+PINNED: header in org-social-lib-file to the given post timestamp. Saves the file.

Arguments:

  • TIMESTAMPstring. RFC 3339 timestamp of the post to pin.

Returns: t

Example:

(org-social-lib-pin-post "2025-03-10T09:00:00+01:00")
; Sets: #+PINNED: 2025-03-10T09:00:00+01:00
; => t

Post Creation

Two levels of API are available:

  • High-level (org-social-lib-new-post, org-social-lib-new-reaction, etc.): write directly to the configured social.org file and save. Suitable for most use cases.
  • Low-level (org-social-lib-new-post-template, etc.): return the Org text as a string without touching any file. Useful when you manage the file yourself or need to preview before writing.

org-social-lib-new-post

(org-social-lib-new-post &key reply-url reply-id group visibility lang content) → string

Appends a new post to org-social-lib-file, saves the file, and returns the generated post URL (feed-url#timestamp).

If org-social-lib-file is a vfile, the file is uploaded to the host after saving.

Keyword Arguments:

  • :reply-urlstring | nil. Feed URL of the post being replied to.
  • :reply-idstring | nil. Timestamp ID of the post being replied to.
  • :groupstring | nil. Group entry in the form "Group Name https://relay.url".
  • :visibility"mention" | nil. Sets :VISIBILITY: mention.
  • :langstring | nil. Language code. Defaults to org-social-lib-default-lang.
  • :contentstring | nil. Post body text. If nil, opens the file and positions the cursor inside the new post for interactive editing.

Returns: string — the new post URL (feed-url#timestamp).

Example:

;; Non-interactive: post with content
(org-social-lib-new-post :content "Hello from org-social-lib!"
                         :lang "en")
; => "https://andros.dev/social.org#2025-03-10T09:00:00+01:00"

;; Interactive: opens buffer and positions cursor for typing
(org-social-lib-new-post)

;; Reply
(org-social-lib-new-post :reply-url "https://shom.dev/social.org"
                         :reply-id  "2025-03-09T18:00:00+01:00"
                         :content   "Totally agree!")

;; Mention-only post
(org-social-lib-new-post :visibility "mention"
                         :content    (format "Hey %s, check this out."
                                             (org-social-lib-mention-link
                                              "https://shom.dev/social.org"
                                              "shom")))

org-social-lib-new-reaction

(org-social-lib-new-reaction REPLY-URL REPLY-ID MOOD) → string

Appends a reaction post (emoji, no body) to org-social-lib-file, saves, and returns the new post URL.

Arguments:

  • REPLY-URLstring. Feed URL of the post being reacted to.
  • REPLY-IDstring. Timestamp ID of the post being reacted to.
  • MOODstring. Emoji or short text (< 200 chars).

Returns: string — the new post URL.

Example:

(org-social-lib-new-reaction "https://tanrax.com/social.org"
                             "2025-03-10T08:00:00+01:00"
                             "❤️")

org-social-lib-new-boost

(org-social-lib-new-boost POST-URL &optional comment) → string

Appends a boost (reshare) post to org-social-lib-file and saves.

Arguments:

  • POST-URLstring. Full post URL (feed-url#timestamp).
  • COMMENTstring | nil. Optional quote comment.

Returns: string — the new post URL.

Example:

(org-social-lib-new-boost
 "https://tanrax.com/social.org#2025-03-10T08:00:00+01:00"
 "This is worth reading.")

org-social-lib-new-poll

(org-social-lib-new-poll QUESTION OPTIONS DAYS &key lang) → string

Appends a poll post to org-social-lib-file and saves.

Arguments:

  • QUESTIONstring. The poll question.
  • OPTIONS(list string). List of option labels.
  • DAYSinteger. Days until the poll closes.
  • :langstring | nil.

Returns: string — the new post URL.

Example:

(org-social-lib-new-poll "Favorite Emacs feature?"
                         '("Org Mode" "Magit" "LSP" "The extensibility")
                         7
                         :lang "en")

org-social-lib-vote-poll

(org-social-lib-vote-poll POST-URL OPTION) → string | error

Submits a vote on a poll by appending a poll-vote post to org-social-lib-file and saving. Validates that OPTION exists in the original poll before writing.

Arguments:

  • POST-URLstring. Full URL of the poll post (feed-url#timestamp).
  • OPTIONstring. The option text exactly as it appears in the poll.

Returns: string — the new vote post URL. Signals an error if the option does not exist in the poll or the poll has already closed.

Example:

(org-social-lib-vote-poll
 "https://andros.dev/social.org#2025-03-10T12:00:00+01:00"
 "Org Mode")
; => "https://me.example.com/social.org#2025-03-10T12:05:00+01:00"

(org-social-lib-vote-poll
 "https://andros.dev/social.org#2025-03-10T12:00:00+01:00"
 "Nonexistent option")
; => error: "Option not found in poll"

org-social-lib-delete-post

(org-social-lib-delete-post TIMESTAMP) → t | error

Removes the post with the given timestamp from org-social-lib-file and saves. Signals an error if no post with that timestamp is found.

Arguments:

  • TIMESTAMPstring. RFC 3339 timestamp identifying the post.

Returns: t

Example:

(org-social-lib-delete-post "2025-03-10T09:00:00+01:00")
; => t

(org-social-lib-delete-post "1999-01-01T00:00:00+00:00")
; => error: "Post not found: 1999-01-01T00:00:00+00:00"

org-social-lib-new-migration

(org-social-lib-new-migration OLD-URL NEW-URL) → string

Creates a migration post in org-social-lib-file and saves. This is the high-level wrapper: it writes the post, updates any self-referential #+FOLLOW: entries that point to OLD-URL, and returns the new post URL.

Arguments:

  • OLD-URLstring. Your previous social.org public URL.
  • NEW-URLstring. Your new social.org public URL.

Returns: string — the new migration post URL.

Example:

(org-social-lib-new-migration
 "https://old-server.com/social.org"
 "https://new-server.com/social.org")
; => "https://new-server.com/social.org#2025-03-10T15:00:00+01:00"

Low-level template functions

The following functions return the Org text as a string without writing to any file. Use them when you need to preview, transform, or manage file writes yourself.


org-social-lib-generate-timestamp

(org-social-lib-generate-timestamp) → string

Generates a new RFC 3339 timestamp for use as a post ID.

Returns: string — e.g. "2025-03-10T09:00:00+01:00"

Example:

(org-social-lib-generate-timestamp) ; => "2025-03-10T09:00:00+01:00"

org-social-lib-new-post-template

(org-social-lib-new-post-template &key reply-url reply-id group visibility lang) → string

Returns the Org text for a new post, ready to be inserted at the end of the * Posts section.

Keyword Arguments:

  • :reply-urlstring | nil. Feed URL of the post being replied to.
  • :reply-idstring | nil. Timestamp ID of the post being replied to.
  • :groupstring | nil. Group entry in the form "Group Name https://relay.url".
  • :visibility"mention" | nil. Sets :VISIBILITY: mention. Defaults to no visibility property.
  • :langstring | nil. Language code. Defaults to org-social-lib-default-lang.

Returns: string — Org Mode text block.

Example:

;; Simple post
(insert (org-social-lib-new-post-template))

;; Reply
(insert (org-social-lib-new-post-template
         :reply-url "https://shom.dev/social.org"
         :reply-id  "2025-03-09T18:00:00+01:00"))

;; Mention-only post
(insert (org-social-lib-new-post-template :visibility "mention" :lang "es"))

Generated output example:

** 2025-03-10T09:00:00+01:00
:PROPERTIES:
:LANG: en
:TAGS: 
:CLIENT: my-package
:REPLY_TO: https://shom.dev/social.org#2025-03-09T18:00:00+01:00
:END:


org-social-lib-new-reaction-template

(org-social-lib-new-reaction-template REPLY-URL REPLY-ID MOOD) → string

Returns the Org text for a reaction post (emoji response with no body).

Arguments:

  • REPLY-URLstring. Feed URL of the post being reacted to.
  • REPLY-IDstring. Timestamp ID of the post being reacted to.
  • MOODstring. Emoji or short text (< 200 chars).

Returns: string

Example:

(insert (org-social-lib-new-reaction-template
         "https://tanrax.com/social.org"
         "2025-03-10T08:00:00+01:00"
         "👍"))

org-social-lib-new-boost-template

(org-social-lib-new-boost-template POST-URL &optional comment) → string

Returns the Org text for a boost (reshare), optionally with a quote comment.

Arguments:

  • POST-URLstring. Full post URL (feed-url#timestamp).
  • COMMENTstring | nil. Optional text accompanying the boost.

Returns: string

Example:

;; Simple boost
(insert (org-social-lib-new-boost-template
         "https://tanrax.com/social.org#2025-03-10T08:00:00+01:00"))

;; Quote boost
(insert (org-social-lib-new-boost-template
         "https://tanrax.com/social.org#2025-03-10T08:00:00+01:00"
         "This is worth reading."))

org-social-lib-new-poll-template

(org-social-lib-new-poll-template QUESTION OPTIONS DAYS &key lang) → string

Returns the Org text for a poll post.

Arguments:

  • QUESTIONstring. The poll question (used as the post body header).
  • OPTIONS(list string). List of option labels.
  • DAYSinteger. How many days until the poll closes.
  • :langstring | nil. Language code.

Returns: string

Example:

(insert (org-social-lib-new-poll-template
         "Favorite Emacs feature?"
         '("Org Mode" "Magit" "LSP" "The extensibility")
         7
         :lang "en"))

Generated output:

** 2025-03-10T12:00:00+01:00
:PROPERTIES:
:LANG: en
:POLL_END: 2025-03-17T12:00:00+01:00
:END:

Favorite Emacs feature?

- [ ] Org Mode
- [ ] Magit
- [ ] LSP
- [ ] The extensibility

org-social-lib-new-migration-template

(org-social-lib-new-migration-template OLD-URL NEW-URL) → string

Returns the Org text for an account migration post.

Arguments:

  • OLD-URLstring. The old feed URL.
  • NEW-URLstring. The new feed URL.

Returns: string

Example:

(insert (org-social-lib-new-migration-template
         "https://old-server.com/social.org"
         "https://new-server.com/social.org"))

Validation

Functions for validating a social.org buffer or string. Validators return structured error lists rather than signaling errors, so callers can decide how to surface them.


org-social-lib-validate-buffer

(org-social-lib-validate-buffer) → (list error-plist)

Validates the current buffer as a social.org file. Must be called with the target buffer current.

Returns: List of error plists, each with :line, :column, :message, :suggestion, and :context keys. Returns nil if valid.

Example:

(with-current-buffer (find-file-noselect "~/social.org")
  (let ((errors (org-social-lib-validate-buffer)))
    (if errors
        (dolist (err errors)
          (message "Line %d: %s"
                   (plist-get err :line)
                   (plist-get err :message)))
      (message "File is valid."))))

org-social-lib-validate-string

(org-social-lib-validate-string CONTENT) → (list error-plist)

Validates a social.org string without requiring a buffer visit.

Arguments:

  • CONTENTstring. Raw feed content.

Returns: List of error plists (same format as org-social-lib-validate-buffer) or nil.

Example:

(let ((errors (org-social-lib-validate-string my-feed-string)))
  (unless errors
    (message "Feed is valid")))

org-social-lib-validate-post

(org-social-lib-validate-post POST) → (list error-plist) | nil

Validates a single post alist against the specification rules.

Arguments:

Returns: List of error plists (same format as org-social-lib-validate-buffer), or nil if valid.

Example:

(let* ((posts (org-social-lib-parse-posts content))
       (first-post (car posts))
       (errors (org-social-lib-validate-post first-post)))
  (when errors
    (message "Invalid post: %s" (car errors))))

Feed Fetching

Asynchronous functions for downloading and parsing remote feeds. All network calls are non-blocking.


org-social-lib-fetch-feed

(org-social-lib-fetch-feed URL CALLBACK &key timeout filter-date)

Fetches a single feed asynchronously and calls CALLBACK with the parsed content.

Arguments:

  • URLstring. The feed URL.
  • CALLBACKfunction. Called with (content) on success, nil on failure.
  • :timeoutinteger. Seconds before giving up. Default: 5.
  • :filter-datestring | nil. RFC 3339 date: only include posts on or after this date.

Example:

(org-social-lib-fetch-feed
 "https://shom.dev/social.org"
 (lambda (content)
   (if content
       (let ((profile (org-social-lib-parse-feed content)))
         (message "Fetched %d posts from %s"
                  (length (alist-get 'posts profile))
                  (alist-get 'nick profile)))
     (message "Failed to fetch feed"))))

org-social-lib-fetch-feeds

(org-social-lib-fetch-feeds URLS CALLBACK &key max-concurrent timeout filter-date)

Fetches multiple feeds in parallel. Calls CALLBACK once when all downloads complete.

Arguments:

  • URLS(list string). List of feed URLs.
  • CALLBACKfunction. Called with a list of (url . content) pairs. content is nil for failed downloads.
  • :max-concurrentinteger. Parallel download limit. Default: org-social-lib-max-concurrent-downloads.
  • :timeoutinteger. Per-feed timeout in seconds. Default: 5.
  • :filter-datestring | nil. Only include posts on or after this date.

Example:

(org-social-lib-fetch-feeds
 '("https://shom.dev/social.org"
   "https://tanrax.com/social.org"
   "https://notxor.nueva-actitud.org/social.org")
 (lambda (results)
   (dolist (pair results)
     (let ((url (car pair))
           (content (cdr pair)))
       (if content
           (message "OK: %s" url)
         (message "FAILED: %s" url))))))

org-social-lib-fetch-feeds-from-profile

(org-social-lib-fetch-feeds-from-profile PROFILE CALLBACK &key max-concurrent timeout)

Convenience wrapper that reads the follow list from a profile alist and fetches all followed feeds.

Arguments:

  • PROFILE — profile alist. Must include a 'follow key.
  • CALLBACKfunction. Called with a list of (url . content) pairs.

Example:

(let ((my-profile (org-social-lib-parse-feed
                   (with-temp-buffer
                     (insert-file-contents org-social-lib-file)
                     (buffer-string)))))
  (org-social-lib-fetch-feeds-from-profile
   my-profile
   (lambda (results)
     (message "Downloaded %d feeds" (length results)))))

Timeline

Functions for assembling and filtering a unified timeline from multiple feeds.


org-social-lib-get-timeline

(org-social-lib-get-timeline CALLBACK &key max-concurrent filter-date languages
                                           exclude-reactions)

High-level entry point. Reads your follow list from org-social-lib-file, downloads all followed feeds in parallel, assembles and filters the timeline, and calls CALLBACK with the result. Equivalent to calling get-my-profilefetch-feeds-from-profilebuild-timelinefilter-timeline in sequence.

Keyword Arguments:

  • :max-concurrentinteger. Parallel download limit. Default: org-social-lib-max-concurrent-downloads.
  • :filter-datestring | nil. RFC 3339 cutoff date. Default: computed from org-social-lib-max-post-age-days.
  • :languages(list string) | nil. Language filter. Default: org-social-lib-language-filter.
  • :exclude-reactionsboolean. Strip pure emoji reactions. Default: t.

CALLBACK receives (timeline) — a sorted list of post alists.

Example:

(org-social-lib-get-timeline
 (lambda (timeline)
   (message "Timeline ready: %d posts" (length timeline))
   (let ((first (car timeline)))
     (message "Latest post by @%s: %s"
              (alist-get 'author-nick first)
              (alist-get 'text first)))))
; => "Timeline ready: 87 posts"
; => "Latest post by @shom: Just discovered org-roam v2..."

org-social-lib-build-timeline

(org-social-lib-build-timeline FEEDS) → (list post-alist)

Merges posts from multiple feed results into a single sorted timeline. Applies visibility rules.

Arguments:

  • FEEDS — list of (url . content) pairs, as returned by org-social-lib-fetch-feeds.

Returns: List of post alists, sorted by date descending, with author metadata attached ('author-nick, 'author-url, 'author-avatar).

Example:

(org-social-lib-fetch-feeds
 my-follow-urls
 (lambda (results)
   (let ((timeline (org-social-lib-build-timeline results)))
     (message "Timeline has %d posts" (length timeline)))))

org-social-lib-filter-timeline

(org-social-lib-filter-timeline TIMELINE &key exclude-reactions exclude-groups
                                          languages my-url)

Applies filters to a timeline list.

Keyword Arguments:

  • :exclude-reactionsboolean. Remove posts that are pure emoji reactions. Default: t.
  • :exclude-groupsboolean. Remove group posts. Default: nil.
  • :languages(list string) | nil. Keep only posts with a matching :LANG: property.
  • :my-urlstring | nil. Your feed URL. Required to evaluate VISIBILITY: mention posts.

Returns: Filtered list of post alists.

Example:

(let ((visible-posts
       (org-social-lib-filter-timeline
        timeline
        :exclude-reactions t
        :languages '("en" "es")
        :my-url "https://andros.dev/social.org")))
  (message "%d posts after filtering" (length visible-posts)))

org-social-lib-find-mentions

(org-social-lib-find-mentions TIMELINE MY-NICK MY-URL) → (list post-alist)

Scans a timeline and returns posts that mention the given user.

Arguments:

  • TIMELINE — list of post alists.
  • MY-NICKstring. Your username.
  • MY-URLstring. Your feed URL.

Returns: List of post alists where the body contains [[org-social:MY-URL][...]].

Example:

(let ((mentions (org-social-lib-find-mentions timeline "andros" my-url)))
  (message "You have %d mentions" (length mentions)))

org-social-lib-find-replies

(org-social-lib-find-replies TIMELINE MY-URL) → (list post-alist)

Returns posts from the timeline that are replies to any of your posts.

Arguments:

  • TIMELINE — list of post alists.
  • MY-URLstring. Your feed URL.

Returns: List of post alists with (alist-get 'reply_to post) matching MY-URL#....


Notifications

High-level functions that aggregate notification data from multiple sources (local timeline scan + relay) into a single result.


org-social-lib-get-notifications

(org-social-lib-get-notifications CALLBACK &key sources)

Collects all notifications for the current user and calls CALLBACK with the combined, date-sorted list. By default it queries both the local timeline and the relay.

Keyword Arguments:

  • :sources(list symbol) | nil. Which sources to query. Accepted values: 'local (scan downloaded timeline), 'relay (query relay mentions/replies endpoint). Default: '(local relay).

CALLBACK receives (notifications) — a list of notification alists sorted by date descending.

Each notification alist has:

  • 'type'mention, 'reply, 'reaction, 'active-poll, or 'poll-result
  • 'authorstring. Nick of the person who triggered the notification.
  • 'author-urlstring. Feed URL of that person.
  • 'timestampstring. RFC 3339 timestamp of the source post.
  • 'post-urlstring. Full URL of the source post.
  • 'textstring | nil. Preview of the post text.

Example:

(org-social-lib-get-notifications
 (lambda (notifications)
   (message "You have %d notifications" (length notifications))
   (dolist (n notifications)
     (pcase (alist-get 'type n)
       ('mention  (message "@%s mentioned you" (alist-get 'author n)))
       ('reply    (message "@%s replied to your post" (alist-get 'author n)))
       ('reaction (message "@%s reacted to your post" (alist-get 'author n))))))
 :sources '(local relay))
; => "You have 5 notifications"
; => "@shom mentioned you"
; => "@tanrax replied to your post"

org-social-lib-get-thread

(org-social-lib-get-thread POST-URL CALLBACK)

Fetches the full thread for a post: retrieves all replies from the relay, then fetches the source feed for each reply to get the full post data. Calls CALLBACK with the assembled thread.

Arguments:

  • POST-URLstring. Full post URL (feed-url#timestamp).
  • CALLBACKfunction. Called with (thread) — a list of post alists sorted by date ascending (chronological order), not including the original post itself.

Example:

(org-social-lib-get-thread
 "https://andros.dev/social.org#2025-03-10T09:00:00+01:00"
 (lambda (thread)
   (message "Thread has %d replies" (length thread))
   (dolist (reply thread)
     (message "  @%s: %s"
              (alist-get 'author-nick reply)
              (alist-get 'text reply)))))
; => "Thread has 3 replies"
; => "  @shom: Great point!"
; => "  @tanrax: Totally agree."
; => "  @user3: Thanks for sharing."

Relay

All relay functions are asynchronous and use a callback pattern. They discover relay endpoints dynamically, so no hardcoded paths are needed.


org-social-lib-relay-register

(org-social-lib-relay-register RELAY-URL FEED-URL &optional callback)

Registers a feed with the relay server.

Arguments:

  • RELAY-URLstring. Relay base URL.
  • FEED-URLstring. Your social.org public URL.
  • CALLBACKfunction | nil. Called with t on success, nil on failure.

Example:

(org-social-lib-relay-register
 "https://relay.org-social.org"
 "https://andros.dev/social.org"
 (lambda (ok)
   (message (if ok "Registered!" "Registration failed."))))

org-social-lib-relay-fetch-feeds

(org-social-lib-relay-fetch-feeds RELAY-URL CALLBACK)

Fetches the list of all feeds known to the relay.

Arguments:

  • RELAY-URLstring.
  • CALLBACKfunction. Called with (list string) of feed URLs, or nil on failure.

Example:

(org-social-lib-relay-fetch-feeds
 "https://relay.org-social.org"
 (lambda (feeds)
   (message "Relay knows %d feeds" (length feeds))))

org-social-lib-relay-fetch-mentions

(org-social-lib-relay-fetch-mentions RELAY-URL FEED-URL CALLBACK)

Fetches post URLs that mention the given feed.

Arguments:

  • RELAY-URLstring.
  • FEED-URLstring. Your public feed URL.
  • CALLBACKfunction. Called with (list string) of post URLs (feed-url#timestamp), or nil.

Example:

(org-social-lib-relay-fetch-mentions
 "https://relay.org-social.org"
 "https://andros.dev/social.org"
 (lambda (post-urls)
   (dolist (url post-urls)
     (message "Mentioned in: %s" url))))

org-social-lib-relay-fetch-replies

(org-social-lib-relay-fetch-replies RELAY-URL POST-URL CALLBACK)

Fetches all replies to a specific post.

Arguments:

  • RELAY-URLstring.
  • POST-URLstring. Full post URL (feed-url#timestamp).
  • CALLBACKfunction. Called with (list post-alist) or nil.

Example:

(org-social-lib-relay-fetch-replies
 "https://relay.org-social.org"
 "https://andros.dev/social.org#2025-03-10T09:00:00+01:00"
 (lambda (replies)
   (message "This post has %d replies" (length replies))))

org-social-lib-relay-fetch-interactions

(org-social-lib-relay-fetch-interactions RELAY-URL POST-URL CALLBACK)

Fetches all interactions (reactions, replies, boosts) for a post.

Arguments:

  • RELAY-URLstring.
  • POST-URLstring.
  • CALLBACKfunction. Called with an alist with 'replies, 'reactions, and 'boosts keys, or nil.

Example:

(org-social-lib-relay-fetch-interactions
 "https://relay.org-social.org"
 my-post-url
 (lambda (data)
   (when data
     (message "Reactions: %d, Replies: %d, Boosts: %d"
              (length (alist-get 'reactions data))
              (length (alist-get 'replies data))
              (length (alist-get 'boosts data))))))

(org-social-lib-relay-search RELAY-URL QUERY CALLBACK &key type page per-page)

Searches posts indexed by the relay.

Arguments:

  • RELAY-URLstring.
  • QUERYstring. Search term.
  • CALLBACKfunction. Called with (results meta). results is a list of post alists; meta is an alist with 'page, 'per-page, 'total.
  • :type'tag | nil. Set to 'tag to search by hashtag instead of full text.
  • :pageinteger. Page number. Default: 1.
  • :per-pageinteger. Results per page. Default: 10.

Example:

;; Full-text search
(org-social-lib-relay-search
 "https://relay.org-social.org"
 "emacs org-mode"
 (lambda (results meta)
   (message "Found %d results (page %d of %d)"
            (length results)
            (alist-get 'page meta)
            (/ (alist-get 'total meta) (alist-get 'per-page meta)))))

;; Hashtag search
(org-social-lib-relay-search
 "https://relay.org-social.org"
 "emacs"
 #'my-display-results
 :type 'tag
 :per-page 20)

org-social-lib-relay-fetch-groups

(org-social-lib-relay-fetch-groups RELAY-URL CALLBACK)

Fetches the list of groups available on the relay.

Arguments:

  • RELAY-URLstring.
  • CALLBACKfunction. Called with a list of group alists. Each has 'name, 'href, 'method, and 'relay-url keys.

Example:

(org-social-lib-relay-fetch-groups
 "https://relay.org-social.org"
 (lambda (groups)
   (dolist (g groups)
     (message "Group: %s" (alist-get 'name g)))))

org-social-lib-relay-fetch-group-posts

(org-social-lib-relay-fetch-group-posts GROUP CALLBACK)

Fetches posts and member list for a specific group.

Arguments:

  • GROUP — group alist as returned by org-social-lib-relay-fetch-groups.
  • CALLBACKfunction. Called with an alist containing 'posts and 'members keys.

Example:

(org-social-lib-relay-fetch-groups
 "https://relay.org-social.org"
 (lambda (groups)
   (when groups
     (org-social-lib-relay-fetch-group-posts
      (car groups)
      (lambda (data)
        (message "%d posts, %d members"
                 (length (alist-get 'posts data))
                 (length (alist-get 'members data))))))))

org-social-lib-relay-fetch-poll-votes

(org-social-lib-relay-fetch-poll-votes RELAY-URL POST-URL CALLBACK)

Fetches the vote counts for a poll post.

Arguments:

  • RELAY-URLstring.
  • POST-URLstring. Full URL of the poll post.
  • CALLBACKfunction. Called with a list of alists, each with 'option and 'votes keys. 'votes is a list of voter feed URLs.

Example:

(org-social-lib-relay-fetch-poll-votes
 "https://relay.org-social.org"
 "https://andros.dev/social.org#2025-03-10T12:00:00+01:00"
 (lambda (votes)
   (dolist (option votes)
     (message "%s: %d votes"
              (alist-get 'option option)
              (length (alist-get 'votes option))))))

Host (vfile)

Functions for working with remotely hosted social.org files. A vfile is any social.org whose path starts with http(s)://.


org-social-lib-vfile-p

(org-social-lib-vfile-p PATH) → boolean

Returns t if PATH is a remote (vfile) path.

Example:

(org-social-lib-vfile-p "~/social.org")                       ; => nil
(org-social-lib-vfile-p "https://host.example.com/social.org") ; => t

org-social-lib-download-vfile

(org-social-lib-download-vfile VFILE-URL CALLBACK)

Downloads a hosted social.org to the local cache asynchronously.

Arguments:

  • VFILE-URLstring. The hosted file URL.
  • CALLBACKfunction. Called with the local cache path on success, nil on failure.

Example:

(org-social-lib-download-vfile
 "https://host.example.com/social.org"
 (lambda (local-path)
   (if local-path
       (message "Cached at %s" local-path)
     (message "Download failed"))))

org-social-lib-upload-vfile

(org-social-lib-upload-vfile VFILE-URL LOCAL-FILE &optional callback)

Uploads the local file to the host server. Used after saving changes.

Arguments:

  • VFILE-URLstring. The hosted file URL (used to determine the host).
  • LOCAL-FILEstring. Path to the local file to upload.
  • CALLBACKfunction | nil. Called with t on success, nil on failure.

Example:

(org-social-lib-upload-vfile
 "https://host.example.com/social.org"
 (org-social-lib-vfile-local-path "https://host.example.com/social.org")
 (lambda (ok)
   (message (if ok "Uploaded." "Upload failed."))))

org-social-lib-vfile-local-path

(org-social-lib-vfile-local-path VFILE-URL) → string

Returns the local cache path for the active vfile. The path is derived from the active account name (from org-social-accounts--current), not from VFILE-URL — the URL argument is accepted for API consistency but is not used to compute the path.

Arguments:

  • VFILE-URLstring. Accepted but not used to derive the path.

Returns: string — local file path inside user-emacs-directory.

Example:

;; With no active account:
(org-social-lib-vfile-local-path "https://host.example.com/social.org")
; => "~/.emacs.d/v-social.org"

;; With account "work" active (org-social-accounts--current = "work"):
(org-social-lib-vfile-local-path "https://host.example.com/social.org")
; => "~/.emacs.d/v-social-work.org"

Utilities


org-social-lib-post-url

(org-social-lib-post-url FEED-URL TIMESTAMP) → string

Constructs a full post URL from a feed URL and a post timestamp.

Example:

(org-social-lib-post-url "https://andros.dev/social.org"
                         "2025-03-10T09:00:00+01:00")
; => "https://andros.dev/social.org#2025-03-10T09:00:00+01:00"

org-social-lib-post-type

(org-social-lib-post-type POST) → symbol

Infers the type of a post from its properties.

Returns: One of: 'post, 'reply, 'reaction, 'boost, 'poll, 'poll-vote, 'group, 'migration.

Example:

(org-social-lib-post-type post) ; => 'reply

(org-social-lib-mention-link FEED-URL NICK) → string

Returns an Org link for mentioning a user in post content.

Example:

(org-social-lib-mention-link "https://shom.dev/social.org" "shom")
; => "[[org-social:https://shom.dev/social.org][shom]]"

Hooks


org-social-lib-after-fetch-hook

Abnormal hook run after all feeds in a batch have been downloaded. Called with a single argument: the list of (url . content) pairs.

(add-hook 'org-social-lib-after-fetch-hook
          (lambda (results)
            (message "Fetched %d feeds" (length results))))

org-social-lib-after-save-hook

Hook run after the local social.org file is saved. Useful for triggering an upload to a vfile host.

(add-hook 'org-social-lib-after-save-hook
          (lambda ()
            (org-social-lib-upload-vfile
             org-social-lib-file
             (org-social-lib-vfile-local-path org-social-lib-file))))

Data Structures Reference

Profile Alist

`((nick        . "andros")
  (title       . "Andros Fenollosa")
  (description . "Software developer.")
  (avatar      . "https://example.com/avatar.jpg")
  (location    . "Spain")
  (birthday    . "1990-01-15")
  (language    . "en")
  (url         . "https://andros.dev/social.org")
  (pinned      . "2025-03-10T09:00:00+01:00")
  (follow      . (((name . "shom") (url . "https://shom.dev/social.org"))
                  ((name . nil)   (url . "https://tanrax.com/social.org"))))
  (group       . (((name . "Emacs") (relay-url . "https://relay.org-social.org"))))
  (posts       . ( ... )))

Post Alist

`((timestamp    . "2025-03-10T09:00:00+01:00")
  (date         . 1741597200.0)           ; float-time, for sorting
  (text         . "Post content text.")
  (lang         . "en")                   ; nil if absent
  (tags         . "emacs org")            ; nil if absent
  (client       . "org-social.el")        ; nil if absent
  (reply_to     . nil)                    ; "url#timestamp" or nil
  (mood         . nil)                    ; emoji string or nil
  (include      . nil)                    ; "url#timestamp" or nil
  (poll_end     . nil)                    ; RFC 3339 string or nil
  (poll_option  . nil)                    ; string or nil
  (group        . nil)                    ; "Name relay-url" or nil
  (visibility   . nil)                    ; "public", "mention", or nil
  (migration    . nil)                    ; not extracted by the parser — requires custom handling
  ;; Added during timeline assembly:
  (author-nick   . "andros")
  (author-url    . "https://andros.dev/social.org")
  (author-avatar . "https://example.com/avatar.jpg"))

Follow Alist

`((name . "shom")                          ; nil if not specified
  (url  . "https://shom.dev/social.org"))

Group Alist

`((name      . "Emacs")
  (relay-url . "https://relay.org-social.org"))

Error Plist (from validation)

(list :line       42
      :column     5
      :message    "REPLY_TO must be in format URL#timestamp"
      :suggestion "Use the format https://feed.url#2025-03-10T09:00:00+01:00"
      :context    "The line text where the error was found")

Access with plist-get: (plist-get err :line), (plist-get err :message), etc.