IMPORTANT NOTE

I no longer maintain diaspora-el; please check:

I'm still waiting for a decent diaspora* API to show up.

Happy hacking!


I've been working on this for a couple of weeks and learned a lot... A few things that were useful:

1. learn how skeletons work (learned be reading sgml-mode); it permits in a very fluid way to insert all the markdown that one needs

2. font-lock: learned from reading emacs-muse-mode, markdown-mode

3. learn how to make key-bindings (copied from html-mode)

4. one can keep a local copy of the different posts by saving it on a .diaspora file (see take-notes-mode or remeber-mode)

It is a good thing tha all of this is free software so that can study it and share it with anyone.

Screenshot

Here is a screenshot

Usage

key-bindings

    C-c C-c             Prefix Command
    C-c 1               diaspora-markdown-insert-headline-1
    C-c 2               diaspora-markdown-insert-headline-2
    C-c 3               diaspora-markdown-insert-headline-3
    C-c 4               diaspora-markdown-insert-headline-4
    C-c l               diaspora-toogle-highlight
    C-c p               diaspora-post-this-buffer

    C-c C-c -           diaspora-markdown-insert-horizontal-rule
    C-c C-c b           diaspora-markdown-insert-bold-text
    C-c C-c e           diaspora-markdown-insert-emph-text
    C-c C-c h           diaspora-markdown-insert-link
    C-c C-c i           diaspora-markdown-insert-image
    C-c C-c l           diaspora-markdown-insert-unordered-list
    C-c C-c m           diaspora-markdown-mention-user

posting

read stream

The code

You can grab the code at github: diaspora.el

Of course there is still a lot to be done. But that's a start.

Comments and suggestions are welcome.

Acknowledgements

Thanks to Christian Giménez cnngimenez@joinsdiaspora.com for suppling the functions for the streaming part.

Cheers.

Source code

;;; diaspora.el --- Simple Emacs-based client for diaspora*

;; Author: Tiago Charters de Azevedo <tca@diale.org>
;; Maintainer: Tiago Charters de Azevedo <tca@diale.org>
;; Created: Jan 16, 2012
;; Version: .0
;; Keywords: diaspora*
;; URL: http://diale.org/diaspora.html

;; Copyright (c) 2011 Tiago Charters de Azevedo, Christian Giménez
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
;; 02110-1301, USA.

;;; Commentary:

;; A diaspora* client for emacs

