From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Hanno Perrey Newsgroups: gmane.emacs.devel Subject: Re: [ELPA] New package: jami-bot and org-jami-bot Date: Sat, 03 Feb 2024 10:28:19 +0100 Message-ID: <87y1c1lufe.fsf@hoowl.se> References: <875y0i7e43.fsf@hoowl.se> <87y1cyqoso.fsf@posteo.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="13886"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Stefan Kangas , emacs-devel@gnu.org To: rms@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sat Feb 03 12:19:53 2024 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1rWE3s-0003N2-MJ for ged-emacs-devel@m.gmane-mx.org; Sat, 03 Feb 2024 12:19:52 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rWE3b-0000IS-0h; Sat, 03 Feb 2024 06:19:35 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rWD9j-0003nB-9A for emacs-devel@gnu.org; Sat, 03 Feb 2024 05:21:51 -0500 Original-Received: from mx0.riseup.net ([198.252.153.6]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rWD9h-0000ie-80; Sat, 03 Feb 2024 05:21:51 -0500 Original-Received: from fews02-sea.riseup.net (fews02-sea-pn.riseup.net [10.0.1.112]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx0.riseup.net (Postfix) with ESMTPS id 4TRpbF2SCyz9vr1; Sat, 3 Feb 2024 10:21:45 +0000 (UTC) X-Riseup-User-ID: C28D049871F8D9A334DAAC67242EDB0B8B7E06953D75D7DFB2399E25B209DFB1 Original-Received: from [127.0.0.1] (localhost [127.0.0.1]) by fews02-sea.riseup.net (Postfix) with ESMTPSA id 4TRpbD4VzNzFvMS; Sat, 3 Feb 2024 10:21:44 +0000 (UTC) In-reply-to: Received-SPF: pass client-ip=198.252.153.6; envelope-from=hanno@hoowl.se; helo=mx0.riseup.net X-Spam_score_int: -25 X-Spam_score: -2.6 X-Spam_bar: -- X-Spam_report: (-2.6 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sat, 03 Feb 2024 06:19:27 -0500 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:315813 Archived-At: --=-=-= Content-Type: text/plain Hej, > If I understood the calling conventions for > jami-bot and the calling conventions for org-capture, rather than > guessing them, I might be able to quickly develop something comparable > to org-jami-bot which does not depend on Org. I am really glad for your interest in jami-bot and happy to help you with the above! > I observed another gap: org-jami-bot does not contain documentation > that answers the basic questions, "What does this do, and how do you use it?" > Its user options and function arguments were carefully documented in detail; > what was missing was the overall framework to understand it all. You have a point in that the documentation has been quite sparse in the code. I have started to close that gap and extended the documentation in both jami-bot and org-jami-bot. The latter refers to the former where appropriate. It might also be useful to summarize the usage of org-jami-bot here with a focus to clarify its function to any non-Org mode users: As a user, the setup of org-jami-bot is done by calling `org-jami-bot-default-setup' which is now highlighted more explicitly in the commentary to the package. This function does the following: - set up a Org mode capture template. Think of this as a combined format string and file name to save anything "captured". - register a hook each to `jami-bot-text-message-functions' and `jami-bot-data-transfer-functions'. Both use the capture template to save any plain text message and data transfer, respectively, to the default notes file. Both message types are essentially just appended (under their own Org mode header as the template adds a leading '*'), but file transfers have an automatically generated header and include a link to the file already downloaded by jami-bot as well as some additional meta information such as the timestamp. - add four entries to `jami-bot-command-function-alist'. jami-bot handles plain text messages that start with an exclamation mark followed by a single word as a "command" which is mapped to a function by said alist. The function then can parse the message or perform any other action. The return string of the function is sent as a reply to the original message. So this registers four new "commands" in jami-bot that are for further, more refined captures (explained below). The four commands are as follows: - "!today" and "!schedule" (`org-jami-bot--command-function-today' and `org-jami-bot--command-function-schedule', respectively) simply add today's date or a date specified in the message, respectively, to the Org mode header together with the "SCHEDULED:" keyword. Within Org mode, this makes this particular entry appear e.g. in time-based searches or the Org mode agenda which summarizes TODO entries for each day. - "!start" and "!done" (`org-jami-bot--command-function-start' and `org-jami-bot--command-function-done', respectively) are used to redirect captures to a temporary buffer created when sending "!start" and finally captured/saved when sending "!done". The idea is that one might want to have several messages appended to a single Org mode header. So this is only necessary as each message by default (due to the capture template format) is added under its own header. For a user that does not want to modify this behavior, only the call to `org-jami-bot-default-setup' is needed, everything else is called via jami-bot when sending messages. A version without Org mode would only need to adjust the two hooks for plain text and file transfer handling and store the messages in some other format to a file. Whether or not the commands introduced by org-jami-bot make sense in that context depends on the chosen format I guess. I attach the new versions of jami-bot and org-jami-bot with the extended documentation. Please let me know whether or not these close any gap for you and -- if not -- what to improve. Thanks and cheers, Hanno --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=jami-bot.el Content-Transfer-Encoding: quoted-printable ;;; jami-bot.el --- An extendable chat bot for the private messenger GNU Ja= mi -*- lexical-binding: t; -*- ;; Copyright (C) 2023, 2024 Free Software Foundation, Inc. ;; Author: Hanno Perrey ;; Maintainer: Hanno Perrey ;; Created: April 15, 2023 ;; Modified: February 4, 2024 ;; Version: 0.0.4 ;; Keywords: comm, jami, messenger, chat bot, dbus ;; Homepage: https://gitlab.com/hperrey/jami-bot ;; Package-Requires: ((emacs "27.1")) ;; This file is not part of GNU Emacs. ;; ;; 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 3 of the License, 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, see . ;;; Commentary: ;; ;; An extendable chat bot for the distributed, private messenger Jami. It ;; interacts with the locally-installed Jami daemon via D-Bus and reacts to ;; both plain text messages and file transfers sent to local accounts. Fur= ther ;; processing of either or both can be configured by adding functions to the ;; abnormal hooks, `jami-bot-text-message-functions' and ;; `jami-bot-data-transfer-functions', respectively. ;; ;; Additionally, the bot allows special actions to be triggered by sending a ;; text message starting with an exclamation mark and a command keyword. F= urther ;; commands than the ones included can be configured by mapping them to ;; functions through `jami-bot-command-function-alist'. See, for example, = the ;; function `jami-bot--command-function-ping' which is by default called wh= en ;; a message starts with the "!ping" command string. ;; ;; Set up `jami-bot' by executing `jami-bot-register'. This will set up the ;; message handler, `jami-bot--messageReceived-handler', to be called on the ;; `messageReceived' D-Bus signal. You will need to run `jami-bot-register' ;; whenever you restart Emacs or the Jami process. ;; ;; `jami-bot' offers several customizable variables, e.g. to limit message ;; processing to certain local accounts or to specify a directory to store ;; downloaded files in. ;; ;;; Code: (require 'dbus) ;;;; Customization (defgroup jami-bot nil "Automatically process messages via GNU Jami." :group 'comm) (defcustom jami-bot-account-user-names nil "List of account user names that `jami-bot' handles messages for. If set to nil then `jami-bot' will react to any message send to a local account. The user name is also sometimes referred to as address in Jami and should be a 40 character hash such as \"badac18e13ec1a6e1266600e457859afebfb9c46\"." :group 'jami-bot :type 'string) (defcustom jami-bot-command-function-alist '(("!ping" . jami-bot--command-function-ping) ("!help" . jami-bot--command-function-help)) "Alist mapping command strings in message body to functions to be execute= d. Each command string needs to start with an exclamation mark '!' and consist of a single (lowercase) word. The corresponding function needs to accept the account id, the conversation id and the message alist as arguments and return a string (that is sent as reply to the original message). A received text message that starts with a given command string will be passed on to the respective handling function for further processing. See documentation for the handler function, `jami-bot--messageReceived-handler' for details on the message format." :group 'jami-bot :type '(alist :key-type string :value-type function)) (defcustom jami-bot-text-message-functions nil "A list of functions that will be called when processing a plain text mes= sage. Functions must take the ACCOUNT and CONVERSATION ids as well as the actual MSG alist as arguments. Their return value will be ignored. See documentation for the handler function, `jami-bot--messageReceived-handler' for details on the message format." :group 'jami-bot :type '(group function)) (defcustom jami-bot-download-path "~/jami/" "Path in which to store files downloaded from conversations. Will be created if not existing yet." :group 'jami-bot :type '(directory)) (defcustom jami-bot-data-transfer-functions nil "A list of functions that will be called when processing a data transfer = message. Functions must take the ACCOUNT and CONVERSATION ids as well as the actual MSG alist and the local downloaded file name, DLNAME, as arguments. Their return value will be ignored. See documentation for the handler function, `jami-bot--messageReceived-handler' for details on the message format." :group 'jami-bot :type '(group function)) ;;;; Internal variables (defvar jami-bot--jami-local-account-ids nil "List of `jami' local accounts user ids and name pairs. Caches output of dbus-methods \"getAccountList\" and \"getAccountDetails\". For internal use in `jami-bot'.") ;;;; Functions (defun jami-bot--messageReceived-handler (account conversation msg) "Handle messages from Jami's `messageReceived' D-Bus signal. ACCOUNT and CONVERSATION are the corresponding ids to which the MSG belongs to. The MSG is an alist which contains various key/value pairs, depending on the message type. These include: author Message author id (fingerprint). body Message body. id Message id (fingerprint). timestamp Time in seconds. type String describing type, e.g. \"text/plain\" For file transfers, additional keys are present in MSG: displayName File name chosen by sender. fileId File id string. sha3sum SHA3SUM of the file. totalSize File size in bytes. The field `type' is used to identify which function to call for further processing, i.e. `jami-bot--process-text-message' for text messages and `jami-bot--process-data-transfer' for data transfers. Any other type is handled via `jami-bot--process-unknown-type'. This function is registered as handler through `jami-bot-register' which needs to be called once per session or after a restart of the Jami daemon process." ;; make sure we are not reacting to messages sent from our own local ;; account(s) or accounts we are not to monitor (unless jami-bot--jami-local-account-ids (jami-bot--refresh-accountid-list)) (let ((author (cadr (assoc "author" msg))) (type (cadr (assoc "type" msg)))) (when (or (and jami-bot-account-user-names ;; account id should match a user name to be monitored (member (car (rassoc account jami-bot--jami-local-account-i= ds)) jami-bot-account-user-names) ;; .. but msg should not be authored by ourselves (not (member author jami-bot-account-user-names))) ;; no account filter: check msg not from local account (and (not jami-bot-account-user-names) (not (assoc author jami-bot--jami-local-account-ids)))) (message "jami-bot received %s message from %s on account %s." type a= uthor account) (pcase type ("text/plain" (jami-bot--process-text-message account conversation msg)) ("application/data-transfer+json" (jami-bot--process-data-transfer account conversation msg)) ;; ignore merges of the conversation; usually transparent to the us= er ;; anyway ("merge" (ignore)) ;; ignore new members joining ("member" (ignore)) (_ (jami-bot--process-unknown-type account conversation msg)))))) (defun jami-bot--process-unknown-type (account conversation msg) "Handle messages of unknown type by sending an error message as reply. ACCOUNT and CONVERSATION are the corresponding ids to which the MSG belongs to." (let ((type (cadr (assoc "type" msg)))) (message "Error: received message with unkonwn type: %s" type) (jami-bot-reply-to-message account conversation msg (format "Unknown message type: %s" type)))) (defun jami-bot-select-and-insert-local-account () "Prompt user for a local Jami user account and insert id at position." (interactive) (jami-bot--refresh-accountid-list) (insert (completing-read "Pick a account user name to insert: " jami-bot-= -jami-local-account-ids))) (defun jami-bot-register () "Ping the Jami daemon and register `jami-bot' handler for receiving messa= ges. This is necessary in order for `jami-bot' to be notified of any incoming messages and needs to be run once per Emacs session or after a restart of the Jami daemon process." (interactive) (or (dbus-ping :session "cx.ring.Ring") (error "Jami Daemon (jamid) not available through dbus. Please check= Jami installation")) (dbus-register-signal :session "cx.ring.Ring" "/cx/ring/Ring/ConfigurationManager" "cx.ring.Ring.ConfigurationManager" "messageReceived" #'jami-bot--messageReceived-handler)) (defun jami-bot--process-text-message (account conversation msg) "Process plain text messages and parse the message body for commands. ACCOUNT and CONVERSATION are the corresponding ids to which the message MSG belongs to. Messages containing commands must start with an exclamation mark (\"!\") followed by the single-word command. Each command is mapped to a function via `jami-bot-command-function-alist' which will be executed when the command is received. The string returned by the function is sent as a reply to the message. If the message does not start with an exclamation mark, the abnormal hook `jami-bot-text-message-functions' will be run for further processing." (let ((body (cadr (assoc-string "body" msg)))) ;; check for criteria handling first line of body as command ;; - string starts with '!' and is a single word (if (string-prefix-p "!" body) ;; command in msg body (let* ((cmd (downcase (substring body 0 (string-match-p "[^[:word:]!]= " body)))) (fcn (cdr (assoc-string cmd jami-bot-command-function-alist)))) ;; remove the command from the message body (setcdr (assoc-string "body" msg) (list (string-trim-left (string-remove-prefix cmd body)))) (if fcn ;; call function and reply with return value (jami-bot-reply-to-message account conversation msg (funcall fcn account conversation msg)) ;; no matching command defined: ;; report error as reply to msg (jami-bot-reply-to-message account conversation msg (format "Unknown command: %s" cmd)))) ;; not a command in msg body: run hook instead (run-hook-with-args 'jami-bot-text-message-functions account conversation msg)))) (defun jami-bot--command-function-ping (_account _conversation msg) "Return the string \"pong!\" followed by the message body. Example for a basic `jami-bot' command handling function. Acts on MSG received via _ACCOUNT in _CONVERSATION. The latter two are unused in this case. This function is intended to be mapped to a particular `jami-bot' command string by adding both to `jami-bot-command-function-alist'. See documentation for the handler function, `jami-bot--messageReceived-handler' for details on typical MSG alist key/value pairs." (let ((body (cadr (assoc-string "body" msg)))) (format "pong! %s" body))) (defun jami-bot--command-function-help (_account _conversation _msg) "Return a summary of all configured commands. Commands are configured and made available by adding them to `jami-bot-command-function-alist'. Acts on _MSG received via _ACCOUNT in _CONVERSATION, none of which are used in this particular command function." (let (result) (dolist (cmd jami-bot-command-function-alist (string-join result "\n")) (push (concat "- " (car cmd) " :: " (car (split-string (documentation (cdr cmd)) "\n"))) re= sult)))) (defun jami-bot--process-data-transfer (account conversation msg) "Process data transfer from received messages. Downloads files to the path given by `jami-bot-download-path' and calls the abnormal hook `jami-bot-data-transfer-functions' for further processing. ACCOUNT and CONVERSATION are the corresponding ids to which the message MSG belongs to." (let* ((id (cadr (assoc-string "id" msg))) (fileid (cadr (assoc-string "fileId" msg))) (filename (cadr (assoc-string "displayName" msg))) (dlpath (file-name-as-directory (expand-file-name jami-bot-download-path))) (dlname (expand-file-name (concat (format-time-string "%Y%m%d-%H%M") "_" filename) dlpath))) (unless (file-directory-p dlpath) (make-directory dlpath 't)) (message "jami-bot: downloading file %s" dlname) (jami-bot--dbus-cfgmgr-call-method "downloadFile" account conversation = id fileid dlname) (run-hook-with-args 'jami-bot-data-transfer-functions account conversat= ion msg dlname))) (defun jami-bot--dbus-cfgmgr-call-method (method &rest args) "Call Jami ConfigurationManager dbus METHOD with arguments ARGS." (apply #'dbus-call-method `(:session "cx.ring.Ring" "/cx/ring/Ring/ConfigurationManager" "cx.ring.Ring.ConfigurationManager" ,method ,@(when args args)))) (defun jami-bot-send-message (account conversation text &optional reply) "Add TEXT to CONVERSATION via ACCOUNT. REPLY specifies a message id." (jami-bot--dbus-cfgmgr-call-method "sendMessage" account conversation text `(,@(if reply reply "")) :int32 0)) (defun jami-bot-reply-to-message (account conversation msg text) "Add TEXT as a reply to MSG in CONVERSATION via ACCOUNT." (let ((id (cadr (assoc-string "id" msg)))) (jami-bot-send-message account conversation text id))) (defun jami-bot--refresh-accountid-list () "Update cached values of known local account ids. The values are stored in `jami-bot--jami-local-account-ids'." (let ((accounts (jami-bot--dbus-cfgmgr-call-method "getAccountList"))) (let ((value)) (dolist (acc accounts) (push (cons (cadr (assoc-string "Account.username" (jami-bot--dbus-cfgmgr-call-method "getAccountDetails" acc))) acc) value)) (setq jami-bot--jami-local-account-ids value))) jami-bot--jami-local-account-ids) (provide 'jami-bot) ;;; jami-bot.el ends here --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=org-jami-bot.el Content-Transfer-Encoding: quoted-printable ;;; org-jami-bot.el --- Capture GNU Jami messages as notes and todos in Org= mode -*- lexical-binding: t; -*- ;; Copyright (C) 2023 Free Software Foundation, Inc. ;; Author: Hanno Perrey ;; Maintainer: Hanno Perrey ;; Created: April 16, 2023 ;; Modified: February 4, 2024 ;; Version: 0.0.5 ;; Keywords: comm, outlines, org-capture, jami ;; Homepage: https://gitlab.com/hperrey/org-jami-bot ;; Package-Requires: ((emacs "28.1") (jami-bot "0.0.4")) ;; This file is not part of GNU Emacs. ;; ;; 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 3 of the License, 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, see . ;;; Commentary: ;; ;; `org-jami-bot' builds upon `jami-bot' and extends it with Org mode captu= re ;; functionality for text messages and images. It allows to schedule agenda ;; items at specific dates, compose multi-measure captures and capture imag= es -- ;; all by sending a message via the GNU Jami messenger. ;; ;; `org-jami-bot' provides multi-message capture from within the Jami messe= nger ;; app -- that is, a capture process that consists of several messages and = can ;; include even images and other files. The process is started by sending = the ;; command "!start" followed by the title of the capture and finished by se= nding ;; "!done". Once the multi-message capture session is started, every follo= wing ;; message is simply added. This includes images which will be downloaded = and ;; stored locally. A reference in the form of a link will be included in t= he ;; notes. ;; ;; Every command consists of an exclamation mark and a single word, for exa= mple: ;; "!help" which shows the available commands or "!today" which captures the ;; remainder of the message as a todo entry scheduled today. "!schedule", = on ;; the other hand, allows to schedule a captured entry to a arbitrary day g= iven ;; by the date on the first line of the message. Everything else is treate= d as ;; a normal message (and captured verbatim). ;; ;; Files sent separately as a single message are captured as links to the ;; locally downloaded file and tagged as =3DFILE=3D. In principle, further= automatic ;; processing (e.g. OCR) could easily be integrated. Any received file wil= l also ;; be added to the variable =3Dorg-stored-links=3D and can then be easily i= nserted ;; as link in any Org mode document using =3DC-c C-l=3D. ;; ;; To get started with `org-jami-bot': ;; - install jamid, the GNU Jami daemon ;; - set up a local Jami account using e.g. Jami's GUI client ;; - configure a key to be used for org-capture templates: ;; (setq org-jami-bot-capture-key "J") ;; - set up `org-jami-bot' using default values: ;; (org-jami-bot-default-setup) ;; - register `jami-bot' to react to received messages: ;; (jami-bot-register) ;; ;; `org-jami-bot-default-setup' contains everything needed to set up ;; `org-jami-bot': it creates a suitable capture template, makes the above ;; mentioned commands available via Jami messages to local accounts and adds ;; hooks to capture other plain text messages and file transfers. Copy and ;; modify `org-jami-bot-default-setup' to customize this setup to your need= s. ;;; Code: (require 'jami-bot) (require 'org) (require 'org-capture) (defcustom org-jami-bot-capture-key "J" "Key for the `org-capture' template to call for Jami messages." :group 'jami-bot :type 'string) (defun org-jami-bot--capture-plain-message (account conversation msg) "Capture body in MSG and replies with acknowledgement to original message. CONVERSATION and ACCOUNT specify the corresponding ids that the message belongs to. See documentation for the `jami-bot' message handler function, `jami-bot--messageReceived-handler' for details on key/value pairs typically present in MSG." (let* ((buf (format "*jami-capture-%s-%s*" account conversation)) (continue (get-buffer buf)) (body (cadr (assoc-string "body" msg))) (lines (string-lines body)) ;; use inactive timestamps (timefmt (org-time-stamp-format 't 't))) (with-current-buffer (get-buffer-create buf) (insert (if continue ;; multi message capture (concat body "\n") ;; single message capture (format "* %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n%s" (car lines) (format-time-string timefmt) (string-jo= in (cdr lines) "\n")))) (jami-bot-reply-to-message account conversation msg (if continue "message added. Finish capture with \"!done\"" (if (and (org-capture-string (buffer-string) org-jami-bot-capture-key) (kill-buffer buf)) "captured!" "error during org-capture :(")))))) (defun org-jami-bot--command-function-start (account conversation msg) "Initiate a multi-message capture with the body of the current MSG. This function creates a capture buffer for CONVERSATION and ACCOUNT. Further plain text messages processed by `org-jami-bot--capture-plain-message' or files received by `org-jami-bot--capture-file' will be added to this capture buffer. The actual capture needs to happen through a separate function, e.g. `org-jami-bot--command-function-done'. Return a reply string informing correspondent about how to finish capture by sending '!done'. This function is intended to be mapped to the `jami-bot' command string \"!start\" by adding both to `jami-bot-command-function-alist' or by calling `org-jami-bot-default-setup'." (let* ((buf (format "*jami-capture-%s-%s*" account conversation)) (body (cadr (assoc-string "body" msg))) (lines (string-lines body)) ;; use inactive timestamps (timefmt (org-time-stamp-format 't 't))) (with-current-buffer (get-buffer-create buf) (insert (if (string-empty-p buf) (format "* Multi-message note capture %s\n:PROPERTIES:\n:= CREATED: %s\n:END:\n" (format-time-string timefmt) (format-time-string = timefmt)) (format "* %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n%s" (car lines) (format-time-string timefmt) (string-jo= in (cdr lines) "\n")))) "Multi-message capture started. Finish capture with \"!done\""))) (defun org-jami-bot--command-function-done (account conversation _msg) "Finish multi-message capture and return a confirmation string. Requires a capture buffer set up for CONVERSATION and ACCOUNT, for example through `org-jami-bot--command-function-start'. This function is intended to be mapped to the `jami-bot' command string \"!done\" by adding both to `jami-bot-command-function-alist' or by calling `org-jami-bot-default-setup'." (let* ((buf (format "*jami-capture-%s-%s*" account conversation)) (continue (get-buffer buf))) (if continue (with-current-buffer (get-buffer-create buf) (if (and (org-capture-string (buffer-string) org-jami-bot-capture-key) (kill-buffer buf)) "capture finished!" "error during org-capture :(")) "No capture to finish. Start multi-message capture with \"!start\""))) (defun org-jami-bot--capture-file (account conversation msg dlname) "Capture downloaded file and reply to original message. DLNAME specifies local file name downloaded from MSG in CONVERSATION for jami ACCOUNT. This function will add a link to the file and store meta information such as the timestamp in a PROPERTIES drawer. See documentation for the `jami-bot' message handler function, `jami-bot--messageReceived-handler' for details on key/value pairs typically present in MSG. This function is intended to be added as hook to `jami-bot-data-transfer-functions'." (let* ((buf (format "*jami-capture-%s-%s*" account conversation)) (continue (get-buffer buf)) (displayname (cadr (assoc-string "displayName" msg))) (timestamp (string-to-number (cadr (assoc-string "timestamp" msg))= )) ;; use inactive timestamps (timefmt (org-time-stamp-format 't 't))) (with-current-buffer (get-buffer-create buf) (let ((link ;; link to downloaded file (concat "file:" (condition-case nil ;; try to create a link relative to the target cap= ture file (file-relative-name dlname (file-name-directory (org-capture-expand-file (cadr (nth 3 (assoc org-jami-bot-c= apture-key org-capture-te= mplates)))))) ;; if this fails, use the absolute path instead (error dlname))))) (insert (if continue ;; multi message capture (concat "#+ATTR_ORG: :width 400\n" (org-link-make-string link) "\n") ;; single message capture (format "* FILE %s :FILE:\n:PROPERTIES:\n\ :CREATED: %s\n:JAMI_TIMESTAMP: %s\n:END:\n\n#+ATTR_ORG: :width 400\n%s\n" (org-link-make-string link displayname) (format-time-string timefmt) (format-time-string timefmt timestamp) (org-link-make-string link))))) ;; store link for easy linking (push (list dlname displayname) org-stored-links) (jami-bot-reply-to-message account conversation msg (if continue "file added. Finish capture with \"!done\"" (if (and (org-capture-string (buffer-string) org-jami-bot-capture-key) (kill-buffer buf)) "captured!" "error during org-capture :(")))))) (defun org-jami-bot--command-function-today (_account _conversation msg) "Capture body of message as todo entry scheduled today. Returns a reply string as confirmation. MSG is the full message alist in CONVERSATION id for ACCOUNT id. This function is intended to be mapped to a `jami-bot' command string, e.g. \"!today\" by adding both to `jami-bot-command-function-alist' or by calling `org-jami-bot-default-setup'." (let* ((body (cadr (assoc-string "body" msg))) (lines (string-lines body)) ;; use inactive timestamps (timefmt (org-time-stamp-format 't 't))) (if (org-capture-string (format "* TODO %s\nSCHEDULED: %s\n:PROPERTIES:\n:CREATED: %s\n:EN= D:\n%s" (car lines) (format-time-string (car org-time-stamp-formats)) (format-time-string timefmt) (string-join (cdr lines) "\n")) org-jami-bot-capture-key) "captured and scheduled!" "error during org-capture :("))) (defun org-jami-bot--command-function-schedule (_account _conversation msg) "Capture body as todo entry and schedule it on the date given after the c= ommand. The entry will be scheduled according to the first line of the MSG body immediately following the command string. The date will be parsed through `org-read-date' and supports the same string-to-date conversations. Returns a reply string as confirmation. ACCOUNT and CONVERSATION are not used. This function is intended to be mapped to a `jami-bot' command string, e.g. \"!schedule\" by adding both to `jami-bot-command-function-alist' or by calling `org-jami-bot-default-setup'." (let* ((body (cadr (assoc-string "body" msg))) (lines (string-lines body)) (swhen (org-read-date nil nil (car lines))) ;; inactive timestamp (timefmt (org-time-stamp-format 't 't))) (if (org-capture-string (format "* TODO %s\nSCHEDULED: %s\n:PROPERTIES:\n:CREATED: %s\n:EN= D:\n%s" (cadr lines) swhen (format-time-string timefmt) (string-join (cdr lines) "\n")) org-jami-bot-capture-key) (format "captured and scheduled on %s!" swhen) "error during org-capture :("))) (defun org-jami-bot-default-setup () "Set up `org-jami-bot' with default values. This function creates a capture template on the key given by `org-jami-bot-capture-key' for the file `org-default-notes-file' that is used by all `org-jami-bot' captures. It also extends the list of `jami-bot' commands, `jami-bot-command-function-alist', with the following commands: \"!today\" Capture a TODO item scheduled for today, see `org-jami-bot--command-function-today'. \"!schedule\" Capture a TODO item scheduled on the day specified by the fi= rst line of the message body, see `org-jami-bot--command-function-schedule'. \"!start\" Initiate a multi-message capture. All following mesages will be appended, see `org-jami-bot--command-function-start'. \"!done\" Finish a multi-message capture, see `org-jami-bot--command-function-done'. Additionally, the function sets up the necessary `jami-bot' hooks to capture other text messages not containing commands as well as file transfers. See `org-jami-bot--capture-plain-message' and `org-jami-bot--capture-file', respectively." (if (assoc org-jami-bot-capture-key org-capture-templates) (message "Capture template referred to by \"%s\" key already defined!" org-jami-bot-capture-key) (add-to-list 'org-capture-templates `(,org-jami-bot-capture-key "Jami message" entry (file org-default-notes-file) "%i" :immediate-finish t))) (dolist (cmd '(("!today" . org-jami-bot--command-function-today) ("!schedule" . org-jami-bot--command-function-schedule) ("!start" . org-jami-bot--command-function-start) ("!done" . org-jami-bot--command-function-done))) (add-to-list 'jami-bot-command-function-alist cmd)) (add-hook 'jami-bot-text-message-functions #'org-jami-bot--capture-plain-= message) (add-hook 'jami-bot-data-transfer-functions #'org-jami-bot--capture-file)) (provide 'org-jami-bot) ;;; org-jami-bot.el ends here --=-=-=--