Ryan Rix 7 years ago
commit dea7f997ea

@ -0,0 +1,69 @@
#+TITLE: Sico: Rockie's Robot Butler
* Installation
=sico= will soon be installable via MELPA. Otherwise you can add this directory to your =load-path=
and =(require 'sico)=.
* Usage
** Basic Usage
- src_emacs-lisp[:exports code]{(customize-group 'sico)}
- At the bare minimum, customize =sico-rooms= and =sico-users=
- You may also want to customize =matrix-homeserver-base-url=
- =M-x sico-start= will connect Sico to the Matrix homeserver and begin polling for events
** Extending
*** Customizing the Note Taker
The note taker is a simple wrapper around =org-capture=, which gives you a nice simple wedge with
which to get your way in to an org-mode document
Begin by defining a capture-template:
#+BEGIN_SRC emacs-lisp :exports code
(add-to-list 'org-capture-templates
"Automatic note taking using Sico"
(file org-default-notes-file)
:immediate-finish t))
The important bits here are:
- The usage of =%c=, which will be expanded to the head of the kill-ring. Sico stores the note in
there as an easy way to push it in to Org-Mode.
- The usage of =:immediate-finish t=, since Sico is not helping you do interactive things over
Matrix, you need to make sure the Capture dialog doesn't stay open.
Do read the [[][Org-Mode documentation on Capture Tepmlates]] (=(info "org#Capture templates")= inside of
Emacs), as it is quite helpful.
*** New Listeners
Creating new listeners is fairly trivial. Create a function which takes a single argument, which is
the sexp representation of a [[][room event]]. It can do whatever you want it to do with that
event, for a minimal example, see the source for =sico-capture-note=.
Once you have your function defined, add a cons to the alist =sico-listeners= where the car is the
regexp that will match the event, and the cdr is the symbol for the function you've just defined.
* Contributing
To submit patches:
- Clone the repo
- Create a git branch, code in a branch.
- When you're done, send me a =git format-patch= style patch
- =git format-patch --to master..HEAD > YOURBRANCHNAME.patch=
- Mail that patch to me at [[][]], and I will integrate it.
- Or send it to me on Matrix, I'm
Project Discussion happens in
* License
See LICENSE in the root of the repository for legal information.

@ -0,0 +1,160 @@
;;; sico.el --- A personal assistant for Emacs and
;; Copyright (C) 2015 Ryan Rix
;; Author: Ryan Rix <>
;; Maintainer: Ryan Rix <>
;; Created: 20 December 2015
;; Keywords: web
;; Homepage:
;; Package-Version: 0.0.1
;; Package-Requires: ((matrix-client "0.1.0"))
;; This file is not part of GNU Emacs.
;; sico.el 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 3 of the License, or (at your option) any later
;; version.
;; sico.el 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 file. If not, see <>.
;;; Commentary:
;; Sico is your personal robot butler. Sico acts as an interface to a running
;; Emacs session over the RPC network. Run `sico-start' and it will
;; jump in to your favorite Matrix rooms and lend a hand when you need it.
;; To get started, customize, at the minumum `sico-rooms' and `sico-users'. You
;; may also have to customize `matrix-homeserver-base-url', if you are
;; connecting to a non-standard homeserver.
;; Right now Sico is in early development, supporting only the ability to
;; capture single-line notes to your Org-Mode. The docstring for
;; `sico-capture-note-template' explains how to set this up.
;; Future work and plans:
;; - Render an Agenda as HTML and send it over Matrix
;; - Schedule timers
;; - Evaluate functions from a whitelist
;; - Notify you for anything using the `sauron' library
;; - Notify you for anything using the `notifications' library
;;; Code:
(require 'matrix-client)
(require 'matrix-api)
(defgroup sico nil "Customization options for Sico, your
Matrix/Emacs robot butler."
:prefix "sico")
(defcustom sico-rooms nil
"Rooms the sico should join."
:type '(repeat string)
:group 'sico)
(defcustom sico-users nil
"List of users that are allowed to interact with the sico."
:type '(repeat string)
:group 'sico)
(defcustom sico-capture-note-regexp "^!note\\b\\(.*\\)"
"Regular expression defining how to capture notes.
Handler expects (match-string 1) to include the note."
:type 'string
:group 'sico)
(defcustom sico-capture-note-template "A"
"The org-capture-templates key that will be used to store a note.
Templates should assume that the body will be given as the head
of the kill ring, accessible from inside a template as %c. A
standard org-capture-template could look like:
'(\"A\" \"Automatic note taking using Sico\"
(file org-default-notes-file)
\"* %c :NOTE:NOEXPORT:\"
:immediate-finish t)")
(defcustom sico-listeners (list (cons sico-capture-note-regexp 'sico-capture-note))
"Alist containing a set of (`regexp' . `function') cells.
The regexps that match will get the entire line passed to it as a
single argument."
:type '(alist :value-type (group function)))
(defcustom sico-connection-string
"Hello, my name is Sico and I am pleased to be of service."
"The message the bot says upon joining each room.")
(defvar sico-current-end-token nil)
(defvar sico-running nil)
(defun sico-start ()
"Start Assitant and connect it to the Matrix homeserver."
(unless matrix-token
(setq sico-running t))
(defun sico-stop ()
"Tell sico to stop polling."
(setq sico-running nil))
(defun sico-start-polling ()
"Begin polling for events with the [`sico-poll-callback'] as the handler."
(matrix-event-poll sico-current-end-token
(defun sico-join-rooms ()
"Join the rooms that Sico is configured to be in."
(dolist (room sico-rooms)
(matrix-join-room room)
(matrix-send-message room sico-connection-string)))
(defun sico-poll-callback (data)
"The callback triggered by [`matrix-event-poll'].
DATA comes directly from the Matrix homeserver, massaged in to a
sexp by [`json-parse']"
(if (eq (car data) 'error)
(message "%s" data)
(let* ((chunk (matrix-get 'chunk data)))
(setq sico-current-end-token (matrix-get 'end data))
(mapc (lambda (item)
(dolist (cell sico-listeners)
(let ((content (matrix-get 'content item))
(type (matrix-get 'type item)))
(when (and content
(string= type "")
(string-match (car cell)
(matrix-get 'body content)))
(funcall (cdr cell) item))))) chunk)))
(when sico-running
(defun sico-capture-note (event)
"Capture a note from EVENT."
(let* ((content (matrix-get 'content event))
(body (matrix-get 'body content))
(matching (string-match sico-capture-note-regexp body))
(match (match-string 1 body))
(user-id (matrix-get 'sender event))
(room-id (matrix-get 'room_id event)))
(when (member user-id sico-users)
(kill-new match)
(org-capture nil sico-capture-note-template)
(matrix-send-message room-id
(format "I have captured: \"%s\"" match)))))
(provide 'sico)
;;; sico.el ends here