(require 'url)
(require 'url-http)
(require 'json)
(require 'font-lock)

(defconst diaspora-el-version ".0"
  "This version of diaspora*-el.")

(defgroup diaspora nil
  "A mode for diaspora* stream view and posting."
  :group 'applications)

;;; User variable:

(defcustom diaspora-pod
  "joinsdiaspora.com"
  "Diaspora* pod."
  :type 'string
  :group 'diaspora)


(defcustom diaspora-mode-hook nil
  "Functions run upon entering `diaspora-mode'."
  :type 'hook
  :options '(flyspell-mode turn-on-auto-fill markdown-mode)
  :group 'diaspora)

(defcustom diaspora-username nil
  "Username to use for connecting to diaspora.
If nil, you will be prompted."
  :type '(choice (const :tag "Ask" nil) (string))
  :group 'diaspora)

(defcustom diaspora-password nil
  "Password to use for connecting to diaspora.
If nil, you will be prompted."
  :type '(choice (const :tag "Ask" nil) (string))
  :group 'diaspora)

(defcustom diaspora-sign-in-url
  "https://joindiaspora.com/users/sign_in"
  "URL used to signing in."
  :group 'diaspora)

(defcustom diaspora-status-messages-url
  "https://joindiaspora.com/status_messages"
  "URL used to update diaspora status messages."
  :group 'diaspora)

(defcustom diaspora-entry-stream-url
  "https://joindiaspora.com/stream.json"
  "JSON version of the entry stream(the main stream)."
  :group 'diaspora)


(defcustom diaspora-entry-file-dir
  "~/public_html/diaspora.posts/"
  "Directory where to save posts made to diaspora*."
  :group 'diaspora)

(defcustom diaspora-data-file
  "~/.diaspora"
  "Name of the file do save posts made to diaspora*."
  :type 'file
  :group 'diaspora)

(defcustom diaspora-header-post
  "## "
  "Header for each post:"
  :type 'string
  :group 'diaspora)

(defcustom diaspora-footer-post
  "#diaspora-el"
  "Footer for each post."
  :type 'string
  :group 'diaspora)

(defcustom diaspora-save-after-posting t
  "*Non-nil means automatically save after posting."
  :type 'boolean
  :group 'diaspora)

;;; Internal Variables:

;(defvar diaspora-auth-token nil


(defvar diaspora-stream-buffer "*diaspora stream*"
  "The name of the diaspora stream buffer.")

(defvar diaspora-post-buffer "*diaspora post*"
  "The name of the diaspora post buffer.")


;;; User Functions:

;; (defun diaspora-create-file-post ()
;;   (interactive)
;;   (read-from-minibuffer "Find file: "
;;                      nil nil nil 'diaspora-post-file-name)
;;   (let ((post-buffer (get-buffer-create (car diaspora-post-file-name))))
;;     (switch-to-buffer post-buffer)
;;     (diaspora-mode)))

;; Posting

(defun diaspora-post-to ()
  (interactive)
  (get-buffer-create diaspora-post-buffer)
  (switch-to-buffer diaspora-post-buffer)
  (diaspora-date)
  (insert diaspora-footer-post)
  (goto-char (point-min))
  (insert diaspora-header-post)
  (diaspora-mode))

(defun diaspora-ask ()
  "Ask for username and password
if `diaspora-username' and  `diaspora-password'
has not been setted."
  (unless (and diaspora-username diaspora-password)
    (read-from-minibuffer "username: "
                          (car diaspora-username)
                          nil nil
                          'diaspora-username)
    (read-from-minibuffer "password: "
                          (car diaspora-password)
                          nil nil
                          'diaspora-password)))

(defun diaspora-authenticity-token (url)
  "Get the authenticity token."
  (let ((url-request-method "POST")
        (url-request-extra-headers
         '(("Content-Type" . "application/x-www-form-urlencoded")))
        (url-request-data
         (mapconcat (lambda (arg)
                      (concat (url-hexify-string (car arg)) "=" (url-hexify-string (cdr arg))))
                    (list (cons "user[username]" (car diaspora-username))
                          (cons "user[password]" (car diaspora-password))
                          (cons "user[remember_me]" "1"))
                    "&")))
    (url-retrieve url 'diaspora-find-auth-token)))

(defun diaspora-find-auth-token (&optional status)
  "Find the authenticity token."

  (save-excursion
    (goto-char (point-min))
    (search-forward-regexp "<meta name=\"csrf-token\" content=\"\\(.*\\)\"/>")
    (setq diaspora-auth-token (match-string-no-properties 1)))
  diaspora-auth-token)


(defun diaspora-post (post &optional id)
  (let ((url-request-method "POST")
        (url-request-extra-headers
         '(("Content-Type" . "application/x-www-form-urlencoded")))
        (url-request-data
         (mapconcat (lambda (arg)
                      (concat (url-hexify-string (car arg)) "=" (url-hexify-string (cdr arg))))
                    (list (cons "user[username]" (car diaspora-username))
                          (cons "user[password]" (car diaspora-password))
                          (cons "status_message[text]" post)
                          (cons "user[remember_me]" "1")
                          (cons "authenticity_token" diaspora-auth-token)
                          (cons "commit" "Sign in")
                          (cons "aspect_ids[]" "public"))
                    "&")))
    (url-retrieve diaspora-status-messages-url
                  (lambda (arg)
                    (kill-buffer (current-buffer))))))

(defun diaspora-post-this-buffer ()
  (interactive)
  (diaspora-ask)
  (message (concat "Getting authenticity token..."))
  (message (concat "done: " diaspora-auth-token))
  (diaspora-authenticity-token diaspora-sign-in-url)
  (message (concat "done: " diaspora-auth-token))
  (diaspora-post (buffer-string))
  (diaspora-post-append-to-file)
  (kill-buffer))

;, Streaming

(defun diaspora-show-stream (status &optional new-buffer-name)
  "Show what was recieved in a new buffer.
If new-buffer-name is given then, the new buffer will have that name,
if not, the buffer called \"Diáspora Stream\" will be re-used or created if needed."
  ;; new-buffer-name has been given? if not, use `diaspora-stream-buffer´ as name.
  (unless new-buffer-name
    (setq new-buffer-name diaspora-stream-buffer))
  (let ((buffer (get-buffer-create new-buffer-name))
        (text (buffer-string))
        (buf-kill (current-buffer)))
    ;; copy text and switch
    (switch-to-buffer buffer)
    (insert text)
    ;; kill the http buffer
    (kill-buffer buf-kill)))

(defun diaspora-get-url-entry-stream (url)
  "Get the Diáspora URL and leave it in a new buffer."
  (let ((url-request-extra-headers
         '(("Content-Type" . "application/x-www-form-urlencoded")
           ("Accept-Language" . "en")
           ("Accept-Charset" . "UTF-8"))))
    (url-retrieve-synchronously url)))


(defun diaspora-get-entry-stream ()
  "Show the entry stream.
First look for the JSON file at `diaspora-entry-stream-url' and then parse it.
I expect to be already logged in. Use `diaspora' for log-in."
  (interactive)
  (diaspora-ask)
  (diaspora-authenticity-token diaspora-sign-in-url) ;; Get the authenticity token
  (let ((buff (diaspora-get-url-entry-stream diaspora-entry-stream-url)))
    (with-current-buffer buff
      ;; Delete the HTTP header...
      (goto-char (point-min))
      (search-forward "\n\n")
      (delete-region (point-min) (match-beginning 0))
      ;; Parse JSON...
      (diaspora-parse-json))
    ;; Delete HTTP Buffer
    (kill-buffer buff)))

(defun diaspora-show-message (parsed-message &optional buffer)
  "Show a parsed message in a given buffer."
  (with-current-buffer buffer
    (let ( (name (cdr (assoc 'name (assoc 'author parsed-message))))
          (diaspora_id (cdr (assoc 'diaspora_id (assoc 'author parsed-message))))
          (text (cdr (assoc 'text parsed-message)))
          (date (cdr (assoc 'created_at parsed-message)))
          (amount-comments (cdr (assoc 'comments_count parsed-message)))
          (amount-likes (cdr (assoc 'likes_count parsed-message))))
      (insert (format "---\n%s(%s):\n%s\n\n" name diaspora_id text))))
  (diaspora-mode))

(defun diaspora-get-entry-stream-tag (tag)
  "Get stream of tag. Just an idea... needs working."
  (let ((buff (diaspora-get-url-entry-stream
               (concat "https://joindiaspora.com/tags/" tag ".json"))))
    (with-current-buffer buff
      (goto-char (point-min))
      (search-forward "\n\n")
      (delete-region (point-min) (match-beginning 0))
      (diaspora-parse-json))
    (kill-buffer buff)))

(defun diaspora-parse-json (&optional status)
  "Parse de JSON entry stream."
  (goto-char (point-min))
  (let ((lstparsed (cdr (assoc 'posts (json-read))))
        (buff (get-buffer-create diaspora-stream-buffer)))
    (switch-to-buffer buff)
    (let ((le (length lstparsed)))
    ;; Show all elements
      (dotimes (i le)
        (diaspora-show-message (aref lstparsed i) buff)))))


(defsubst diaspora-date ()
  "Date string."
  (interactive)
  (insert "\n\n#" (format-time-string "%Y%m%d") " "))



(defun diaspora-post-append-to-file ()
  ;; Based on take-notes.el/remember.el
  (with-temp-buffer
    (insert-buffer diaspora-post-buffer)
    (insert "\n" "---" "\n")
    (if (find-buffer-visiting diaspora-data-file)
        (let ((post-text (buffer-string)))
          (set-buffer (get-file-buffer diaspora-data-file))
          (save-excursion
            (goto-char (point-min))
            (insert post-text)
            (insert "\n")
            (when diaspora-save-after-posting (save-buffer)))
          (append-to-file (point-min) (point-max) diaspora-data-file)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar diaspora-mode-map
  (let ((diaspora-mode-map (make-sparse-keymap)))
    (define-key diaspora-mode-map "\C-c4" 'diaspora-markdown-insert-headline-4)
    (define-key diaspora-mode-map "\C-c3" 'diaspora-markdown-insert-headline-3)
    (define-key diaspora-mode-map "\C-c2" 'diaspora-markdown-insert-headline-2)
    (define-key diaspora-mode-map "\C-c1" 'diaspora-markdown-insert-headline-1)
    (define-key diaspora-mode-map "\C-c\C-cl" 'diaspora-markdown-insert-unordered-list)
    (define-key diaspora-mode-map "\C-c\C-ce" 'diaspora-markdown-insert-emph-text)
    (define-key diaspora-mode-map "\C-c\C-cb" 'diaspora-markdown-insert-bold-text)
    (define-key diaspora-mode-map "\C-c\C-c-" 'diaspora-markdown-insert-horizontal-rule)
    (define-key diaspora-mode-map "\C-c\C-ch" 'diaspora-markdown-insert-link)
    (define-key diaspora-mode-map "\C-c\C-ci" 'diaspora-markdown-insert-image)
    (define-key diaspora-mode-map "\C-c\C-cm" ' diaspora-markdown-mention-user)
    (define-key diaspora-mode-map "\C-cp" 'diaspora-post-this-buffer)
    (define-key diaspora-mode-map "\C-cl" 'diaspora-toogle-highlight) ; not implemente yet
    diaspora-mode-map)
  "Keymap based on html-mode")



(define-skeleton diaspora-markdown-insert-headline-1
  "Headline 1."
  "Text: "
  "# " str \n \n)

(define-skeleton diaspora-markdown-insert-headline-2
  "Headline 2."
  "Text: "
  "## " str \n \n)

(define-skeleton diaspora-markdown-insert-headline-3
  "Headline 3."
  "Text: "
  "### " str \n \n)

(define-skeleton diaspora-markdown-insert-headline-4
  "Headline 4."
  "Text: "
  "#### " str \n \n)

(define-skeleton diaspora-markdown-insert-unordered-list
  "Unordered list."
  "Text: "
  "* " str \n \n)

(define-skeleton diaspora-markdown-insert-emph-text
  "Emphasis."
  "Text: "
  "*" str "*")

(define-skeleton diaspora-markdown-insert-bold-text
  "Bold."
  "Text: "
  "**" str "**")

(define-skeleton diaspora-markdown-insert-horizontal-rule
  "Horizontal rule tag."
  nil
  "---" \n \n)

(define-skeleton diaspora-markdown-insert-link
  "Link"
  "Text: "
  "[" str "](http://" _ ")")

(define-skeleton diaspora-markdown-insert-image
  "Image with URL."
  "Text: "
  "![" str "](http://" _ ")")

(define-skeleton diaspora-markdown-mention-user
  "Mention user."
  "User: "
  "@{" str ";" _ (concat "@" diaspora-pod "}"))


;; Font lock

(defgroup diaspora-faces nil
  "Faces used in diaspora Mode"
  :group 'diaspora
  :group 'faces)

(defcustom diaspora-regex-image
  "\\(!?\\[[^]]*?\\]\\)\\(([^\\)]*)\\)"
  "Regular expression for a [text](file) or an image link ![text](file)."
  :type 'regexp
  :group 'diaspora)

(defcustom diaspora-regex-user-entry
"^[a-zA-Z0-9_\s-]*([a-zA-Z0-9_\s-]*@[a-zA-Z0-9\s-]*\.[a-zA-Z0-9\s-]*)"
  "Regular expression for user entry."
  :type 'regexp
  :group 'diaspora)

(defcustom diaspora-regex-tag
  "#\\([a-zA-Z0-9_]\\)+"
  "Regular expression for a tag."
  :type 'regexp
  :group 'diaspora)

(defcustom diaspora-regex-header-1
  "^\\(# \\)\\(.*?\\)\\($\\| #+$\\)"
  "Regular expression for level 1"
  :type 'regexp
  :group 'diaspora)

(defcustom diaspora-regex-header-2
  "^\\(## \\)\\(.*?\\)\\($\\| #+$\\)"
  "Regular expression for level 2"
  :type 'regexp
  :group 'diaspora)

(defcustom diaspora-regex-header-3
  "^\\(### \\)\\(.*?\\)\\($\\| #+$\\)"
  "Regular expression for level 3"
  :type 'regexp
  :group 'diaspora)


(defcustom diaspora-regex-header-4
  "^\\(#### \\)\\(.*?\\)\\($\\| #+$\\)"
  "Regular expression for level 4"
  :type 'regexp
  :group 'diaspora)

(defconst diaspora-regex-code
  "\\(^\\|[^\\]\\)\\(\\(`\\{1,2\\}\\)\\([^ \\]\\|[^ ]\\(.\\|\n[^\n]\\)*?[^ \\]\\)\\3\\)"
  "Regular expression for matching inline code fragments.")


(defconst diaspora-regex-bold
  "\\(^\\|[^\\]\\)\\(\\([*_]\\{2\\}\\)\\(.\\|\n[^\n]\\)*?[^\\ ]\\3\\)"
  "Regular expression for matching bold text.")

(defconst diaspora-regex-emph
  "\\(^\\|[^\\]\\)\\(\\([*_]\\)\\([^ \\]\\3\\|[^ ]\\(.\\|\n[^\n]\\)*?[^\\ ]\\3\\)\\)"
  "Regular expression for matching emph text.")

(defconst diaspora-regex-email
  "<\\(\\sw\\|\\s_\\|\\s.\\)+@\\(\\sw\\|\\s_\\|\\s.\\)+>"
  "Regular expression for matching inline email addresses.")

(defconst diaspora-regex-blockquote
  "^>.*$"
  "Regular expression for matching blockquote lines.")

(defconst diaspora-regex-hr
  "^\\(\\*[ ]?\\*[ ]?\\*[ ]?[\\* ]*\\|-[ ]?-[ ]?-[--- ]*\\)$"
  "Regular expression for matching markdown horizontal rules.")

(defvar diaspora-header-face-1 'diaspora-header-face-1
  "Face name to use for level-1 headers.")

(defvar diaspora-header-face-2 'diaspora-header-face-2
  "Face name to use for level-2 headers.")

(defvar diaspora-header-face-3 'diaspora-header-face-3
  "Face name to use for level-3 headers.")

(defvar diaspora-header-face-4 'diaspora-header-face-4
  "Face name to use for level-4 headers.")

(defvar diaspora-url-face 'diaspora-url-face
  "Face name to use for URLs.")

(defvar diaspora-link-face 'diaspora-link-face
  "Face name to use for links.")

(defvar diaspora-emph-face 'diaspora-emph-face
  "Face name to use for links.")

(defvar diaspora-bold-face 'diaspora-bold-face
  "Face name to use for links.")

(defvar diaspora-emph-face 'diaspora-emph-face
  "Face name to use for links.")

(defvar diaspora-inline-code-face 'diaspora-inline-code-face
  "Face name to use for inline code.")

(defvar diaspora-blockquote-face 'diaspora-blockquote-face
  "Face name to use for blockquote text.")

(defface diaspora-inline-code-face
  '((t :inherit font-lock-constant-face))
  "Face for inline code."
  :group 'diaspora-faces)

(defface diaspora-blockquote-face
  '((t :inherit font-lock-doc-face))
  "Face for blockquote sections."
  :group 'diaspora-faces)

(defface diaspora-header-face
  '((t :inherit font-lock-function-name-face :weight bold))
  "Base face for headers."
  :group 'diaspora-faces)

(defface diaspora-header-face-1
  '((t :inherit diaspora-header-face))
  "Face for level-1 headers."
  :group 'diaspora-faces)

(defface diaspora-header-face-2
  '((t :inherit diaspora-header-face))
  "Face for level-2 headers."
  :group 'diaspora-faces)

(defface diaspora-header-face-3
  '((t :inherit diaspora-header-face))
  "Face for level-3 headers."
  :group 'diaspora-faces)

(defface diaspora-header-face-4
  '((t :inherit diaspora-header-face))
  "Face for level-4 headers."
  :group 'diaspora-faces)

(defface diaspora-link-face
  '((t :inherit font-lock-keyword-face))
  "Face for links."
  :group 'diaspora-faces)

(defface diaspora-url-face
  '((t :inherit font-lock-string-face))
  "Face for URLs."
  :group 'diaspora-faces)

(defface diaspora-emph-face
  '((t :inherit font-lock-variable-name-face :italic t))
  "Face for italic text."
  :group 'diaspora-faces)

(defface diaspora-bold-face
  '((t :inherit font-lock-variable-name-face :bold t))
  "Face for bold text."
  :group 'diaspora-faces)

(defface diaspora-link-face
  '((t :inherit font-lock-keyword-face))
  "Face for links."
  :group 'diaspora-faces)



(defvar diaspora-mode-font-lock-keywords
  (list
   (cons diaspora-regex-blockquote 'diaspora-blockquote-face)
   (cons diaspora-regex-user-entry 'diaspora-header-face-1)
   (cons diaspora-regex-header-1 'diaspora-header-face-1)
   (cons diaspora-regex-header-2 'diaspora-header-face-2)
   (cons diaspora-regex-header-3 'diaspora-header-face-3)
   (cons diaspora-regex-header-4 'diaspora-header-face-4)
   (cons diaspora-regex-hr 'diaspora-header-face-1)
   (cons diaspora-regex-image
         '((1 diaspora-link-face t)
           (2 diaspora-url-face t)))
   (cons diaspora-regex-bold '(2 diaspora-bold-face))
   (cons diaspora-regex-emph '(2 diaspora-emph-face))
   (cons diaspora-regex-code '(2 diaspora-inline-code-face))
   (cons diaspora-regex-email 'diaspora-link-face)
   (cons diaspora-regex-tag 'diaspora-url-face))
  "Syntax highlighting for diaspora files.")


;; Mode

(defun diaspora-show-version ()
  "Show the version number in the minibuffer."
  (interactive)
  (message "diaspora.el, version %s" diaspora-el-version))

;;;###autoload
(define-derived-mode diaspora-mode text-mode "diaspora"
  "Major mode for output from \\[diaspora*]."
  (set (make-local-variable 'font-lock-defaults)
       '(diaspora-mode-font-lock-keywords))
  (set (make-local-variable 'font-lock-multiline) t)
  (use-local-map diaspora-mode-map)
  (run-hooks 'diaspora-mode-hook))

(provide 'diaspora)

;;; diaspora.el ends here

Created: NaN

Last updated: 16-02-2026 [16:02]


For attribution, please cite this page as:

Charters, T., "diaspora(dot)el -- Simple Emacs-based client for diaspora*": https://nexp.pt/diaspora.html (16-02-2026 [16:02])


(cc-by-sa) Tiago Charters - tiagocharters@nexp.pt