<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
 xmlns:content="http://purl.org/rss/1.0/modules/content/"
 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:atom="http://www.w3.org/2005/Atom"
 xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
 xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
 xmlns:georss="http://www.georss.org/georss"
 xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
 xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<atom:link href="https://www.hoowl.se/rss.xml" rel="self" type="application/rss+xml" />
<title>hoowl</title>
<link>https://www.hoowl.se</link>
<description><![CDATA[Blog posts by hanno]]></description>
<language>en</language>
<lastBuildDate>Fri, 03 Oct 2025 22:02:53 +0200</lastBuildDate>
<generator>Emacs 30.1 org-publish-rss.el 0.8</generator>
<image>
<url>http://www.hoowl.se/images/android-chrome-256x256.png</url>
<title>hoowl</title>
<link>https://www.hoowl.se</link>
</image>
<item>
<title>Emacs: Salutation auto-complete for mail</title>
<link>https://www.hoowl.se/emacs_salutation_auto_complete_for_mail.html</link>
<pubDate>Mon, 25 Jul 2022 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/emacs_salutation_auto_complete_for_mail.html</guid>
<description>
<![CDATA[<p>
I use email quite extensively in my every-day communication, at least at work.
The style is usually half-formal, with a salutation but on a first-name basis as
it is customary in Sweden. Since quite a number of the mails I write or reply to
are to first-time contacts, I am even more nervous than usual to misspell
someone&rsquo;s name and always feel the need to double- or even tripple-check for
typos. So, why not automatize this step, extract the recipients&rsquo; names with some
Elisp code and provide salutation completion with a single keystroke?
</p>

<p>
The result will look something like this:
</p>

<div id="org1cb7da8" class="figure">
<p><img src="https://www.hoowl.se/./../images/posts/emacs/yas_salutation.gif" alt="&quot;Animation demonstrating the salutation completion using yassnippet and some made-up email addresses&quot;" />
</p>
</div>

<p>
This builds on the power of the <code>yas</code> package in Emacs which I already mentioned
in <a href="https://www.hoowl.se/auto_inserting_gitignore_templates_in_emacs.html">a previous post for auto-inserting file templates</a>. Using the lisp function
below (inspired by <a href="https://pragmaticemacs.com/emacs/email-templates-in-mu4e-with-yasnippet/">this blog post</a>), we can extract the first name of the
recipients of a mail and add them to a <code>yasnippet</code> template to create a
personalized greeting for any number of people the mail is addressed to.
</p>

<p>
The code parses both the TO field of the email as well as the quoted FROM field
(&ldquo;XYZ writes:&rdquo;) to determine the first names of all recipients. The latter field
is only used in case the TO field only contains an email address and matches
that of the quoted FROM field. That makes sure that forwarding mails does not
mess up the greeting.
</p>

<p>
In any case, should we only have an email address for the recipient, the first
name is guessed using the first (period-separated) part of the address.
</p>

<p>
This is the code:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">hanno/msg-get-names-for-yasnippet</span> ()
<span class="linenr"> 2: </span>  <span class="org-doc">"Return comma separated string of recipients' names for an email.
<span class="linenr"> 3: </span>
<span class="linenr"> 4: </span>Uses information from both the TO field and the quoted FROM field.
<span class="linenr"> 5: </span>Guesses first name from the (period-separated) email address if
<span class="linenr"> 6: </span>no name string is known."</span>
<span class="linenr"> 7: </span>  (<span class="org-keyword">interactive</span>)
<span class="linenr"> 8: </span>  (<span class="org-keyword">let</span> (email-name str email-string email-list email-ff email-name-ff tmpname)
<span class="linenr"> 9: </span>    (<span class="org-keyword">save-excursion</span>
<span class="linenr">10: </span>      (goto-char (point-min))
<span class="linenr">11: </span>      <span class="org-comment-delimiter">;; </span><span class="org-comment">first line in email could be some hidden line containing NO to field
<span class="linenr">12: </span></span>      (<span class="org-keyword">setq</span> str (buffer-substring-no-properties (point-min) (point-max))))
<span class="linenr">13: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">take name from TO field - match series of names
<span class="linenr">14: </span></span>    (<span class="org-keyword">when</span> (string-match <span class="org-string">"^To: </span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.+</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">"</span> str)
<span class="linenr">15: </span>      (<span class="org-keyword">setq</span> email-string (match-string 1 str)))
<span class="linenr">16: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">split to list by comma
<span class="linenr">17: </span></span>    (<span class="org-keyword">setq</span> email-list
<span class="linenr">18: </span>      (split-string
<span class="linenr">19: </span>       <span class="org-comment-delimiter">;; </span><span class="org-comment">flip "last, first" name combos
<span class="linenr">20: </span></span>       (replace-regexp-in-string
<span class="linenr">21: </span>        <span class="org-string">"\"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.*</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">,</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.*</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">\""</span> <span class="org-string">"\\2 \\1"</span>
<span class="linenr">22: </span>        email-string) <span class="org-string"><span class="org-warning">" *, *"</span></span><span class="org-warning">))</span>
<span class="linenr">23: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">extract the name in the FROM field ("XYZ writes:");
<span class="linenr">24: </span></span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">sometimes this field contains more info than the TO field
<span class="linenr">25: </span></span>    (<span class="org-keyword">when</span> (string-match <span class="org-string">"^</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">[</span><span class="org-string"><span class="org-negation-char">^</span></span><span class="org-string"> ,\n]+</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">.+writes:$"</span> str)
<span class="linenr">26: </span>      (<span class="org-keyword">setq</span> email-name-ff (match-string 1 str)))
<span class="linenr">27: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">extract the email in the FROM field ("XYZ writes:")
<span class="linenr">28: </span></span>    (<span class="org-keyword">when</span> (string-match <span class="org-string">"^.+&lt;</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">[</span><span class="org-string"><span class="org-negation-char">^</span></span><span class="org-string"> ,\n]+</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">&gt; writes:$"</span> str)
<span class="linenr">29: </span>      (<span class="org-keyword">setq</span> email-ff (match-string 1 str)))
<span class="linenr">30: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">loop over emails in TO field
<span class="linenr">31: </span></span>    (<span class="org-keyword">dolist</span> (tmpstr email-list)
<span class="linenr">32: </span>      <span class="org-comment-delimiter">;; </span><span class="org-comment">get first word of email string
<span class="linenr">33: </span></span>      (<span class="org-keyword">setq</span> tmpname (car (split-string tmpstr <span class="org-string">" "</span> 't <span class="org-string">" "</span>)))
<span class="linenr">34: </span>      <span class="org-comment-delimiter">;; </span><span class="org-comment">remove whitespace or "" or '</span><span class="org-comment"><span class="org-constant">&lt;&gt;</span></span><span class="org-comment">'
<span class="linenr">35: </span></span>      (<span class="org-keyword">setq</span> tmpname (replace-regexp-in-string <span class="org-string">"[ \"&lt;&gt;]"</span> <span class="org-string">""</span> tmpname))
<span class="linenr">36: </span>      <span class="org-comment-delimiter">;; </span><span class="org-comment">only got mail address?
<span class="linenr">37: </span></span>      (<span class="org-keyword">when</span> (string-match <span class="org-string">"@"</span> tmpname)
<span class="linenr">38: </span>        <span class="org-comment-delimiter">;; </span><span class="org-comment">check if it matches FROM field
<span class="linenr">39: </span></span>        <span class="org-comment-delimiter">;; </span><span class="org-comment">and that a name was found there
<span class="linenr">40: </span></span>        (<span class="org-keyword">if</span> (<span class="org-keyword">and</span>
<span class="linenr">41: </span>             email-ff
<span class="linenr">42: </span>             (string-match email-ff tmpname)
<span class="linenr">43: </span>             email-name-ff)
<span class="linenr">44: </span>            <span class="org-comment-delimiter">;; </span><span class="org-comment">replace with name from FROM field
<span class="linenr">45: </span></span>            (<span class="org-keyword">setq</span> tmpname email-name-ff)
<span class="linenr">46: </span>          <span class="org-comment-delimiter">;; </span><span class="org-comment">otherwise, extract (first) name from mail address
<span class="linenr">47: </span></span>          (<span class="org-keyword">progn</span>
<span class="linenr">48: </span>            (<span class="org-keyword">setq</span> tmpname (car (split-string tmpname <span class="org-string">"@"</span>)))
<span class="linenr">49: </span>            (<span class="org-keyword">setq</span> tmpname (car (split-string tmpname <span class="org-string">"\\."</span>))))))
<span class="linenr">50: </span>      <span class="org-comment-delimiter">;; </span><span class="org-comment">add to list
<span class="linenr">51: </span></span>      (<span class="org-keyword">setq</span> email-name (cons (capitalize tmpname) email-name)))
<span class="linenr">52: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">convert to comma-separated string
<span class="linenr">53: </span></span>    (mapconcat 'identity (nreverse email-name) <span class="org-string">", "</span>)))
</code></pre>
</div>

<p>
I am using <code>mu4e</code> for managing my mail, but this code should work for any
composition buffer that builds upon <code>message-mode</code>.
</p>

<p>
Running <code>yas-new-snippet</code>, create a new snippet for <code>message-mode</code> with the following contents:
</p>
<div class="org-src-container">
<pre class="src src-snippet"><code># -*- mode: snippet -*-
# name: hej name
# key: hej
# --
Hej ${1:`(mapconcat 'identity (split-string (hanno/msg-get-names-for-yasnippet) ", " t) ", hej ")`},

$0

Mvh,

Hanno
</code></pre>
</div>

<p>
Once active, you only need to write &ldquo;hej&rdquo; (the usual informal salutation in
Swedish) and press <code>TAB</code> to complete the names. The <code>mapconcat</code> in the snippet
makes sure that each person is greeted individually.
</p>

<p>
This has been working very well for me the past year, and I only tuned the code
slightly to better deal with some typical challenges I encounter in my mails. Of
course, you can easily extend this to other snippets using different salutation
forms or even extract last names instead for first names as above. Let me know
should you take this in interesting new directions! :)
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/mail.html">mail</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a></p>
]]>
</description></item>
<item>
<title>Revising history: How to clean a git project prior to publication</title>
<link>https://www.hoowl.se/revising_history_cleaning_a_git_project_prior_to_publication.html</link>
<pubDate>Mon, 27 Jul 2020 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/revising_history_cleaning_a_git_project_prior_to_publication.html</guid>
<description>
<![CDATA[<p>
Version control systems such as <code>git</code> are amazing tools for software development
and most projects involving more than a handful developers would hardly work
without them. Especially <code>git</code> has proven to be incredibly useful for me even
in small, personal projects as well as for other things than source code, such
as my notes and (system) configuration files. Being able to review an evening&rsquo;s
worth of frantic hacking or figuring out why things look different from what I
remember when returning weeks later really eases my mind.
</p>

<p>
But of course, I usually don&rsquo;t keep the same &rsquo;hygiene&rsquo; for those private repositories
and commit binary files, include passwords or other secrets and, on
occasion, add rather explicit comments. That can be an obstacle when one
later wants to publish any of those repositories after all.
</p>

<p>
Of course, one could simply strip all of the history created by <code>git</code> e.g. by
deleting the <code>.git/</code> directory in the root of the repository and creating a
fresh repository from the current working directory. When that is not desired,
there is an alternative option though: <a href="https://github.com/newren/git-filter-repo">git-filter-repo</a>.
</p>

<p>
With <code>git-filter-repo</code>, one can easily rewrite the history of a repository:
removing files, changing commit messages or even search-replace strings across
all files to remove e.g. passwords. To be clear, <b>this will lead to incompatible
histories with your collaborators</b> in most cases &ndash; so use with care, make a backup
and read the <a href="https://github.com/newren/git-filter-repo/blob/main/Documentation/git-filter-repo.txt">documentation</a>! The latter provides several useful examples. Here is
some of the ones I have already found applications for:
</p>
<section id="outline-container-org3acfe6b" class="outline-2">
<h2 id="org3acfe6b">Removing files accidentally included in commits</h2>
<div class="outline-text-2" id="text-org3acfe6b">
<p>
As a version control system, <code>git</code> keeps a copy of any file deleted from the current working
tree in its history, forever.
</p>

<p>
To search for files that were deleted but are still accessible in the history, you can run <code>git log</code> and filter on deletions:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code>git log --diff-filter=D --summary
</code></pre>
</div>

<p>
Usually, that is exactly what one expects and not a problem at all. But at some
point one might have committed a very large file, a bunch of temporary files or files
containing sensitive information. So, how to get rid of these for good?
</p>

<p>
As an example, when I was still using Apple OSX, I sometimes included those
<code>.DS_Store</code> meta information files that the OS scatters across the file system.
No reason to keep them accumulating in the repository&rsquo;s history:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>git filter-repo --invert-paths --path <span class="org-string">'.DS_Store'</span> --use-base-name
</code></pre>
</div>

<p>
<code>--use-base-name</code> matches on file base names instead of full paths and <code>--invert-paths</code> keeps anything <b>not</b> matching.
To match a pattern, you can use instead
</p>
<div class="org-src-container">
<pre class="src src-bash"><code>git filter-repo --invert-paths --path-glob <span class="org-string">'*/*.jpg'</span>
</code></pre>
</div>
<p>
which removes all files with the <code>jpg</code> extension from any path.
</p>
</div>
</section>
<section id="outline-container-org105ea4c" class="outline-2">
<h2 id="org105ea4c">Removing sensitive information</h2>
<div class="outline-text-2" id="text-org105ea4c">
<p>
If you entered sensitive information into text or source files that you don&rsquo;t
want to delete entirely, you can even replace those passwords or explicit
language with something safe to push to the outside world! Look for any mention
of those unwanted strings of text in any commit first:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>git log -SMyPassword -c
</code></pre>
</div>

<p>
This searches for any mention of &ldquo;MyPassword&rdquo; in any commit. Should the command
return any results, you can use <code>git-filter-repo</code> to replace the sensitive
string with something else. Create a file <code>expresions.txt</code> which contains the
desired translations:
</p>

<div class="org-src-container">
<pre class="src src-nil"><code>MyPassword ==&gt; ***REMOVED***
</code></pre>
</div>

<p>
Even regular expressions are allowed if you prefix the line with <code>regex:</code>. Then apply the replacements:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>git filter-repo --replace-text expressions.txt
</code></pre>
</div>
</div>
</section>
<section id="outline-container-org5137c15" class="outline-2">
<h2 id="org5137c15">Changing author name and/or email</h2>
<div class="outline-text-2" id="text-org5137c15">
<p>
In case you have used a different commit author name or email address from the
one you want to see publicly, you can permanently change it across all commits
quite easily. First, you have to create a file containing the mapping of the old
to the new name/email. This file follows the <a href="https://git-scm.com/docs/git-check-mailmap"><code>mailmap</code></a> format. For adjustments
to only the email address, use for example:
</p>

<div class="org-src-container">
<pre class="src src-nil"><code>&lt;proper@email.example.com&gt; &lt;commit@email.example.com&gt;
</code></pre>
</div>

<p>
If you called this file <code>mailmap.txt</code>, then simply execute
</p>
<div class="org-src-container">
<pre class="src src-bash"><code>git filter-repo --mailmap mailmap.txt
</code></pre>
</div>
<p>
to make the changes.
</p>
</div>
</section>
<section id="outline-container-orgb9621d6" class="outline-2">
<h2 id="orgb9621d6">Summary</h2>
<div class="outline-text-2" id="text-orgb9621d6">
<p>
<code>git-filter-repo</code> is a powerful tool to clean up a <code>git</code> repository once you
decide to make your private project public and share it with others. Your
cleaned repository will end up with a rewritten history that is incompatible
with previous incarnations &ndash; so keep that in mind should you already have a
public copy out there!
</p>

<p>
<b>Comments?</b> Leave them as reply to <a href="https://fosstodon.org/web/statuses/104585986555007205">the post on Mastodon</a>.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/git.html">git</a>, <a href="https://www.hoowl.se/../tags/programming.html">programming</a></p>
</div>
</section>
]]>
</description></item>
<item>
<title>New Blog and On Blogging with Emacs</title>
<link>https://www.hoowl.se/bloggingwithemacs.html</link>
<pubDate>Sat, 13 Jun 2020 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/bloggingwithemacs.html</guid>
<description>
<![CDATA[<p>
A warm welcome to my first blog post! :)
</p>

<p>
This is not the first time I attempt to keep a blog and have an online presence.
But this time around, I have a much more coherent vision and many more ideas of
what to actually write about. Having come to an actual first published post, in
fact, a big step for me! For this to continue to work, I need to both be able to
identify myself with the end result as well as enjoy the process of creating
something. The whole thing needs to fit and be <i>me</i>. Or, at least, have the
potential to become something I appreciate.
</p>

<p>
This is where Emacs comes in. It has been my tool of choice for many of my
computing tasks whether at work or at home. Becoming more and more familiar with
its patterns, I am starting to be able to express myself most efficiently when
in front of an Emacs frame. But more than just familiarity, it is <i>fun</i> to use
Emacs. Behind every function and every variable, there lies potential to learn
something powerful and very useful. Thanks to the context-aware help system that
is just a keypress away, much of this power becomes more and more accessible
once one is over an initial threshold. This allows me to do my computing with
the feeling of actually controlling what is going on and not just adjusting to
someone else&rsquo;s vision of how I am to use the computer.
</p>

<p>
But back to blogging. When I realized that I could use Emacs not only to collect
my thoughts, structure them into cross-linked notes, and write them into
publishable posts using <code>org-mode</code>, but even for <i>the publishing itself</i>, I was
intrigued again on the idea of having a blog.
</p>

<p>
Of course, thanks to the strong and collaborative community around Emacs, I
didn&rsquo;t have to start from scratch. Building on the code and guide of <a href="https://opensource.com/article/20/3/blog-emacs">Sachin
Patil</a>, I quickly had a running setup. I found many other sources of inspiration
online. Noteworthy mentions are posts by <a href="https://duncan.codes/posts/2019-09-03-migrating-from-jekyll-to-org/index.html">duncan</a>, <a href="https://loomcom.com/blog/0110_emacs_blogging_for_fun_and_profit.html">loomcom</a> and <a href="https://pank.eu/blog/blog-setup.html">pank</a>. Tying
everything I liked together and adding anything missing (such as adding linked
keywords) was a welcome challenge and a chance to learn more about Emacs.
</p>

<p>
A recent find that would probably have been a good starting point as well was
the <a href="https://github.com/bastibe/org-static-blog">static blog with org-mode</a> repository &ndash; oh well, I am quite happy with what
I ended up with :)
</p>

<p>
While I see myself as rather creative, coming up with original designs and
concepts is not my strong suite. It does happen but takes a <b>long</b> time and
many, many discarded drafts. So I am incredible thankful for the inspiring ideas
and suggestions by <a href="http://abohemianpeasant.com/">Melisa Özdilek</a> who created a very personal and fitting theme
for me :)
</p>

<p>
(Putting this into code and style sheets was my doing though, so any quirks in
the implementation are solely on me.)
</p>

<p>
I personally believe that our technical choices matter, and nowadays we do have
them. Therefore, I didn&rsquo;t want to go with whatever hosting was free <i>right now</i>
or whoever was eager to host <i>my</i> content under their domain &ndash; so I went with
<a href="https://hcoop.net/">HCoop</a>, who provide cooperative internet hosting. Nice personal contact, an
impressive configuration tool and SSH as well as AFS (!) access are the icing on
the cake. Try to get anything like that elsewhere without having all
responsibility dumped on you as well.
</p>

<p>
If you want to take a peek at the gears behind this blog, you can find <a href="https://gitlab.com/hperrey/hoowl_blog">its source code on gitlab</a>.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/thoughts.html">thoughts</a>, <a href="https://www.hoowl.se/../tags/blog.html">blog</a></p>
]]>
</description></item>
<item>
<title>Finding the right and open font for me</title>
<link>https://www.hoowl.se/finding_the_right_and_open_font_for_me.html</link>
<pubDate>Sun, 30 Aug 2020 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/finding_the_right_and_open_font_for_me.html</guid>
<description>
<![CDATA[<p>
I have been looking into a more suitable font choice for my blog. While my original and rather spontaneous choice, <a href="https://github.com/mozilla/Fira">Mozilla&rsquo;s &ldquo;Fira&rdquo;</a>, is clean-looking it was a bit too smooth for my taste. I am looking for a calm font with a hint more serifs and a dash of charm. And, of course, without any restrictive license or the need to embed scripts or CSS hosted elsewhere.
</p>
<section id="outline-container-org24830ad" class="outline-2">
<h2 id="org24830ad">The Technical</h2>
<div class="outline-text-2" id="text-org24830ad">
<p>
Since my web skills have been rusting for more than 10 years, I was pleasantly surprised about the amount of options and the level of support in all major browsers for external fonts. Even self-hosting is little more than dropping in a single file or two into your web host&rsquo;s file structure and adding a bit of CSS! I found <a href="https://opensource.com/article/19/3/webfonts">this blog entry on opensource.com</a> very helpful.
</p>
</div>
</section>
<section id="outline-container-org79f3d2e" class="outline-2">
<h2 id="org79f3d2e">Open Fonts: So much choice!</h2>
<div class="outline-text-2" id="text-org79f3d2e">
<p>
Concerning the actual fonts, that is a bit more tricky. Not so much because they are difficult to find though: even with the requirement of having an open font license, there is a ton of choice. A good starting point for me was <a href="https://opensource.com/life/16/2/top-sources-open-source-fonts">another blog post on opensource.com</a>. The challenge was more to find the &ldquo;right&rdquo; font that I like and that suits the blog. Here, my very general interest in fonts is quickly overwhelmed by the amount of variables: one font might look interesting in the headlines but produces flow text that is tiring to read. Another produces odd-looking results when rendering special characters common in various European languages such as &rsquo;ü&rsquo; or &rsquo;å&rsquo;.
</p>
</div>
</section>
<section id="outline-container-org57487b2" class="outline-2">
<h2 id="org57487b2">Giving it a shot</h2>
<div class="outline-text-2" id="text-org57487b2">
<p>
A very useful in my trial-and-error phase was the &ldquo;Inspector&rdquo; tool of Firefox (opened by right-clicking somewhere on the page and selecting &ldquo;Inspect Element&rdquo;). Any change to any font property is updated in real-time and allows to compare results easily. Even fonts with variable weights can be smoothly adjusted with a little slider widget!
</p>


<div id="org4754e4a" class="figure">
<p><img src="https://www.hoowl.se/images/posts/fonts/screenshot.png" alt="screenshot.png" />
</p>
</div>
</div>
</section>
<section id="outline-container-orgfff7fff" class="outline-2">
<h2 id="orgfff7fff">The League of Moveable Type</h2>
<div class="outline-text-2" id="text-orgfff7fff">
<p>
Some really promising candidates I found at <a href="https://www.theleagueofmoveabletype.com/">the league of moveable type</a> who provide open, high-quality fonts in various formats. Not just that, they have a ton of material on typography including articles and even a podcast!
</p>

<p>
So for now I changed to <a href="https://www.theleagueofmoveabletype.com/fanwood">Fanwood</a> by Barry Schwartz.
</p>


<div id="orgd79c29f" class="figure">
<p><img src="https://www.hoowl.se/../../images/posts/fonts/fanwood-1.jpeg" alt="fanwood-1.jpeg" />
</p>
</div>


<p>
Tags: <a href="https://www.hoowl.se/../tags/blog.html">blog</a></p>
</div>
</section>
]]>
</description></item>
<item>
<title>git-annex: Managing my most ancient data</title>
<link>https://www.hoowl.se/git_annex_managing_ancient_data.html</link>
<pubDate>Fri, 15 Nov 2024 00:00:00 +0100</pubDate>
<guid>https://www.hoowl.se/git_annex_managing_ancient_data.html</guid>
<description>
<![CDATA[<p>
:ID:       a841da2f-48f7-43c3-ac52-1ca0c30b1e7c
</p>
<p>
At the moment, my various files are quite spread out between different
computers, a number of USB drives and even old hard drives I usually keep
offline. For the stuff I need access to everyday and always want to have the
newest version in use, such as my notes, Emacs configuration or music files, I
have been relying on <a href="https://syncthing.net/">Syncthing</a>: a private, continuous file synchronisation tool
which only keeps copies of my files on my own devices.
</p>

<p>
Generally, that is a great solution, but for me it breaks down when adding
either large or very many files that I <i>sometimes</i> want access to. If I were to
sync, for example, all video files as well as all archived project files between
all devices, then I would have to invest into new storage first. But if I
<b>don&rsquo;t</b> sync all files, then I am sure to have to search a while before I find
what I am looking for.
</p>

<p>
This is one of the scenarios that <a href="https://git-annex.branchable.com/">git-annex</a> is out to solve. It builds upon
<code>git</code> but allows to keep files in an <i>annex</i> instead for the standard
repository. When cloning the repository, you do not automatically get all the
files in the annex: on your target machine, you will instead only see a (broken)
symbolic link. Just like <code>git</code>, <code>git-annex</code> is decentralized and <a href="https://git-annex.branchable.com/special_remotes/">allows many different types of remotes, including &ldquo;special&rdquo; ones</a> like the hosted storage
offerings of large-brand tech companies. Using <code>git-annex</code>, you can not only
find out how many copies of any given file exist and where they are located but
even retrieve the file with a single command. That is, as long as e.g. the
necessary drive is connected or the remote computer is reachable, depending on
your configuration and distribution of data.
</p>

<p>
This way, you can carry only what you need with you on your laptop knowing that
you can always retrieve missing files on the go. When deleting (&ldquo;dropping&rdquo; in
<code>git-annex</code> terminology) files, <code>git-annex</code> will make sure that a minimum number
of copies are still around to prevent you from deleting the last remaining copy
of your holiday pictures from last year.
</p>

<p>
If you are interested to get started, I found the <a href="https://git-annex.branchable.com/walkthrough/">walkthrough on the project&rsquo;s
homepage</a> very helpful. I would suggest to try it out on a practice repository
first: some concepts, such as &ldquo;unlocking&rdquo; files for editing them take some time
to get used to and might not be the right approach to your data management
needs. I am myself still quite new to it and have been testing <code>git-annex</code> for a
few weeks now. I am quite satisfied with it so far, even if the day-to-day workflows are not really in my muscle memory yet and I am still figuring out how to configure everything to my needs.
</p>

<p>
For me, the main issue so far has been that <code>git-annex</code> by default does not
track the modification time of a file. While it keeps it intact when adding a
file, any cloned repository will have all files and their symlinks appear to
have last been modified when the repository was cloned. So even when I look into
the oldest data I have on this computer, I only see:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>$ ls -lHh *
-r--r--r-- 1 hanno hanno  49K nov 11 10:24 dorf.map
-r--r--r-- 1 hanno hanno  18K nov 11 10:24 end.map
-r--r--r-- 1 hanno hanno  12K nov 11 10:24 fire2.map
-r--r--r-- 1 hanno hanno  32K nov 11 10:24 fire.map
-r--r--r-- 1 hanno hanno 4,1K nov 11 10:24 forfirst.itm
-r--r--r-- 1 hanno hanno 2,7K nov 11 10:24 forfirst.mon
-r--r--r-- 1 hanno hanno 2,4K nov 11 10:24 forfirst.spl
-r--r--r-- 1 hanno hanno  22K nov 11 10:24 forrest.map
-r--r--r-- 1 hanno hanno  14K nov 11 10:24 garten.map
-r--r--r-- 1 hanno hanno  43K nov 11 10:24 kanal.map
-r--r--r-- 1 hanno hanno  45K nov 11 10:24 schloss.map
-r--r--r-- 1 hanno hanno  33K nov 11 10:24 unbek.map
</code></pre>
</div>

<p>
I know those files are older than that!
</p>

<p>
For some files such as photos with embedded meta data, this is not an issue. But
for my old, archived documents and projects, I <i>do</i> want to know when I last
touched them.
</p>

<p>
The good news is that <a href="https://git-annex.branchable.com/metadata/"><code>git-annex</code> has support for arbitrary metadata</a> and even
allows the modification date of a file to be automatically recorded when the
file is being added to the annex: unfortunately, it is off by default, so I ran
<code>git config annex.genmetadata true</code> inside the repository to enable this
feature. Now, we can retrieve the metadata:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>$ git annex metadata 50<span class="org-string">\ </span>Years<span class="org-string">\ </span>of<span class="org-string">\ </span>Text<span class="org-string">\ </span>Games<span class="org-string">\ </span>-<span class="org-string">\ </span>Aaron<span class="org-string">\ </span>A.<span class="org-string">\ </span>Reed.epub
metadata 50 Years of Text Games - Aaron A. Reed.epub
  <span class="org-variable-name">day</span>=27
  day-lastchanged=2024-11-14@12-59-21
  <span class="org-variable-name">lastchanged</span>=2024-11-14@12-59-21
  <span class="org-variable-name">month</span>=07
  month-lastchanged=2024-11-14@12-59-21
  <span class="org-variable-name">year</span>=2024
  year-lastchanged=2024-11-14@12-59-21
ok
</code></pre>
</div>

<p>
Great! It keeps the year, month and date as well as when the respective
information was last changed. However, there are two crucial drawbacks: firstly,
this will only work for files we are adding <i>after</i> this setting became active.
As it is not the default, I already had a bunch of data without this metadata in
my repository. Secondly, while a cloned repository will have this meta
information available too, the file&rsquo;s <code>mtime</code> will still be set to when the
cloning was done and <i>not</i> to the date from the metadata.
</p>

<p>
Let&rsquo;s address the first issue and add <code>mtime</code> metadata to all files in the
repository. I had a newly-cloned annex (without metadata nor the correct
modification times set) as well as an old <code>rsync</code>&rsquo;d copy from which I want to
retrieve and transfer the modification times. So I created a bash script:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr"> 1: </span><span class="org-variable-name">ANNEX</span>=<span class="org-string">"/home/hanno/annex/Documents"</span>
<span class="linenr"> 2: </span><span class="org-variable-name">DOCS_OLD</span>=<span class="org-string">"/home/hanno/Documents.old"</span>
<span class="linenr"> 3: </span>
<span id="coderef-setmeta" class="coderef-off"><span class="linenr"> 4: </span><span class="org-keyword">function</span> <span class="org-function-name">set_meta</span> {</span>
<span class="linenr"> 5: </span>    <span class="org-variable-name">afile</span>=<span class="org-string">"$(</span><span class="org-sh-quoted-exec">echo "$1" | sed "s#$DOCS_OLD#$ANNEX#"</span><span class="org-string">)"</span>
<span class="linenr"> 6: </span>    <span class="org-keyword">if</span> [ -e  <span class="org-string">"$afile"</span> -a <span class="org-negation-char">!</span> -d <span class="org-string">"$afile"</span> ]
<span class="linenr"> 7: </span>    <span class="org-keyword">then</span>
<span class="linenr"> 8: </span>        <span class="org-variable-name">DAY</span>=$(<span class="org-sh-quoted-exec">date -r "$1" "+%d"</span>)
<span class="linenr"> 9: </span>        <span class="org-variable-name">MONTH</span>=$(<span class="org-sh-quoted-exec">date -r "$1" "+%m"</span>)
<span class="linenr">10: </span>        <span class="org-variable-name">YEAR</span>=$(<span class="org-sh-quoted-exec">date -r "$1" "+%Y"</span>)
<span class="linenr">11: </span>        git annex metadata <span class="org-string">"$afile"</span> -s <span class="org-variable-name">day</span>=$<span class="org-variable-name">DAY</span> -s <span class="org-variable-name">month</span>=$<span class="org-variable-name">MONTH</span> -s <span class="org-variable-name">year</span>=$<span class="org-variable-name">YEAR</span>
<span class="linenr">12: </span>    <span class="org-keyword">else</span>
<span class="linenr">13: </span>        <span class="org-keyword">if</span> [ <span class="org-negation-char">!</span> -d <span class="org-string">"$afile"</span> ]
<span class="linenr">14: </span>        <span class="org-keyword">then</span>
<span class="linenr">15: </span>            <span class="org-builtin">echo</span> <span class="org-string">"File not in annex: $afile"</span>
<span class="linenr">16: </span>        <span class="org-keyword">fi</span>
<span class="linenr">17: </span>    <span class="org-keyword">fi</span>
<span class="linenr">18: </span>}
<span class="linenr">19: </span>
<span id="coderef-setmdate" class="coderef-off"><span class="linenr">20: </span><span class="org-keyword">function</span> <span class="org-function-name">set_mdate</span> {</span>
<span class="linenr">21: </span>    <span class="org-variable-name">afile</span>=<span class="org-string">"$(</span><span class="org-sh-quoted-exec">echo "$1" | sed "s#$DOCS_OLD#$ANNEX#"</span><span class="org-string">)"</span>
<span class="linenr">22: </span>    <span class="org-keyword">if</span> [ -e  <span class="org-string">"$afile"</span> ]
<span class="linenr">23: </span>    <span class="org-keyword">then</span>
<span class="linenr">24: </span>        <span class="org-variable-name">MDATE</span>=$(<span class="org-sh-quoted-exec">date -r "$1" "+%Y%m%d%H%M"</span>)
<span class="linenr">25: </span>        touch -t $<span class="org-variable-name">MDATE</span> <span class="org-string">"$afile"</span>
<span class="linenr">26: </span>        touch -h -t $<span class="org-variable-name">MDATE</span> <span class="org-string">"$afile"</span>
<span class="linenr">27: </span>    <span class="org-keyword">fi</span>
<span class="linenr">28: </span>}
<span class="linenr">29: </span>
<span class="linenr">30: </span>
<span id="coderef-main" class="coderef-off"><span class="linenr">31: </span>find $<span class="org-variable-name">DOCS_OLD</span> | <span class="org-keyword">while </span><span class="org-builtin">read</span> file</span>
<span class="linenr">32: </span><span class="org-keyword">do</span>
<span class="linenr">33: </span>    set_meta <span class="org-string">"$file"</span>
<span class="linenr">34: </span>    set_mdate <span class="org-string">"$file"</span>
<span class="linenr">35: </span><span class="org-keyword">done</span>
</code></pre>
</div>

<p>
The script searches through the <code>=rclone</code>&rsquo;d path on line <a href="#coderef-main" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-main');" onmouseout="CodeHighlightOff(this, 'coderef-main');">31</a> and calls two
functions for each file found: <code>set_meta</code> (on line <a href="#coderef-setmeta" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-setmeta');" onmouseout="CodeHighlightOff(this, 'coderef-setmeta');">4</a>) and <code>set_mdate</code>
(on line <a href="#coderef-setmdate" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-setmdate');" onmouseout="CodeHighlightOff(this, 'coderef-setmdate');">20</a>) which set the metadata in the annex and adjust the <code>mtime</code>
of the file in the annex, respectively. This is provided that the file exists
under the same (sub) path in the annex, of course, as the <code>if</code> conditions in the
functions ensure. <code>set_mdate</code> will adjust the date for both the symlink itself
as well as the file linked to by running <code>touch</code> once with <code>-h</code> flag and once
without.
</p>

<p>
In case the files in your local annex still have the original modification time
but not the corresponding metadata set in the annex, you should be able to run
the above script setting the reference variable <code>DOCS_OLD</code> to your annex. Best
remove the call to <code>set_mdate</code> as well, as that would be unnecessary in this
case.
</p>

<p>
When all files finally have the necessary metadata entries, we can use the
following script to set the files&rsquo; <code>mtime</code> accordingly on freshly cloned
repositories:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr"> 1: </span><span class="org-variable-name">ANNEX</span>=<span class="org-string">"/home/hanno/annex/Documents"</span>
<span class="linenr"> 2: </span>
<span class="linenr"> 3: </span>find $<span class="org-variable-name">ANNEX</span> | <span class="org-keyword">while </span><span class="org-builtin">read</span> file
<span class="linenr"> 4: </span><span class="org-keyword">do</span>
<span class="linenr"> 5: </span>    <span class="org-keyword">if</span> [ <span class="org-negation-char">!</span> -d <span class="org-string">"$file"</span> ]
<span class="linenr"> 6: </span>       <span class="org-keyword">then</span>
<span class="linenr"> 7: </span>           <span class="org-variable-name">META</span>=<span class="org-string">"$(</span><span class="org-sh-quoted-exec">git annex metadata "$file"</span><span class="org-string">)"</span>
<span class="linenr"> 8: </span>           <span class="org-keyword">if </span><span class="org-builtin">echo</span> <span class="org-string">"$META"</span> | grep -q day
<span class="linenr"> 9: </span>              <span class="org-keyword">then</span>
<span class="linenr">10: </span>                  <span class="org-variable-name">DAY</span>=$(<span class="org-sh-quoted-exec">echo "$META"  | grep 'day=' | sed 's/.*=//'</span>)
<span class="linenr">11: </span>                  <span class="org-variable-name">MONTH</span>=$(<span class="org-sh-quoted-exec">echo "$META"  | grep 'month=' | sed 's/.*=//'</span>)
<span class="linenr">12: </span>                  <span class="org-variable-name">YEAR</span>=$(<span class="org-sh-quoted-exec">echo "$META"  | grep 'year=' | sed 's/.*=//'</span>)
<span id="coderef-time" class="coderef-off"><span class="linenr">13: </span>                  <span class="org-variable-name">MDATE</span>=<span class="org-string">"${YEAR}${MONTH}${DAY}1201"</span></span>
<span class="linenr">14: </span>                  <span class="org-builtin">echo</span> <span class="org-string">"Setting mtime $MDATE on $file"</span>
<span class="linenr">15: </span>                  touch -t $<span class="org-variable-name">MDATE</span> <span class="org-string">"$afile"</span>
<span class="linenr">16: </span>                  touch -h -t $<span class="org-variable-name">MDATE</span> <span class="org-string">"$afile"</span>
<span class="linenr">17: </span>           <span class="org-keyword">else</span>
<span class="linenr">18: </span>               <span class="org-builtin">echo</span> <span class="org-string">"$file has no metadata set."</span>
<span class="linenr">19: </span>               <span class="org-keyword">fi</span>
<span class="linenr">20: </span>        <span class="org-keyword">fi</span>
<span class="linenr">21: </span><span class="org-keyword">done</span>
</code></pre>
</div>

<p>
Note that the time is simply set to 12:01 on line <a href="#coderef-time" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-time');" onmouseout="CodeHighlightOff(this, 'coderef-time');">13</a> as this information is lacking in the metadata.
</p>

<p>
I plan on only running this once, as for files retrieved later the deviation
from the &ldquo;real&rdquo; modification time should be minor (and the correct one will be
stored in the annex&rsquo; metadata). But don&rsquo;t forget to run <code>git config
annex.genmetadata true</code> in all cloned repositories as the configuration option
is not synced between annexes!
</p>

<p>
With all this in place, I can finally browse my old data sets and see this:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>$ ls -lHh *
-r--r--r-- 1 hanno hanno  49K jun 20  1994 dorf.map
-r--r--r-- 1 hanno hanno  18K apr  3  1994 end.map
-r--r--r-- 1 hanno hanno  12K apr  6  1994 fire2.map
-r--r--r-- 1 hanno hanno  32K apr  6  1994 fire.map
-r--r--r-- 1 hanno hanno 4,1K dec  3  1993 forfirst.itm
-r--r--r-- 1 hanno hanno 2,7K nov 23  1994 forfirst.mon
-r--r--r-- 1 hanno hanno 2,4K dec  3  1993 forfirst.spl
-r--r--r-- 1 hanno hanno  22K apr  6  1994 forrest.map
-r--r--r-- 1 hanno hanno  14K apr  7  1994 garten.map
-r--r--r-- 1 hanno hanno  43K dec  1  1994 kanal.map
-r--r--r-- 1 hanno hanno  45K dec  3  1993 schloss.map
-r--r--r-- 1 hanno hanno  33K dec  3  1993 unbek.map
</code></pre>
</div>

<p>
Yes, that looks about right! 😸
</p>

<p>
In case you are wondering, these files belong to one of my first attempts at
making a video game using the <a href="https://en.wikipedia.org/wiki/The_Bard's_Tale_Construction_Set">Bard&rsquo;s Tale Construction Set</a>.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/git.html">git</a>, <a href="https://www.hoowl.se/../tags/gitannex.html">gitannex</a></p>
]]>
</description></item>
<item>
<title>org-capture-ref-jami-bot: Capturing bibliography entries while on the road</title>
<link>https://www.hoowl.se/org-capture-ref-jami-bot.html</link>
<pubDate>Sun, 16 Apr 2023 23:01:00 +0200</pubDate>
<guid>https://www.hoowl.se/org-capture-ref-jami-bot.html</guid>
<description>
<![CDATA[<p>
Let us get a little more fancy than <a href="https://hoowl.se/org-jami-bot.html">the simple captures of the previous post</a>:
capturing bibliographic references (or simply interesting links).
</p>

<p>
I often find myself getting excited about projects, blogs and (scientific)
articles I discover while surfing or scrolling through Mastodon on my mobile
phone. So I keep the tab open in my browser, which usually already shows &ldquo;∞&rdquo;
instead for the number of open tabs. But usually I want to come back to these
pages in a different context: when I am at my computer. Firefox Sync helps, but
requires additional steps or managing bookmarks in a tedious interface &ndash; I
really just want to put the link with some tag where it belongs: in my Org mode
notes!
</p>

<p>
So I use <a href="https://gitlab.com/hperrey/jami-bot"><code>jami-bot</code></a> and <a href="https://github.com/yantar92/org-capture-ref"><code>org-capture-ref</code></a> to send myself the link with a list of
tags via the Jami messenger. <code>org-capture-ref</code> collects meta data and stores it
to an inbox org file. If the link is to an article in a scientific journal or
similar citeable resource, the meta data will be quite extensive as long as the
publisher is supported by <code>org-capture-ref</code>. In either case, the captured entry
will contain a source block with BibTeX information which can be tangled into a
bib file and used e.g. with <code>citar</code> or <code>org-ref</code>. For an extensive example on
how <a href="https://github.com/yantar92/org-capture-ref#capture-setup=">to configure <code>org-capture-ref</code>, see the online documentation</a>. And see
<a href="https://hoowl.se/org-jami-bot.html">the previous post on <code>org-jami-bot</code></a> for a more detailed run-down of the
configuration of <code>jami-bot</code>.
</p>

<p>
To be able to trigger a capture via Jami, I define the following function:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--command-function-url</span> (account conversation msg)
  <span class="org-doc">"Capture a URL and tag it with any words immediately following the command.

Captures a single URL in MSG using `</span><span class="org-doc"><span class="org-constant">org-capture-ref-capture-url</span></span><span class="org-doc">'.
The entry will be tagged with any words on the first line of
the message immediately following the command string. Tags should
be separated by spaces."</span>
  (<span class="org-keyword">let*</span> ((body (cadr (assoc-string <span class="org-string">"body"</span> msg)))
         (lines (string-lines body))
         (tags (split-string (car lines)))
         (url (string-clean-whitespace (string-join (cdr lines)))))
    (<span class="org-keyword">let</span> ((org-capture-ref-headline-tags (append org-capture-ref-headline-tags tags)))
      (org-capture-ref-capture-url url))
           <span class="org-string">"captured url!"</span>))
</code></pre>
</div>

<p>
Now add this function to the list of commands known by <code>jami-bot</code> and map it to
trigger on messages starting with <code>!url</code>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(add-to-list 'jami-bot-command-function-alist '(<span class="org-string">"!url"</span> . jami-bot--command-function-url))
</code></pre>
</div>

<p>
Now any message such as &ldquo;!url <a href="https://hoowl.se">https://hoowl.se</a>&rdquo; will be captured immediately via <code>org-capture-ref</code>!
</p>

<p>
<del>Note that the Jami app on Android (as of today) has the strange behavior of deleting any pre-existing text in the message composing field if one pastes anything into it &ndash; so be sure to paste first, then add command and tags!</del> This seems to be fixed now!
</p>

<p>
Have fun 😺
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/pim.html">pim</a>, <a href="https://www.hoowl.se/../tags/org.html">org</a>, <a href="https://www.hoowl.se/../tags/jami.html">jami</a>, <a href="https://www.hoowl.se/../tags/programming.html">programming</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a></p>
]]>
</description></item>
<item>
<title>Collecting images into one directory for self-contained Org mode exports</title>
<link>https://www.hoowl.se/collect_images_on_export.html</link>
<pubDate>Sat, 17 Jun 2023 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/collect_images_on_export.html</guid>
<description>
<![CDATA[<p>
I often embed images into my Org mode documents. Especially attachments via
<code>org-download</code> are convenient while composing notes or drafts.
To share such documents, I usually export them. While <a href="https://www.gnu.org/software/emacs/manual/html_mono/org.html#Images-in-HTML-export">HTML export allows to inline
images</a>, this is not an option for e.g. LaTeX (if I want to share the <code>.tex</code>
file) or even Org mode.
</p>

<p>
To create a self-contained export including images, I have written an Elisp
routine that extracts the file path from a link, copies the target file into a
common directory and returns a modified link to the new location.
The directory can be specified as a relative path, e.g. <code>./images/</code>, which will
result in relative links in the exported document as well. The exported file can
then be easily compressed together with this directory into a zip file and send
off.
</p>

<p>
This is the code that defines the variable that configures the output path and
the filter function:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">hanno/org-copy-linked-files-export-path</span> <span class="org-string">"images/"</span>
  <span class="org-doc">"Sets the directory that linked local files are copied to on export.

The directory needs to already exist. Used by
`</span><span class="org-doc"><span class="org-constant">hanno/org-link-filter-copy-images-on-export</span></span><span class="org-doc">' as target
directory."</span>)

(<span class="org-keyword">defun</span> <span class="org-function-name">hanno/org-copy-linked-files-on-export</span> (text backend _info)
  <span class="org-doc">"Copy linked files in exports into a common location.

Returns modified TEXT with path to the copied file for a given
export BACKEND. Supported backends are HTML, LaTeX and Org mode.
Intended to be added to `</span><span class="org-doc"><span class="org-constant">org-export-filter-link-functions</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">let</span> (target source)
    (<span class="org-keyword">cond</span>
     ((org-export-derived-backend-p backend 'html)
      (<span class="org-keyword">let*</span> ((url
              (url-unhex-string
               (car (cl-remove-if
                     (<span class="org-keyword">lambda</span> (link) (not (string-match-p <span class="org-string">"^file:/"</span> link)))
                     (split-string-and-unquote text)))))
             (filename (file-name-nondirectory
                        (car (url-path-and-query
                              (url-generic-parse-url url))))))
        (<span class="org-keyword">unless</span> (string-empty-p filename)
          (<span class="org-keyword">setq</span> target (concat
                        (file-name-as-directory
                         hanno/org-copy-linked-files-export-path)
                        filename))
          (<span class="org-keyword">setq</span> source url)
          (<span class="org-keyword">with-demoted-errors</span> <span class="org-string">"Copy-files-link-filter error: %S"</span>
            (url-copy-file url target)))))
     ((<span class="org-keyword">or</span> (org-export-derived-backend-p backend 'latex)
          (org-export-derived-backend-p backend 'org))
      (<span class="org-keyword">let</span> ((match (<span class="org-keyword">if</span> (org-export-derived-backend-p backend 'latex)
                        <span class="org-string">"\\includegraphics.*?{</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.*?</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">}"</span>
                        <span class="org-string">"\\[\\[file:</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.*?</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">\\]"</span>)))
        (<span class="org-keyword">when</span> (string-match match text)
          (<span class="org-keyword">setq</span> source (match-string 1 text))
          (<span class="org-keyword">let*</span> ((filename (file-name-nondirectory source)))
            (<span class="org-keyword">setq</span> target (concat
                          (file-name-as-directory
                           hanno/org-copy-linked-files-export-path)
                          filename))
            (<span class="org-keyword">with-demoted-errors</span> <span class="org-string">"Copy-files-link-filter error: %S"</span>
              (copy-file source target)))))))
    (<span class="org-keyword">when</span> (<span class="org-keyword">and</span> source target (file-exists-p target))
      (string-replace source target text))))
</code></pre>
</div>

<p>
By adding this function to <code>org-export-filter-link-functions</code> it will be
triggered once for each link when exporting an Org file:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(add-to-list 'org-export-filter-link-functions
             'hanno/org-copy-linked-files-on-export)
</code></pre>
</div>

<p>
The filter will handle links such as
</p>
<pre class="example" id="orgdac6854">
[[file:/path/to/some/file]]
</pre>
<p>
and is technically not limited to images but would work with any linked file type.
</p>

<p>
Note that links with a description result in a <code>\href{...}</code> instead of an
<code>\includegraphics{...}</code> in the LaTeX backend and are ignored by the filter to
avoid affecting external links. Similarly, links without the <code>file:</code> prefix are
not considered in the HTML backend.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/org.html">org</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a></p>
]]>
</description></item>
<item>
<title>Auto-inserting .gitignore (and license) templates in Emacs</title>
<link>https://www.hoowl.se/auto_inserting_gitignore_templates_in_emacs.html</link>
<pubDate>Mon, 23 Nov 2020 00:00:00 +0100</pubDate>
<guid>https://www.hoowl.se/auto_inserting_gitignore_templates_in_emacs.html</guid>
<description>
<![CDATA[<p>
When you are using Emacs with the <a href="https://github.com/hlissner/doom-emacs">Doom-Emacs</a> configuration, you might have
noticed that you are being offered a couple of templates to select from whenever
you are visiting certain types of file for the first time: your brand new file
will be pre-populated from the template you pick. That is really quite
convenient, and I especially enjoy that for files such as the special
<code>.gitignore</code> file that tells <code>git</code> what files should not be considered relevant
for the repository. As I typically work with repositories holding different
types of code (and sometimes mixes thereof), this is really practical!
</p>

<p>
However, the templates that Doom ships with are quite limited. The LaTeX one
only covers a handful of patterns while the LaTeX compiler typically <b>litters</b>
the working directory with countless generated files. But manually copying over
files for each project is boring, so let&rsquo;s extend the mechanism!
</p>

<p>
<a href="https://github.com/hlissner/doom-emacs/tree/develop/modules/editor/file-templates">DOOM uses yasnippet</a> to populate those blank files whose name matches a certain pattern. The templates are hard-coded though and I would rather build upon excellent template collections such as <a href="https://github.com/github/gitignore">github&rsquo;s gitignores</a>. So time to create our own little function:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr">1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">hanno/template-insert-gitignore</span>()
<span class="linenr">2: </span>  (<span class="org-keyword">interactive</span>)
<span class="linenr">3: </span>  (<span class="org-keyword">let*</span> ((dir (concat doom-private-dir <span class="org-string">"/templates/gitignore/"</span>))
<span class="linenr">4: </span>         (files (directory-files dir nil <span class="org-string">".*\\.gitignore"</span>))
<span class="linenr">5: </span>         (pick (yas-choose-value (mapcar #'file-name-sans-extension files))))
<span class="linenr">6: </span>    (insert-file-contents (concat dir (concat pick <span class="org-string">".gitignore"</span>)))))
</code></pre>
</div>

<p>
This function lists all files in <code>[doom-private-dir]/templates/gitignore</code> (located under <code>~/.doom.d</code> for me) and offers an interactive selection prompt via <code>yas-choose-value</code>. There, I can place all the templates I want:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr">1: </span><span class="org-builtin">cd</span> ~/.doom.d
<span class="linenr">2: </span>mkdir templates
<span class="linenr">3: </span><span class="org-builtin">cd</span> templates
<span class="linenr">4: </span>git clone https://github.com/github/gitignore
</code></pre>
</div>

<p>
Just make sure they have the file extension &ldquo;.gitignore&rdquo; as we filter on that in the above elisp code. Now we can register the template through Doom&rsquo;s <code>set-file-template!</code> macro:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr">1: </span>(set-file-template! <span class="org-string">"\\.gitignore$"</span> <span class="org-builtin">:trigger</span> 'hanno/template-insert-gitignore <span class="org-builtin">:mode</span> 'gitignore-mode)
</code></pre>
</div>

<p>
And here is the code in action when visiting a new file:
<img src="https://www.hoowl.se/images/posts/emacs/2020-11-23-215728_774x726_scrot.png" alt="2020-11-23-215728_774x726_scrot.png" />
</p>

<p>
This can be easily extended to other template types, for example the various (FOSS) licences hosted at <a href="https://github.com/github/choosealicense.com">choosealicense.com</a>:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr">1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">hanno/template-insert-license</span>()
<span class="linenr">2: </span>  (<span class="org-keyword">interactive</span>)
<span class="linenr">3: </span>  (<span class="org-keyword">let*</span> ((dir (concat doom-private-dir <span class="org-string">"/templates/choosealicense.com/_licenses/"</span>))
<span class="linenr">4: </span>         (files (directory-files dir nil <span class="org-string">".*\\.txt"</span>))
<span class="linenr">5: </span>         (pick (yas-choose-value (mapcar #'file-name-sans-extension files))))
<span class="linenr">6: </span>    (insert-file-contents (concat dir (concat pick <span class="org-string">".txt"</span>)))))
<span class="linenr">7: </span>
<span class="linenr">8: </span>(set-file-template! <span class="org-string">"license</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">$</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">\\.txt</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">\\.md</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">"</span> <span class="org-builtin">:trigger</span> 'hanno/template-insert-license)
</code></pre>
</div>

<p>
The regular expression of the template matches for example <code>license</code>, <code>LICENSE</code>, <code>license.txt</code> and <code>license.md</code>.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a>, <a href="https://www.hoowl.se/../tags/git.html">git</a></p>
]]>
</description></item>
<item>
<title>Shrinking an SD card image using Linux shell commands</title>
<link>https://www.hoowl.se/shrinking_an_sd_card_image.html</link>
<pubDate>Sat, 09 Oct 2021 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/shrinking_an_sd_card_image.html</guid>
<description>
<![CDATA[<p>
I really enjoy messing with <a href="https://www.raspberrypi.org/">Raspberry Pi</a> computing systems and have several running inside my home, either feeding my stereo system with music or <a href="https://www.hoowl.se/the_day_I_started_worrying_and_deleted_google.html">keeping my personal data in sync</a>. To make backups or creating clones of existing systems, I can always stick the SD card running the operating system of the Raspberry Pi into my laptop and copy the entire content onto my hard disk. On those occasions it is sometimes nice to be able to shrink the resulting image down to the amount of actual data contained within (instead of the size of the SD card). I prefer to use command-line tools as they are easier to document and control.
</p>

<p>
This post is part of the <i>notes-to-future-self</i> series: I will probably want to do this again, so better write down how I worked things out the first time! ;)
</p>

<p>
A great help was <a href="http://www.aoakley.com/articles/2015-10-09-resizing-sd-images.php">this article</a> on which much of this post is based upon. I managed to simplify a couple of things, however, sticking with only command-line tools.
</p>

<p>
First, I made a copy of the SD card using <code>dd</code>. Now I want to shrink part of the image containing the root file system. I can list the partitions contained in the image using <code>fdisk</code>:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>fdisk -l /home/hanno/rasppi_20210429_SHRUNK.img
</code></pre>
</div>

<pre class="example" id="org7c5219e">
Disk /home/hanno/rasppi_20210429_SHRUNK.img: 29,81 GiB, 31992053760 bytes, 62484480 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xf16f32c5

Device                                    Boot Start      End  Sectors  Size Id Type
/home/hanno/rasppi_20210429_SHRUNK.img1       8192    98045    89854 43,9M  c W95 FAT32 (LBA)
/home/hanno/rasppi_20210429_SHRUNK.img2      98304 62484479 62386176 29,8G 83 Linux
</pre>

<p>
Using the <code>--partscan</code> option of <code>losetup</code>, we can access these partitions directly via a loopback device and thus making them individually visible to the operating system:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>losetup --show -f --partscan /home/hanno/rasppi_20210429_SHRUNK.img
</code></pre>
</div>

<pre class="example">
/dev/loop22
</pre>

<div class="org-src-container">
<pre class="src src-bash"><code>ls /dev/loop22*
</code></pre>
</div>

<pre class="example">
/dev/loop22  /dev/loop22p1  /dev/loop22p2
</pre>

<p>
On these partitions reside the filesystems that contain the actual data. Find out what the minimum size of the image would be (i.e. the actual data contained within):
</p>
<div class="org-src-container">
<pre class="src src-sh"><code>sudo resize2fs -P /dev/loop22p2
</code></pre>
</div>

<pre class="example">
resize2fs 1.45.5 (07-Jan-2020)
Estimated minimum size of the filesystem: 615955
</pre>

<p>
This is in file-system block size which are 4 kB large. Add a little and use this number to shrink the filesystem down to this size:
</p>

<div class="org-src-container">
<pre class="src src-sh"><code>sudo e2fsck -p -f /dev/loop22p2 &amp;&amp; sudo resize2fs /dev/loop22p2 750000
</code></pre>
</div>

<pre class="example">
rootfs: 61061/1884960 files (0.1% non-contiguous), 544080/7798272 blocks
resize2fs 1.45.5 (07-Jan-2020)
Resizing the filesystem on /dev/loop22p2 to 750000 (4k) blocks.
The filesystem on /dev/loop22p2 is now 750000 (4k) blocks long.

</pre>

<p>
Now we can be sure that all the data is contained within those limits. Time to
reduce the partition boundaries to these values.
</p>

<p>
We can use <code>fdisk</code> on the main
loopback device to delete the 2nd partition and recreate it with the same start
and the new end (i.e. 750000*4 kB):
</p>
<div class="org-src-container">
<pre class="src src-sh"><code><span class="org-builtin">echo</span> +$(<span class="org-sh-quoted-exec">(750000*4</span>))K
</code></pre>
</div>

<pre class="example">
+3000000K
</pre>

<p>
The plus in the beginning is important as it indicates a relative number and not an absolute number.
</p>

<p>
Run <code>fdisk</code>:
</p>
<div class="org-src-container">
<pre class="src src-sh"><code>fdisk /dev/loop22
</code></pre>
</div>

<p>
Press <code>d</code>, select the 2nd partition, confirm, press <code>n</code> and follow the prompts and enter the values above.
</p>

<p>
Then, remove the loopback devices again:
</p>
<div class="org-src-container">
<pre class="src src-sh"><code>sudo losetup -d /dev/loop22*
</code></pre>
</div>

<pre class="example">
losetup: /dev/loop22p1: failed to use device: No such device
losetup: /dev/loop22: detach failed: No such device or address
losetup: /dev/loop22p2: failed to use device: No such device
losetup: /dev/loop22: detach failed: No such device or address
</pre>

<p>
Looks like they were removed already. Let&rsquo;s look at the new partition table:
</p>

<div class="org-src-container">
<pre class="src src-sh"><code>fdisk -l /home/hanno/rasppi_20210429_SHRUNK.img
</code></pre>
</div>

<pre class="example" id="orgca312e5">
Disk /home/hanno/rasppi_20210429_SHRUNK.img: 29,81 GiB, 31992053760 bytes, 62484480 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xf16f32c5

Device                                    Boot Start     End Sectors  Size Id Type
/home/hanno/rasppi_20210429_SHRUNK.img1       8192   98045   89854 43,9M  c W95 FAT32 (LBA)
/home/hanno/rasppi_20210429_SHRUNK.img2      98304 6098943 6000640  2,9G 83 Linux
</pre>

<p>
It worked so far! The total image is still 30 GB large but the data partition is only about 3 GB. Now we can remove everything from the image until the end sector of the 2nd partition as provided in the <code>fdisk</code> output above:
</p>
<div class="org-src-container">
<pre class="src src-sh"><code><span class="org-variable-name">END</span>=6098943
truncate -s $(<span class="org-sh-quoted-exec">((END+1</span>)*512)) /home/hanno/rasppi_20210429_SHRUNK.img
</code></pre>
</div>

<div class="org-src-container">
<pre class="src src-sh"><code>ls -lh /home/hanno/rasppi_20210429_SHRUNK.img
</code></pre>
</div>

<pre class="example">
-rw-r--r-- 1 hanno hanno 3,0G jul  8 23:33 /home/hanno/rasppi_20210429_SHRUNK.img
</pre>

<p>
And that is it, the file is a mere 3 GB in size now! :)
</p>

<p>
Of course, from within the Raspian OS, you can easily stretch the system again to fill any size SD card from within the configuration tool <code>raspi-config</code>.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/raspberrypi.html">raspberrypi</a>, <a href="https://www.hoowl.se/../tags/linux.html">linux</a></p>
]]>
</description></item>
<item>
<title>An extendable GNU Jami chat bot written in Elisp</title>
<link>https://www.hoowl.se/jami-bot.html</link>
<pubDate>Sun, 16 Apr 2023 22:58:00 +0200</pubDate>
<guid>https://www.hoowl.se/jami-bot.html</guid>
<description>
<![CDATA[<p>
:header-args:emacs-lisp: :tangle &ldquo;~/src/jami-bot/jami-bot.el&rdquo;
</p>
<p>
<a href="https://docs.jami.net/user/faq.html#what-makes-jami-different-from-other-communication-platforms">Jami is a distributed and private messenger</a> that is part of the GNU project and
<a href="https://savoirfairelinux.com">mainly developed by Savoir-faire Linux</a>. <i>Private</i> in this context does not only
refer to the fact that messages, calls and video chats are encrypted, but
that <a href="https://docs.jami.net/user/faq.html#what-information-do-i-need-to-provide-to-create-a-jami-account">you need to provide essentially no personal information to create a Jami
account</a>.
</p>

<p>
The technology behind Jami is quite interesting: since the recent introduction
of <a href="https://docs.jami.net/developer/swarm.html">the so-called Swarm conversations</a>, it uses <code>git</code> behind the scenes to store,
sync and merge conversations between peers. This means that you can have
encrypted group chats without requiring any server to exchange them through &ndash;
which even <a href="https://docs.jami.net/user/lan-only.html">works between devices on the same local network while the internet is
down</a>.
</p>

<p>
Jami is easy to deploy on most OS and is available for mobile devices as well. As
we have seen in the previous post where I explored Jami&rsquo;s D-Bus interface, most
of the applications functions can be easily accessed from other local
applications. That makes Jami an interesting candidate for a <i>chat bot</i> written
in Elisp: a function that subscribes to Jami&rsquo;s <code>messageReceived</code> signal, parses
the message and reacts to it.
</p>

<p>
While generating silly replies (Emacs&rsquo; <code>doctor</code> anyone?) would be fun for a
while, I am envisioning more of a simple command parser that allows to access
and/or record specific information through Emacs. For me, that would be taking
quick notes on my mobile to record those fleeting thoughts and ideas while
sitting on the bus on my way home. Or, to construct longer notes in several
steps: like when I am on a tour of a lab or other place of interest and want to
combine a series of pictures with short notes to keep track of things I want to
remember. Of course I want to have those notes in Org mode at the end of the
day, so why not send and capture them immediately?
</p>

<p>
But this is getting ahead of what this post is about: creating a bare-bones chat
bot in Elisp that can be extended to perform above mentioned tasks. The
extension to Org mode functionality is what <a href="https://hoowl.se/org-jami-bot.html">the next post in this series</a> is
about. On that occasion, I will discuss the pro and contra of this approach in a
little more detail. If you are not so much interested in the technical details
of the implementation then feel free to skip ahead! If, on the other hand, you
are curious how to make the chat bot follow your own commands, then just
continue reading.
</p>

<p>
<b>Note</b>: the code discussed below is what I originally wrote &ndash; it probably has
evolved since and the newest version will be hosted at this repository:
<a href="https://gitlab.com/hperrey/jami-bot">https://gitlab.com/hperrey/jami-bot</a>
</p>
<section id="outline-container-org65b3696" class="outline-2">
<h2 id="org65b3696">Defining a message handler</h2>
<div class="outline-text-2" id="text-org65b3696">
<p>
The entry point to our chat bot will be a function that we register on the
<code>messageReceived</code> signal. Each message by Jami for any account and any
conversation will be passed on to this function as well as the account it was
received via and the conversation that the message is part of.
</p>

<p>
To keep things modular, this function needs only to serve two purposes:
</p>
<ol class="org-ol">
<li>Filter out any unwanted messaging activity. Most importantly, this includes
messages that were sent by the chat bot account(s) to avoid ending in an
infinite loop reacting to our own messages. But the user might want to limit
the chat bot to certain local accounts.</li>
<li>Distinguish the type of message (text, file transfer, internal) and pass it
on to the appropriate function for further processing or ignore it.</li>
</ol>


<p>
Below is the definition for the function &ndash; we will walk through the various branches step-by-step.
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>
<span class="linenr"> 2: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--messageReceived-handler</span> (account conversation msg)
<span class="linenr"> 3: </span>  <span class="org-doc">"Handle messages from Jami's `</span><span class="org-doc"><span class="org-constant">messageReceived</span></span><span class="org-doc">' D-Bus signal.
<span class="linenr"> 4: </span>
<span class="linenr"> 5: </span>  ACCOUNT and CONVERSATION are the corresponding ids to which the
<span class="linenr"> 6: </span>  MSG belongs to. The latter contains additional fields such as
<span class="linenr"> 7: </span>  `</span><span class="org-doc"><span class="org-constant">author</span></span><span class="org-doc">' and `</span><span class="org-doc"><span class="org-constant">body</span></span><span class="org-doc">'. The field `</span><span class="org-doc"><span class="org-constant">type</span></span><span class="org-doc">' is used to identify which
<span class="linenr"> 8: </span>  function to call for further processing."</span>
<span class="linenr"> 9: </span>  <span class="org-comment-delimiter">;; </span><span class="org-comment">make sure we are not reacting to messages sent from our own local
<span class="linenr">10: </span></span>  <span class="org-comment-delimiter">;; </span><span class="org-comment">account(s) or accounts we are not to monitor
<span class="linenr">11: </span></span>  (<span class="org-keyword">unless</span> jami-bot--jami-local-account-ids
<span class="linenr">12: </span>    (jami-bot--refresh-accountid-list))
<span class="linenr">13: </span>  (<span class="org-keyword">let</span> ((author (cadr (assoc <span class="org-string">"author"</span> msg)))
<span class="linenr">14: </span>        (type (cadr (assoc <span class="org-string">"type"</span> msg))))
<span class="linenr">15: </span>    (<span class="org-keyword">when</span> (<span class="org-keyword">or</span>
<span class="linenr">16: </span>           (<span class="org-keyword">and</span> jami-bot-account-user-names
<span class="linenr">17: </span>                 <span class="org-comment-delimiter">;; </span><span class="org-comment">account id should match a user name to be monitored
<span class="linenr">18: </span></span>                 (member (car (rassoc account jami-bot--jami-local-account-ids))
<span class="linenr">19: </span>                         jami-bot-account-user-names)
<span class="linenr">20: </span>                 <span class="org-comment-delimiter">;; </span><span class="org-comment">.. but msg should not be authored by ourselves
<span class="linenr">21: </span></span>                 (not (member author jami-bot-account-user-names)))
<span class="linenr">22: </span>            <span class="org-comment-delimiter">;; </span><span class="org-comment">no account filter: check msg not from local account
<span class="linenr">23: </span></span>           (<span class="org-keyword">and</span> (not jami-bot-account-user-names)
<span class="linenr">24: </span>                (not (assoc author jami-bot--jami-local-account-ids))))
<span class="linenr">25: </span>      (message <span class="org-string">"jami-bot received %s message from %s on account %s."</span> type author account)
<span id="coderef-pcase" class="coderef-off"><span class="linenr">26: </span>      (<span class="org-keyword">pcase</span> type</span>
<span class="linenr">27: </span>        (<span class="org-string">"text/plain"</span>
<span id="coderef-text" class="coderef-off"><span class="linenr">28: </span>         (jami-bot--process-text-message</span>
<span class="linenr">29: </span>          account
<span class="linenr">30: </span>          conversation
<span class="linenr">31: </span>          msg))
<span class="linenr">32: </span>        (<span class="org-string">"application/data-transfer+json"</span>
<span class="linenr">33: </span>         (jami-bot--process-data-transfer
<span class="linenr">34: </span>          account
<span class="linenr">35: </span>          conversation
<span class="linenr">36: </span>          msg))
<span class="linenr">37: </span>        <span class="org-comment-delimiter">;; </span><span class="org-comment">ignore merges of the conversation; usually transparent to the user
<span class="linenr">38: </span></span>        <span class="org-comment-delimiter">;; </span><span class="org-comment">anyway
<span class="linenr">39: </span></span>        (<span class="org-string">"merge"</span> (ignore))
<span class="linenr">40: </span>        <span class="org-comment-delimiter">;; </span><span class="org-comment">ignore new members joining
<span class="linenr">41: </span></span>        (<span class="org-string">"member"</span> (ignore))
<span class="linenr">42: </span>        (_
<span class="linenr">43: </span>         (jami-bot--process-unknown-type
<span class="linenr">44: </span>          account
<span class="linenr">45: </span>          conversation
<span class="linenr">46: </span>          msg))))))
<span class="linenr">47: </span>
<span id="coderef-unknown" class="coderef-off"><span class="linenr">48: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--process-unknown-type</span> (account conversation msg)</span>
<span class="linenr">49: </span><span class="org-doc">"Handle messages of unknown type by sending an error message as reply.
<span class="linenr">50: </span>
<span class="linenr">51: </span>  ACCOUNT and CONVERSATION are the corresponding ids to which the
<span class="linenr">52: </span>  MSG belongs to."</span>
<span class="linenr">53: </span>  (<span class="org-keyword">let</span> ((type (cadr (assoc <span class="org-string">"type"</span> msg))))
<span class="linenr">54: </span>    (message <span class="org-string">"Error: received message with unkonwn type: %s"</span> type)
<span class="linenr">55: </span>    (jami-bot-reply-to-message
<span class="linenr">56: </span>     account
<span class="linenr">57: </span>     conversation
<span class="linenr">58: </span>     msg
<span class="linenr">59: </span>     (format <span class="org-string">"Unknown message type: %s"</span> type))))
</code></pre>
</div>

<p>
The first part of <code>jami-bot--messageReceived-handler</code> handles the filtering. Any
messages from local accounts are ignored to avoid feedback loops created when
responding to our own replies. In case several local Jami accounts are present,
one can limit <code>jami-bot</code> to only react to messages to specific accounts:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp" id="orgf833252"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">jami-bot-account-user-names</span> nil
  <span class="org-doc">"List of account user names that `</span><span class="org-doc"><span class="org-constant">jami-bot</span></span><span class="org-doc">' handles messages for.

        If set to nil then `</span><span class="org-doc"><span class="org-constant">jami-bot</span></span><span class="org-doc">' 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 has such as
        \"badac18e13ec1a6e1266600e457859afebfb9c46\"."</span>)
</code></pre>
</div>

<p>
This has to be the full user name (I think sometimes referred to as <i>address</i> in
Jami), that is to say the full hash, for example:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">setq</span> jami-bot-account-user-names '(<span class="org-string">"badac18e13ec1a6e1266600e457859afebfb9c46"</span>))
</code></pre>
</div>

<p>
To make it easier to enter these, here is a little interactive helper function
that allows to pick a user from the list of local accounts:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot-select-and-insert-local-account</span> ()
  <span class="org-doc">"Prompt user for a local Jami user account and insert id at position."</span>
  (<span class="org-keyword">interactive</span>)
  (jami-bot--refresh-accountid-list)
  (insert (completing-read <span class="org-string">"Pick a account user name to insert: "</span> jami-bot--jami-local-account-ids)))
</code></pre>
</div>


<p>
From line <a href="#coderef-pcase" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-pcase');" onmouseout="CodeHighlightOff(this, 'coderef-pcase');">25</a> onward, the different message types are distinguished.
While <i>text</i> and <i>file transfer</i> are handled in separate functions, the internal
messages <i>merge</i> (when the conversation is synced after it diverged due to a
loss of connectivity) and <i>member</i> (when someone is added to a conversation) are
ignored. Anything else will be sent to <code>jami-bot--process-unknown-type</code> defined
on line <a href="#coderef-unknown" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-unknown');" onmouseout="CodeHighlightOff(this, 'coderef-unknown');">47</a>. In that case, a warning will be added to the
conversation, mentioning the unknown type.
</p>


<p>
The actual registration of the handler on the bus for the <code>messageReceived</code>
signal is done by a helper function:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot-register</span> ()
<span class="linenr"> 2: </span>  <span class="org-doc">"Ping the Jami daemon and register `</span><span class="org-doc"><span class="org-constant">jami-bot</span></span><span class="org-doc">' handler for receiving messages."</span>
<span class="linenr"> 3: </span>  (<span class="org-keyword">interactive</span>)
<span id="coderef-ping" class="coderef-off"><span class="linenr"> 4: </span>  (<span class="org-keyword">or</span> (dbus-ping <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span>)</span>
<span class="linenr"> 5: </span>      (<span class="org-warning">error</span> <span class="org-string">"Jami Daemon (jamid) not available through dbus. Please check Jami installation"</span>))
<span class="linenr"> 6: </span>  (dbus-register-signal <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span>
<span class="linenr"> 7: </span>                      <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
<span class="linenr"> 8: </span>                      <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
<span class="linenr"> 9: </span>                      <span class="org-string">"messageReceived"</span>
<span class="linenr">10: </span>                      #'jami-bot--messageReceived-handler))
</code></pre>
</div>

<p>
Pinging the Jami D-Bus service on line <a href="#coderef-ping" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-ping');" onmouseout="CodeHighlightOff(this, 'coderef-ping');">4</a> verifies not only that Jami is
installed correctly and registered on D-Bus but also has the side effect of
starting the daemon if it is not already running. Therefore, this helper
function is useful should you ever need to restart the Jami daemon: simply run
<code>killall jamid</code> on the terminal and call <code>M-x jami-bot-register</code> in Emacs to
start it up again.
</p>

<p>
Now that our chat bot can receive messages, let us start to react to them. We
start with text messages.
</p>
</div>
</section>
<section id="outline-container-orga2dddff" class="outline-2">
<h2 id="orga2dddff">A simple command parser</h2>
<div class="outline-text-2" id="text-orga2dddff">
<p>
To make the conversations with the bot a little more interesting, I want to be
able to issue simple commands via text messages. Any message starting with an
exclamation mark and a single word will be interpreted as a command and
forwarded to a specific function. An example would be the <code>!help</code> command which
will respond with the list of available commands.
</p>

<p>
To make this easily extensible, I define an <code>alist</code> that maps commands with the function that processes them:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org150747c"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">jami-bot-command-function-alist</span>
  '((<span class="org-string">"!ping"</span> . jami-bot--command-function-ping)
    (<span class="org-string">"!help"</span> . jami-bot--command-function-help))
  <span class="org-doc">"Alist mapping command strings in message body to functions to be executed.

Each command needs to start with an exclamation mark '</span><span class="org-doc"><span class="org-constant">!</span></span><span class="org-doc">' 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)."</span>)
</code></pre>
</div>

<p>
Of course, some actions should also be taken if there is no command given at
all. For those cases, I define
<a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Hooks.html">an
<i>abnormal</i> hook</a> that the user can set arbitrary functions to process. Abnormal
means that the functions will be called with arguments, as they will need the
account, conversation and actual message to process.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp" id="orgd563893"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">jami-bot-text-message-functions</span> nil
  <span class="org-doc">"A list of functions that will be called when processing a plain text message.

Functions must take the ACCOUNT and CONVERSATION ids as well as
the actual MSG as arguments. Their return value will be ignored."</span>)
</code></pre>
</div>

<p>
Every text message is forwarded from <code>jami-bot--messageReceived-handler</code> on line
<a href="#coderef-text" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-text');" onmouseout="CodeHighlightOff(this, 'coderef-text');">27</a> processed by <code>jami-bot--process-text-message</code> below.
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--process-text-message</span> (account conversation msg)
<span class="linenr"> 2: </span>  <span class="org-doc">"Process plain text messages and parse the message body for commands.
<span class="linenr"> 3: </span>
<span class="linenr"> 4: </span>  ACCOUNT and CONVERSATION are the corresponding ids to which the
<span class="linenr"> 5: </span>  message MSG belongs to. Messages containing commands must start
<span class="linenr"> 6: </span>  with an exclamation mark (\"!\") followed by the single-word
<span class="linenr"> 7: </span>  command. Each command is mapped to a function via
<span class="linenr"> 8: </span>  `</span><span class="org-doc"><span class="org-constant">jami-bot-command-function-alist</span></span><span class="org-doc">' which will be executed when
<span class="linenr"> 9: </span>  the command is received.
<span class="linenr">10: </span>
<span class="linenr">11: </span>  If the message does not start with an exclamation mark, the
<span class="linenr">12: </span>  abnormal hook `</span><span class="org-doc"><span class="org-constant">jami-bot-text-message-functions</span></span><span class="org-doc">' will be run for
<span class="linenr">13: </span>  further processing."</span>
<span class="linenr">14: </span>  (<span class="org-keyword">let</span> ((body (cadr (assoc-string <span class="org-string">"body"</span> msg))))
<span class="linenr">15: </span>    <span class="org-comment-delimiter">;; </span><span class="org-comment">check for criteria handling first line of body as command
<span class="linenr">16: </span></span>    <span class="org-comment-delimiter">;;  </span><span class="org-comment">- string starts with '</span><span class="org-comment"><span class="org-constant">!</span></span><span class="org-comment">' and is a single word
<span class="linenr">17: </span></span>    (<span class="org-keyword">if</span> (string-prefix-p <span class="org-string">"!"</span> body)
<span class="linenr">18: </span>        <span class="org-comment-delimiter">;; </span><span class="org-comment">command in msg body
<span class="linenr">19: </span></span>        (<span class="org-keyword">let*</span>
<span class="linenr">20: </span>            ((cmd (downcase (substring body 0 (string-match-p <span class="org-string">"[</span><span class="org-string"><span class="org-negation-char">^</span></span><span class="org-string">[:word:]!]"</span> body))))
<span class="linenr">21: </span>             (fcn (cdr (assoc-string cmd jami-bot-command-function-alist))))
<span class="linenr">22: </span>          <span class="org-comment-delimiter">;; </span><span class="org-comment">remove the command from the message body
<span class="linenr">23: </span></span>          (setcdr (assoc-string <span class="org-string">"body"</span> msg)
<span class="linenr">24: </span>                  (list (string-trim-left (string-remove-prefix cmd body))))
<span id="coderef-fcn" class="coderef-off"><span class="linenr">25: </span>          (<span class="org-keyword">if</span> fcn</span>
<span class="linenr">26: </span>            <span class="org-comment-delimiter">;; </span><span class="org-comment">call function and reply with return value
<span class="linenr">27: </span></span>            (jami-bot-reply-to-message
<span class="linenr">28: </span>             account
<span class="linenr">29: </span>             conversation
<span class="linenr">30: </span>             msg
<span class="linenr">31: </span>             (funcall fcn account conversation msg))
<span class="linenr">32: </span>            <span class="org-comment-delimiter">;; </span><span class="org-comment">no matching command defined:
<span class="linenr">33: </span></span>            <span class="org-comment-delimiter">;; </span><span class="org-comment">report error as reply to msg
<span class="linenr">34: </span></span>            (jami-bot-reply-to-message
<span class="linenr">35: </span>             account
<span class="linenr">36: </span>             conversation
<span class="linenr">37: </span>             msg
<span class="linenr">38: </span>             (format <span class="org-string">"Unknown command: %s"</span> cmd))))
<span class="linenr">39: </span>      <span class="org-comment-delimiter">;; </span><span class="org-comment">not a command in msg body: run hook instead
<span id="coderef-texthook" class="coderef-off"><span class="linenr">40: </span></span>      (run-hook-with-args 'jami-bot-text-message-functions</span>
<span class="linenr">41: </span>                          account conversation msg))))
</code></pre>
</div>

<p>
If a command prefix was found in the message and a matching handler defined,
then the function will be called on line <a href="#coderef-fcn" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-fcn');" onmouseout="CodeHighlightOff(this, 'coderef-fcn');">25</a> and the functions return
value be sent as a reply. Otherwise, a reply will be sent with &ldquo;unknown
command&rdquo;. In case there was no command in the message, we might still want to do
something with it and provide a hook on line <a href="#coderef-texthook" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-texthook');" onmouseout="CodeHighlightOff(this, 'coderef-texthook');">40</a>.
</p>

<p>
Now we can define a couple of functions that react to commands.
</p>

<p>
The simplest command processor is the <i>ping</i> command that replies to a message
with <i>pong!</i> (and any part of the original message after the command keyword):
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--command-function-ping</span> (_account _conversation msg)
  <span class="org-doc">"Return the string '</span><span class="org-doc"><span class="org-constant">pong!</span></span><span class="org-doc">' followed by the message's body.

Example for a basic jami bot command handling function. Acts on MSG
received via _ACCOUNT in _CONVERSATION. The latter two are unused."</span>
  (<span class="org-keyword">let</span> ((body (cadr (assoc-string <span class="org-string">"body"</span> msg))))
    (format <span class="org-string">"pong! %s"</span> body)))
</code></pre>
</div>

<p>
A little more useful is the help command that lists all available commands and
their docstring:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--command-function-help</span> (_account _conversation _msg)
  <span class="org-doc">"Return a summary of available commands.

Acts on _MSG received via _ACCOUNT in _CONVERSATION, none of
which are used."</span>
  (<span class="org-keyword">let</span> (result)
    (<span class="org-keyword">dolist</span> (cmd jami-bot-command-function-alist (string-join result <span class="org-string">"\n"</span>))
      (<span class="org-keyword">push</span> (concat <span class="org-string">"- "</span> (car cmd) <span class="org-string">" :: "</span>
                    (car (split-string (documentation (cdr cmd)) <span class="org-string">"\n"</span>))) <span class="org-warning">result))))</span>
</code></pre>
</div>
</div>
</section>
<section id="outline-container-org969ed16" class="outline-2">
<h2 id="org969ed16">Handling data transfers (i.e. attached files)</h2>
<div class="outline-text-2" id="text-org969ed16">
<p>
If one sends a file via Jami, e.g. an image, the message contains the file name
and a file id but no actual data. This has to be retrieved by downloading it
before we can continue processing it. So for this simple handler, we define a
variable path to store files in (<code>jami-bot-download-path</code>) and a function
<code>jami-bot--process-data-transfer</code> to download the file and run a hook for
additional handling.
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org3829fbc"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">jami-bot-download-path</span> <span class="org-string">"~/jami/"</span>
<span class="org-doc">"Path in which to store files downloaded from conversations.

Will be created if not existing yet."</span>)
</code></pre>
</div>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--process-data-transfer</span> (account conversation msg)
<span class="linenr"> 2: </span>  <span class="org-doc">"Process data transfer from received messages.
<span class="linenr"> 3: </span>
<span class="linenr"> 4: </span>  Downloads files to the path given by `</span><span class="org-doc"><span class="org-constant">jami-bot-download-path</span></span><span class="org-doc">'
<span class="linenr"> 5: </span>  and calls the abnormal hook `</span><span class="org-doc"><span class="org-constant">jami-bot-data-transfer-functions</span></span><span class="org-doc">'
<span class="linenr"> 6: </span>  for further processing. ACCOUNT and CONVERSATION are the
<span class="linenr"> 7: </span>  corresponding ids to which the message MSG belongs to."</span>
<span class="linenr"> 8: </span>  (<span class="org-keyword">let*</span> ((id (cadr (assoc-string <span class="org-string">"id"</span> msg)))
<span class="linenr"> 9: </span>         (fileid (cadr (assoc-string <span class="org-string">"fileId"</span> msg)))
<span class="linenr">10: </span>         (filename (cadr (assoc-string <span class="org-string">"displayName"</span> msg)))
<span class="linenr">11: </span>         (dlpath (file-name-as-directory
<span class="linenr">12: </span>                  (expand-file-name jami-bot-download-path)))
<span class="linenr">13: </span>         (dlname (concat dlpath (format-time-string <span class="org-string">"%Y%m%d-%H%M"</span>) <span class="org-string">"_"</span> filename)))
<span class="linenr">14: </span>    (<span class="org-keyword">unless</span> (file-directory-p dlpath) (make-directory dlpath 't))
<span class="linenr">15: </span>    (message <span class="org-string">"jami-bot: downloading file %s"</span> dlname)
<span class="linenr">16: </span>    (jami-bot--dbus-cfgmgr-call-method <span class="org-string">"downloadFile"</span> account conversation id fileid dlname)
<span id="coderef-datahook" class="coderef-off"><span class="linenr">17: </span>    (run-hook-with-args 'jami-bot-data-transfer-functions account conversation msg dlname)))</span>
</code></pre>
</div>

<p>
This so far takes care of transferring the file to local storage. If desired,
the user can process the data further by adding a hook which is run on line
<a href="#coderef-datahook" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-datahook');" onmouseout="CodeHighlightOff(this, 'coderef-datahook');">17</a>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org3e8472b"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">jami-bot-data-transfer-functions</span> nil
  <span class="org-doc">"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 and the local downloaded file name, DLNAME, as
arguments. Their return value will be ignored."</span>)
</code></pre>
</div>
</div>
</section>
<section id="outline-container-org6c5583f" class="outline-2">
<h2 id="org6c5583f">Support functions for D-Bus and account bookkeeping</h2>
<div class="outline-text-2" id="text-org6c5583f">
<p>
The final puzzle pieces still missing are the support functions that help with
the D-Bus interaction and with keeping track of local Jami accounts.
</p>


<p>
As all D-Bus calls necessary for the chat bot are part of Jami&rsquo;s
<code>ConfigurationManager</code> interface, and mostly concerned with sending messages, we
can factorize the constant bits of these calls into three helper functions:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--dbus-cfgmgr-call-method</span> (method <span class="org-type">&amp;rest</span> args)
<span class="linenr"> 2: </span>  <span class="org-doc">"Call Jami ConfigurationManager dbus METHOD with arguments ARGS."</span>
<span class="linenr"> 3: </span>  (apply #'dbus-call-method `(<span class="org-builtin">:session</span>
<span class="linenr"> 4: </span>                    <span class="org-string">"cx.ring.Ring"</span>
<span class="linenr"> 5: </span>                    <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
<span class="linenr"> 6: </span>                    <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
<span id="coderef-marker" class="coderef-off"><span class="linenr"> 7: </span>                    ,method ,@(<span class="org-keyword">when</span> args args))))</span>
<span class="linenr"> 8: </span>
<span class="linenr"> 9: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot-send-message</span> (account conversation text <span class="org-type">&amp;optional</span> reply)
<span class="linenr">10: </span>  <span class="org-doc">"Add TEXT to CONVERSATION via ACCOUNT. REPLY specifies a message id."</span>
<span class="linenr">11: </span>  (jami-bot--dbus-cfgmgr-call-method <span class="org-string">"sendMessage"</span>
<span class="linenr">12: </span>                                     account
<span class="linenr">13: </span>                                     conversation
<span class="linenr">14: </span>                                     text
<span class="linenr">15: </span>                                     `(,@(<span class="org-keyword">if</span> reply reply <span class="org-string">""</span>))
<span class="linenr">16: </span>                                     <span class="org-builtin">:int32</span> 0))
<span class="linenr">17: </span>
<span class="linenr">18: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot-reply-to-message</span> (account conversation msg text)
<span class="linenr">19: </span>  <span class="org-doc">"Add TEXT as a reply to MSG in CONVERSATION via ACCOUNT."</span>
<span class="linenr">20: </span>  (<span class="org-keyword">let</span> ((id (cadr (assoc-string <span class="org-string">"id"</span> msg))))
<span class="linenr">21: </span>    (jami-bot-send-message account conversation text id)))
</code></pre>
</div>

<p>
If you are not familiar with the special marker shown on line <a href="#coderef-marker" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-marker');" onmouseout="CodeHighlightOff(this, 'coderef-marker');">7</a> then
<a href="https://www.hoowl.se/elisp_splicing_conditional_argument_lists_to_pass_to_function_calls.html">you can read about how to use the special marker to construct argument lists in
a previous blog post</a>.
</p>

<p>
Finally, we can cache local accounts in a variable and set up a function to
update these:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org71a80b7"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">jami-bot--jami-local-account-ids</span> nil
  <span class="org-doc">"List of `</span><span class="org-doc"><span class="org-constant">jami</span></span><span class="org-doc">' local accounts user ids and name pairs.

Caches output of dbus-methods '</span><span class="org-doc"><span class="org-constant">getAccountList</span></span><span class="org-doc">' and
'</span><span class="org-doc"><span class="org-constant">getAccountDetails</span></span><span class="org-doc">'. For internal use in `</span><span class="org-doc"><span class="org-constant">jami-bot</span></span><span class="org-doc">'."</span>)
</code></pre>
</div>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-bot--refresh-accountid-list</span> ()
  <span class="org-doc">"Update cached values of known local account ids.

The values are stored in `</span><span class="org-doc"><span class="org-constant">jami-bot--jami-local-account-ids</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">let</span> ((accounts (jami-bot--dbus-cfgmgr-call-method
                   <span class="org-string">"getAccountList"</span>)))
    (<span class="org-keyword">let</span> ((value))
      (<span class="org-keyword">dolist</span> (acc accounts)
        (<span class="org-keyword">push</span> (cons (cadr (assoc-string
                           <span class="org-string">"Account.username"</span>
                           (jami-bot--dbus-cfgmgr-call-method
                            <span class="org-string">"getAccountDetails"</span>
                            acc))) <span class="org-warning">acc)</span>
              value))
      (<span class="org-keyword">setq</span> jami-bot--jami-local-account-ids value)))
  jami-bot--jami-local-account-ids)
</code></pre>
</div>
</div>
</section>
<section id="outline-container-orgeaa581b" class="outline-2">
<h2 id="orgeaa581b">Where to next?</h2>
<div class="outline-text-2" id="text-orgeaa581b">
<p>
If you have read this far, you likely have already ideas how you could use this
code to scratch an old itch of yours or you might have other comments &ndash;
<a href="mailto:hanno@hoowl.se">please get in touch</a>, I would love to hear them!
</p>

<p>
My personal need was, as mentioned before, straightforward note-taking on the
go: pop out the mobile phone, send a message to Emacs and have that thought
transferred directly into my second brain.
</p>

<p>
I wrote another blog post detailing the code and usage thereof:
<a href="https://hoowl.se/org-jami-bot.html">Note-taking on the go: Capturing messages and images
sent via Jami in Org mode</a>
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/jami.html">jami</a>, <a href="https://www.hoowl.se/../tags/programming.html">programming</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a></p>
</div>
</section>
]]>
</description></item>
<item>
<title>How to keep a lossless digital music library with lossy mirror in a few lines of /bash/</title>
<link>https://www.hoowl.se/mirror_flac2mp3.html</link>
<pubDate>Sat, 13 Jun 2020 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/mirror_flac2mp3.html</guid>
<description>
<![CDATA[<p>
I have been building (and rebuilding) my digital music library for around 22
years now. Starting with encoding runs of my few CDs into mp3 which took close
to an hour per track on my computer back when &ndash; and giving pretty abhorrent
quality nevertheless &ndash; I moved over the years to a collection which is now
several hundreds of albums large. Storage nowadays is much less of a concern,
however, and encoding into different formats is blazing fast, so I opt for the
lossless FLAC format whenever transferring new CDs to the computer or
downloading new albums. For mobile devices, I transcode the files into a mp3
mirror of my library by just running a handy little bash script.
</p>

<p>
My music library is stored in <code>~/Music</code> in two folders <code>mp3z</code> and <code>flacs</code> for
mp3 and FLAC formats, respectively. My mp3 collection also contains several albums
that I could not get in FLAC format (or downloaded before I had an interest in
lossless storage). But everything in my FLAC folder can also be found as mp3.
This is ensured when I run the following script:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr">1: </span><span class="org-comment-delimiter">#</span><span class="org-comment">!/usr/bin/</span><span class="org-keyword">env</span><span class="org-comment"> bash
<span class="linenr">2: </span></span>
<span class="linenr">3: </span><span class="org-builtin">cd</span> $<span class="org-variable-name">HOME</span>/Music/flacs
<span id="coderef-find" class="coderef-off"><span class="linenr">4: </span>find . -type f -name <span class="org-string">'*.flac'</span> -exec bash -c <span class="org-string">'if [ ! -e "../mp3z/${0/%flac/mp3}" ]; then mkdir -p "../mp3z/`dirname "$0"`" &amp;&amp; ffmpeg -i "$0" -y -codec:a libmp3lame -c:v copy -qscale:a 2 "../mp3z/${0/%flac/mp3}"; else echo SKIPPING: "$0"; fi'</span> {} <span class="org-string">\;</span></span>
<span id="coderef-sacad" class="coderef-off"><span class="linenr">5: </span>sacad_r . 1000 cover.jpg</span>
<span class="linenr">6: </span>find . -type f -name <span class="org-string">'cover.jpg'</span> -exec bash -c <span class="org-string">'if [ ! -e "../mp3z/${0}" ]; then cp "$0" "../mp3z/${0}"; fi'</span> {} <span class="org-string">\;</span>
</code></pre>
</div>

<p>
It looks for FLAC files without corresponding mp3 and reencodes them using
<code>ffmpeg</code>. The meta information contained in the tag is copied 1:1 as well. <i>Note
to self:</i> Line <a href="#coderef-find" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-find');" onmouseout="CodeHighlightOff(this, 'coderef-find');">4</a> is <b>quite</b> long and can probably be refactorized into a
function. After re-encoding the file, the cover art is retrieved using <code>sacad_r</code> in line <a href="#coderef-sacad" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-sacad');" onmouseout="CodeHighlightOff(this, 'coderef-sacad');">5</a>
and copied into the folder of the mp3 album.
</p>

<p>
As final step, the music is automatically spread to my various devices using
<a href="https://syncthing.net/">syncthing</a>. Which means that besides changing CDs and running <code>abcde -o flac &amp;&amp;
eject</code> I don&rsquo;t have to do much :)
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/scripts.html">scripts</a></p>
]]>
</description></item>
<item>
<title>Find recently modified files using =vertico= in Emacs</title>
<link>https://www.hoowl.se/find_recently_modified_files_using_vertico_in_emacs.html</link>
<pubDate>Fri, 03 Oct 2025 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/find_recently_modified_files_using_vertico_in_emacs.html</guid>
<description>
<![CDATA[<p>
<a href="https://github.com/minad/vertico"><code>vertico</code></a> is a powerful completion framework for Emacs that helps, for example, to find files using partial matches.
</p>

<p>
By default, it sorts all matches alphabetically and even offers some alternative
sorting mechanisms; but none matched my unpleasantly frequent use case: opening
<i>whatever-that-file-I-just-downloaded-is-called</i> or
<i>just-give-me-whatever-that-command-just-produced</i>. So sorting by modification
time was desperately needed!
</p>

<p>
While that seems like a humble enough desire, it turned out to be more
complicated then I thought. My searches online yielded few hits and no working
solution. But luckily my slowly evolving ELISP skills seem to have just passed
the threshold necessary to come up with a working solution &ndash; and here it is in
all its hacky glory:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(after! vertico
  (<span class="org-keyword">defun</span> <span class="org-function-name">hanno/vertico-sort-by-mtime</span> (files)
    <span class="org-doc">"Sort FILES by modification time (newest first)."</span>
    (<span class="org-keyword">let</span> ((dir nil))
      (<span class="org-keyword">when</span> (&lt; (minibuffer-prompt-end) (point))
        (<span class="org-keyword">setq</span> dir (buffer-substring (minibuffer-prompt-end) (point-max))))
      (sort files
            (<span class="org-keyword">lambda</span> (a b)
              (<span class="org-keyword">let*</span> (
                     (fa (expand-file-name a dir))
                     (fb (expand-file-name b dir))
                     (ta (file-attribute-modification-time (file-attributes fa)))
                     (tb (file-attribute-modification-time (file-attributes fb))))
                (time-less-p tb ta))))))

(<span class="org-keyword">defun</span> <span class="org-function-name">vertico-toggle-sort</span> ()
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">setq-local</span> vertico-sort-override-function
              (<span class="org-keyword">and</span> (not vertico-sort-override-function)
                   (<span class="org-keyword">lambda</span> (files)
                     (<span class="org-keyword">if</span> (<span class="org-keyword">and</span> (eq minibuffer-history-variable 'file-name-history)
                              (not (eq (car-safe minibuffer-completion-table) 'boundaries)))
                         (hanno/vertico-sort-by-mtime files)
                       (vertico-sort-history-length-alpha files))))
              vertico--input t))

(keymap-set vertico-map <span class="org-string">"M-S"</span> #'vertico-toggle-sort))
</code></pre>
</div>

<p>
After running the above code, open the <code>find-file</code> dialog by pressing <code>C-x C-f</code>
and then press <code>M-S s</code> (meta + shift + <code>s</code>) to toggle the sorting.
</p>
<section id="outline-container-org0c263d6" class="outline-2">
<h2 id="org0c263d6">How this works</h2>
<div class="outline-text-2" id="text-org0c263d6">
<p>
This uses the foreseen mechanism in <code>vertico</code> to modify the sorting of matches,
<code>vertico-sort-override-function</code>. Unfortunately, the function called by this
hook is only provided with a list of file names without any absolute path needed
to actually locate them. Retrieving the modification times is therefore not
straightforward.
</p>

<p>
<code>hanno/vertico-sort-by-mtime</code> therefore starts by deriving the current path from
the minibuffer prompt which (of course) displays it for the user. Using this, it
gets the modification timestamp for each of the candidates displayed and returns
a sorted list.
</p>

<p>
This certainly feels a little hacky even though it works fine and is probably
robust enough. Still, as the <code>marginalia</code> annotations displayed besides the
files already have the modification times, I am sure there is a more elegant way
to get to this information. One drawback is that it is fairly slow when there
are <i>lots</i> of files; in that case, using <code>dired</code> and its built-in sorting might
be quicker and more comfortable.
</p>

<p>
If you have a clever idea how to improve this or should you have encounted any
issues, get in touch and let me know!
</p>

<p>
This code is loosely based on the very helpful <a href="https://github.com/minad/vertico/wiki#customize-sorting-based-on-completion-category">examples in the <code>vertico</code> wiki</a>
and took inspiration from this <a href="https://www.reddit.com/r/emacs/comments/1g8yccr/sorting_file_date_using_vertico/">post addressing the same problem</a> (but missing the
issue with the current directory path).
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a></p>
</div>
</section>
]]>
</description></item>
<item>
<title>Goodbye file-format woes: Using Pandoc to export LaTeX documents to word processors</title>
<link>https://www.hoowl.se/how_to_export_latex_to_ms_word.html</link>
<pubDate>Wed, 14 Apr 2021 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/how_to_export_latex_to_ms_word.html</guid>
<description>
<![CDATA[<p>
I really like writing papers and complex documents in LaTeX. The results look
very nice (with a little tweaking) and things tend to behave even when the text
gets long. Emacs makes editing the document a smooth experience. For
collaboration, I can rely on the awesome power of git.
</p>

<p>
While the use of LaTeX is quite wide-spread in my discipline, some of my
colleagues prefer to use WYSIWYG-style word processors. In that case, I still
want to at least be able to draft the document in a format that I work
efficiently in before converting it to something else. So, here is how I turned
a physics paper draft from LaTeX into MS Word with a very satisfying end result!
</p>

<p>
The go-to tool for these purposes is <a href="https://pandoc.org/">pandoc</a>. It It
describes itself as swiss-army knife for markup file format conversions and has
a long list of supported formats. Many of these come with bidirectional
capabilities, meaning you can convert both to and from that particular format.
However, LaTeX itself can be rather complex, at least behind the scenes, which
makes it hard to convert <i>from</i>. It is what makes the format so flexible and the
typesetting so clean. However, for conversion into other formats, this makes it
notoriously difficult to get anywhere near right.
</p>

<p>
Pandoc does come with its own, simplified LaTeX parser which supports enough of
the format to produce good-looking and complete conversions including figures,
formulas and references. You have to help it along a little though by removing
and/or replacing certain non-supported packages and classes.
</p>

<p>
My <code>main.tex</code> LaTeX document from the Elsevier template package, which includes
all header information, looked roughly like this:
</p>

<div class="org-src-container">
<pre class="src src-latex"><code><span class="linenr"> 1: </span><span class="org-keyword">\documentclass</span>[draft]{<span class="org-builtin">elsarticle</span>}
<span class="linenr"> 2: </span>
<span class="linenr"> 3: </span><span class="org-keyword">\usepackage</span>{<span class="org-builtin">lineno,hyperref</span>}
<span class="linenr"> 4: </span><span class="org-keyword">\modulolinenumbers</span>[5]
<span class="linenr"> 5: </span>
<span class="linenr"> 6: </span><span class="org-keyword">\journal</span>{Journal of <span class="org-keyword">\LaTeX</span>\ Templates}
<span class="linenr"> 7: </span><span class="org-keyword">\usepackage</span>{<span class="org-builtin">fixltx2e</span>}
<span id="coderef-siunitx" class="coderef-off"><span class="linenr"> 8: </span><span class="org-keyword">\usepackage</span>[binary-units = true]{<span class="org-builtin">siunitx</span>}    <span class="org-comment">% Enable SI units</span>
<span class="linenr"> 9: </span></span><span class="org-keyword">\DeclareSIUnit\neutron</span>{neutron}
<span class="linenr">10: </span><span class="org-keyword">\DeclareSIUnit\Bq</span>{Bq}
<span class="linenr">11: </span><span class="org-keyword">\DeclareSIUnit\uranium</span>{U}
<span class="linenr">12: </span><span class="org-keyword">\DeclareSIUnit\n</span>{n}
<span class="linenr">13: </span><span class="org-keyword">\usepackage</span>{<span class="org-builtin">tikz</span>}
<span class="linenr">14: </span><span class="org-keyword">\usetikzlibrary</span>{backgrounds,positioning,fit,decorations.pathmorphing,arrows,shapes,calc,shadows,fadings}
<span class="linenr">15: </span>
<span class="linenr">16: </span><span class="org-keyword">\usepackage</span>{<span class="org-builtin">xfrac</span>} <span class="org-comment">% for nice (inline) fractions
<span class="linenr">17: </span></span><span class="org-keyword">\usepackage</span>[utf8]{<span class="org-builtin">inputenc</span>}
<span class="linenr">18: </span><span class="org-comment-delimiter">%% </span><span class="org-comment">`Elsevier LaTeX' style
<span class="linenr">19: </span></span><span class="org-keyword">\bibliographystyle</span>{elsarticle-num}
<span class="linenr">20: </span>
<span class="linenr">21: </span><span class="org-keyword">\begin</span>{<span class="org-function-name">document</span>}
<span class="linenr">22: </span><span class="org-comment-delimiter">%% </span><span class="org-comment">Settings for how to display units using the SI and SIrange commands
<span class="linenr">23: </span></span><span class="org-keyword">\sisetup</span>{range-phrase=-,range-units=single,product-units = power}
<span class="linenr">24: </span>
<span id="coderef-frontmatter" class="coderef-off"><span class="linenr">25: </span><span class="org-keyword">\begin</span>{<span class="org-function-name">frontmatter</span>}</span>
<span class="linenr">26: </span>
<span class="linenr">27: </span><span class="org-keyword">\title</span>{<span class="org-function-name">My paper's title</span>}
<span class="linenr">28: </span>
<span class="linenr">29: </span><span class="org-keyword">\author</span>[mysecondaryaddress]{Author 1}
<span class="linenr">30: </span><span class="org-keyword">\author</span>[mymainaddress]{Author 2<span class="org-keyword">\corref</span>{mycorrespondingauthor}}
<span class="linenr">31: </span><span class="org-keyword">\cortext</span>[mycorrespondingauthor]{Corresponding author}
<span class="linenr">32: </span><span class="org-keyword">\ead</span>{email@example.com}
<span class="linenr">33: </span>
<span class="linenr">34: </span><span class="org-keyword">\begin</span>{<span class="org-function-name">abstract</span>}
<span class="linenr">35: </span>Add text here, summarizing the paper.
<span class="linenr">36: </span><span class="org-keyword">\end</span>{<span class="org-function-name">abstract</span>}
<span class="linenr">37: </span>
<span class="linenr">38: </span><span class="org-keyword">\begin</span>{<span class="org-function-name">keyword</span>}
<span class="linenr">39: </span>add<span class="org-keyword">\sep</span> Text<span class="org-keyword">\sep</span> here<span class="org-keyword">\sep</span>
<span class="linenr">40: </span><span class="org-keyword">\end</span>{<span class="org-function-name">keyword</span>}
<span class="linenr">41: </span>
<span class="linenr">42: </span><span class="org-keyword">\end</span>{<span class="org-function-name">frontmatter</span>}
<span class="linenr">43: </span>
<span class="linenr">44: </span><span class="org-keyword">\linenumbers</span>
<span class="linenr">45: </span>
<span id="coderef-text" class="coderef-off"><span class="linenr">46: </span><span class="org-keyword">\input</span>{<span class="org-builtin">article_text</span>}</span>
<span class="linenr">47: </span><span class="org-keyword">\bibliography</span>{<span class="org-builtin">references</span>}
<span class="linenr">48: </span>
<span class="linenr">49: </span><span class="org-keyword">\end</span>{<span class="org-function-name">document</span>}
</code></pre>
</div>

<p>
In the document above, the main text of the paper is contained in <code>article_text</code>
on line <a href="#coderef-text" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-text');" onmouseout="CodeHighlightOff(this, 'coderef-text');">46</a> which is included in the document&rsquo;s body. Running <code>pandoc</code>
over this, I encountered issues mostly with the meta-data (title, authors due to
the <code>elsarticle</code> class stuff line <a href="#coderef-frontmatter" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-frontmatter');" onmouseout="CodeHighlightOff(this, 'coderef-frontmatter');">25</a>ff) and with the units (<code>siunitx</code>
package on line <a href="#coderef-siunitx" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-siunitx');" onmouseout="CodeHighlightOff(this, 'coderef-siunitx');">8</a>). The latter helps with handling various units and
typesetting them in a consistent manner. For the word-processor document that we
want to create, all this doesn&rsquo;t have too much of a visible effect though. So, I
created a separate <code>export.tex</code> where I replaced many of the style settings and
set up my own, much reduced unit commands:
</p>

<div class="org-src-container">
<pre class="src src-latex"><code><span class="linenr"> 1: </span><span class="org-keyword">\documentclass</span>{<span class="org-builtin">article</span>}
<span class="linenr"> 2: </span>
<span class="linenr"> 3: </span><span class="org-keyword">\usepackage</span>{<span class="org-builtin">tikz</span>}
<span class="linenr"> 4: </span><span class="org-keyword">\usetikzlibrary</span>{backgrounds,positioning,fit,decorations.pathmorphing,arrows,shapes,calc,shadows,fadings}
<span class="linenr"> 5: </span>
<span class="linenr"> 6: </span><span class="org-keyword">\usepackage</span>{<span class="org-builtin">xfrac</span>} <span class="org-comment">% for nice (inline) fractions
<span class="linenr"> 7: </span></span><span class="org-keyword">\usepackage</span>[utf8]{<span class="org-builtin">inputenc</span>}
<span class="linenr"> 8: </span>
<span class="linenr"> 9: </span><span class="org-keyword">\bibliographystyle</span>{usrtnum}
<span class="linenr">10: </span>
<span id="coderef-si" class="coderef-off"><span class="linenr">11: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\SI</span>[2]{#1~#2}</span>
<span class="linenr">12: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\SIrange</span>[3]{#1~#3 - #2~#3}
<span class="linenr">13: </span>
<span class="linenr">14: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\neutron</span>{<span class="org-keyword">\textrm</span>{n}}
<span class="linenr">15: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\uranium</span>{<span class="org-keyword">\textrm</span>{U}}
<span class="linenr">16: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\n</span>{<span class="org-keyword">\textrm</span>{n}}
<span class="linenr">17: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\nano</span>{<span class="org-keyword">\textrm</span>{n}}
<span class="linenr">18: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\kilo</span>{<span class="org-keyword">\textrm</span>{k}}
<span class="linenr">19: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\mega</span>{<span class="org-keyword">\textrm</span>{M}}
<span class="linenr">20: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\giga</span>{<span class="org-keyword">\textrm</span>{G}}
<span class="linenr">21: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\tera</span>{<span class="org-keyword">\textrm</span>{T}}
<span class="linenr">22: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\meter</span>{<span class="org-keyword">\textrm</span>{m}}
<span class="linenr">23: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\m</span>{<span class="org-keyword">\textrm</span>{m}}
<span class="linenr">24: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\mm</span>{<span class="org-keyword">\textrm</span>{mm}}
<span class="linenr">25: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\cm</span>{<span class="org-keyword">\textrm</span>{cm}}
<span class="linenr">26: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\volt</span>{<span class="org-keyword">\textrm</span>{V}}
<span class="linenr">27: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\micro</span>{<span class="org-keyword">\mu</span>}
<span class="linenr">28: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\second</span>{<span class="org-keyword">\textrm</span>{s}}
<span class="linenr">29: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\hour</span>{<span class="org-keyword">\textrm</span>{h}}
<span class="linenr">30: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\keV</span>{<span class="org-keyword">\textrm</span>{keV}}
<span class="linenr">31: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\MeV</span>{<span class="org-keyword">\textrm</span>{MeV}}
<span class="linenr">32: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\per</span>{/}
<span class="linenr">33: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\ampere</span>{<span class="org-keyword">\textrm</span>{A}}
<span class="linenr">34: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\Hz</span>{<span class="org-keyword">\textrm</span>{Hz}}
<span class="linenr">35: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\hertz</span>{<span class="org-keyword">\textrm</span>{Hz}}
<span class="linenr">36: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\byte</span>{<span class="org-keyword">\textrm</span>{B}}
<span class="linenr">37: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\gray</span>{<span class="org-keyword">\textrm</span>{Gr}}
<span class="linenr">38: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\Bq</span>{<span class="org-keyword">\textrm</span>{Bq}}
<span class="linenr">39: </span><span class="org-keyword">\newcommand</span><span class="org-function-name">\sievert</span>{<span class="org-keyword">\textrm</span>{Sv}}
<span class="linenr">40: </span>
<span class="linenr">41: </span><span class="org-keyword">\title</span>{<span class="org-function-name">My paper's title</span>}
<span class="linenr">42: </span><span class="org-keyword">\author</span>{Author 1, Author 2}
<span class="linenr">43: </span>
<span class="linenr">44: </span><span class="org-keyword">\begin</span>{<span class="org-function-name">document</span>}
<span class="linenr">45: </span><span class="org-comment-delimiter">%% </span><span class="org-comment">Settings for how to display units using the SI and SIrange commands
<span class="linenr">46: </span></span>
<span class="linenr">47: </span><span class="org-keyword">\begin</span>{<span class="org-function-name">abstract</span>}
<span class="linenr">48: </span>Add text here, summarizing the paper.
<span class="linenr">49: </span><span class="org-keyword">\end</span>{<span class="org-function-name">abstract</span>}
<span class="linenr">50: </span>
<span class="linenr">51: </span><span class="org-comment-delimiter">% </span><span class="org-comment">\input{article_text}
<span class="linenr">52: </span></span><span class="org-keyword">\input</span>{<span class="org-builtin">article_text</span>}
<span class="linenr">53: </span><span class="org-keyword">\bibliography</span>{<span class="org-builtin">references</span>}
<span class="linenr">54: </span>
<span class="linenr">55: </span><span class="org-keyword">\end</span>{<span class="org-function-name">document</span>}
</code></pre>
</div>

<p>
On line <a href="#coderef-si" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-si');" onmouseout="CodeHighlightOff(this, 'coderef-si');">11</a> I create my own <code>\SI</code> command for setting units that I then define in the following lines.
Yes, I do need that many different units in the same text&hellip; ;)
</p>

<p>
This can now be converted with <code>pandoc</code>:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr">1: </span>pandoc -s export.tex -o export.docx --bibliography references.bib --csl elsevier-vancouver.csl
</code></pre>
</div>

<p>
The needed citation style file (and many others) can be downloaded from <a href="https://citationstyles.org/authors/">https://citationstyles.org/authors/</a>.
</p>

<p>
Voila, we now have a <code>export.docx</code> document! It does not look identical to the
LaTeX output (how could it?) but it includes the same information and looks not
too shabby!
</p>

<p>
If you also want to have your figures and equations numbered as you would expect
in LaTeX, you need an additional pandoc-filter: <a href="https://github.com/tomduck/pandoc-xnos"><code>pandoc-xnos</code></a>. It can be
installed via <code>pip</code>:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code>pip install pandoc-fignos pandoc-eqnos pandoc-tablenos <span class="org-sh-escaped-newline">\</span>
            pandoc-secnos --user
</code></pre>
</div>

<p>
Then, call pandoc using the corresponding filter:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr">1: </span>pandoc -s export.tex -o export.docx --filter pandoc-xnos --bibliography references.bib --csl elsevier-vancouver.csl
</code></pre>
</div>

<p>
But beware that labels for equations, figures and such cannot have underline
characters (&rsquo;_&rsquo;) but have to consist of single words (camel case is ok though).
That was at least the case at the time of testing it here.
</p>

<p>
Once sent out, you might get back documents in unhandly proprietary formats. In that
case, you can even extract diffs between versions send out and received back &ndash;
or even from the <i>track-changes</i> feature of MS Word using <code>pandiff</code>:
<a href="https://github.com/davidar/pandiff">https://github.com/davidar/pandiff</a>
</p>

<p>
Hope this helps your workflow &ndash; and keeps your colleagues happy while allowing
you to use the tools of your choice!
</p>


<p>
<i>Update 20210610</i>: Added info on numbering equations and figures.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/latex.html">latex</a>, <a href="https://www.hoowl.se/../tags/pandoc.html">pandoc</a></p>
]]>
</description></item>
<item>
<title>Elisp: Splicing conditional argument lists to pass to function calls</title>
<link>https://www.hoowl.se/elisp_splicing_conditional_argument_lists_to_pass_to_function_calls.html</link>
<pubDate>Wed, 03 Aug 2022 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/elisp_splicing_conditional_argument_lists_to_pass_to_function_calls.html</guid>
<description>
<![CDATA[<p>
Today, I found out about the <a href="https://www.gnu.org/software/emacs/manual/html_mono/elisp.html#Backquote">special marker <code>,@</code> </a> in Elisp: it allows to <i>splice</i> an evaluated value into a quoted list while keeping the level of the &ldquo;injected&rdquo; elements the same as other elements on the resulting list. This solved (or at least simplified) a problem I faced a couple of times already: whenever I wanted to assemble arguments for functions that have a <code>&amp;rest ARGS</code> in their signature, such as <a href="https://www.gnu.org/software/emacs/manual/html_mono/elisp.html#Synchronous-Processes"><code>call-process</code></a>, using conditionals and evaluated functions, the code quickly became more complicated or duplicative than felt right to me. Let me give you an example.
</p>

<p>
<code>call-process</code> uses the signature <code>call-process PROGRAM &amp;optional INFILE DESTINATION DISPLAY &amp;rest ARGS</code>. All the arguments <code>ARGS</code> to the called program have to be given as individual strings. So for a call to the command line tool <code>find</code> to look for files matching the pattern <code>elisp*.org</code> in the current directory that have been modified today this could look like:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">with-temp-buffer</span>
  (call-process
   <span class="org-string">"find"</span> nil t nil
   <span class="org-string">"./"</span>
   <span class="org-string">"-mtime"</span> <span class="org-string">"0"</span>
   <span class="org-string">"-name"</span> <span class="org-string">"elisp*.org"</span>)
  (buffer-substring (point-min) (point-max)))
</code></pre>
</div>

<pre class="example">
./elisp_splicing_conditional_argument_lists_to_pass_to_function_calls.org
</pre>

<p>
Here, <code>with-temp-buffer</code> and <code>buffer-substring</code> are used to process the output of the command. Note that <code>-name-</code> and <code>elisp*.org</code> are separate strings despite the latter giving the <i>pattern</i> to the argument <i>name</i> &ndash; anything else, and <code>find</code> would not recognize the argument as valid. The same goes for the <code>-mtime</code> argument and its parameter.
</p>

<p>
I found it difficult to provide arguments in this particular format when I want
to fill the values from variables and make some of them optional. Consider this
example where the result should be a <code>-name</code> and <i>pattern</i> but no <i>mtime</i>
arguments to <code>find</code>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">let</span> ((pattern <span class="org-string">"elisp*.org"</span>)
      (n nil))
  `(,(<span class="org-keyword">when</span> pattern
       `(<span class="org-string">"-name"</span> ,pattern))
    ,(<span class="org-keyword">when</span> n
       `(<span class="org-string">"-mtime"</span> ,n))))
</code></pre>
</div>

<pre class="example">
(("-name" "elisp*.org") nil)
</pre>

<p>
This approach <i>almost</i> works, but the result is a nested list and a <code>nil</code> value which we have to remove before calling <code>call-process</code>. This can be easily done by introducing the <code>,@</code> special marker:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">let*</span> ((pattern <span class="org-string">"elisp*.org"</span>)
      (n nil))
  `(,@(<span class="org-keyword">when</span> pattern
        `(<span class="org-string">"-name"</span> ,pattern))
    ,@(<span class="org-keyword">when</span> n
        `(<span class="org-string">"-mtime"</span> ,n))))
</code></pre>
</div>

<pre class="example">
("-name" "elisp*.org")
</pre>

<p>
Now the result is in the right format and the code had to be changed only minimally! Final puzzle piece: feed the arguments to <code>call-process</code> using <code>apply #'call-process args</code>:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">let*</span> ((pattern <span class="org-string">"elisp*.org"</span>)
      (n nil))
  (<span class="org-keyword">with-temp-buffer</span>
    (apply #'call-process
           `(<span class="org-string">"find"</span> nil t nil
             ,@(<span class="org-keyword">when</span> pattern
                 `(<span class="org-string">"-name"</span> ,pattern))
             ,@(<span class="org-keyword">when</span> n
                 `(<span class="org-string">"-mtime"</span> ,n))))
    (buffer-substring (point-min) (point-max))))
</code></pre>
</div>

<pre class="example">
./elisp_splicing_conditional_argument_lists_to_pass_to_function_calls.org
</pre>

<p>
Which &ndash; to my eyes &ndash; is compact and reasonably readable code 😸
</p>

<p>
But feel free to get in touch and tell me otherwise (preferably with suggestions for improvements)!
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a>, <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a></p>
]]>
</description></item>
<item>
<title>Note-taking on the go: Capturing messages and images sent via Jami in Org mode</title>
<link>https://www.hoowl.se/org-jami-bot.html</link>
<pubDate>Sun, 16 Apr 2023 23:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/org-jami-bot.html</guid>
<description>
<![CDATA[<p>
:header-args:emacs-lisp: :tangle &ldquo;~/src/org-jami-bot/org-jami-bot.el&rdquo;
</p>
<p>
I keep most of my life in plain text files and manage them using Org mode.
That&rsquo;s how I keep track of things to do, ideas to flesh out or random
information that might come in useful later. However, when ideas strike, I don&rsquo;t
always have a keyboard and Emacs ready; often that is on the bus going to or
from work. Or I find myself wanting to document something interesting by taking
a picture. But what use is a picture without additional notes and context, ready
to be refiled into my second (digital) brain? I have not found any app that fits
my workflow and does not disrupt my train of thoughts. The most natural thing
would be a message to myself &ndash; or more specifically, Emacs&hellip;
</p>

<p>
So I wrote <code>org-jami-bot</code> which builds upon <code>jami-bot</code> and extends it with Org
mode capture functionality for text messages and images. It allows me to
schedule agenda items at specific dates, compose multi-message captures and even
capture URLs including meta-data using <code>org-capture-ref</code> &ndash; all by sending a
message via the GNU Jami messenger.
</p>

<p>
<code>jami-bot</code> was covered in detail in an earlier blog post of this series and
reference/URL capture will be the focus of the last part. This blog post will
take up a more user-centric perspective on capturing notes with Jami.
</p>

<p>
So the natural start is a demo!
</p>
<section id="outline-container-org5db6f4e" class="outline-2">
<h2 id="org5db6f4e">Demo (with cats!)</h2>
<div class="outline-text-2" id="text-org5db6f4e">

<div id="org83c16fe" class="figure">
<p><img src="https://www.hoowl.se/../images/posts/jami-bot/org-jami-bot_capture_animation.gif" alt="org-jami-bot_capture_animation.gif" />
</p>
<p><span class="figure-number">Figure 1: </span>Animation of a multi-message capture from the Android Jami app.</p>
</div>

<p>
The animation shows how one initiates a multi-message capture from within the
Jami messenger app &ndash; 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 <code>!start</code> followed by the title of the capture. Every command
consists of an exclamation mark and a single word, for example: <code>!help</code> which
shows the available commands or <code>!today</code> which captures the remainder of the
message as a todo entry scheduled today. Everything else is treated as a normal
message (and captured verbatim).
</p>

<p>
On the other side of the chat is <code>jami-bot</code> running within Emacs on my local
computer. It responds to each of my messages to indicate how it was processed.
</p>

<p>
Once the multi-message capture session is started, every following message is
simply added. This includes images which will be downloaded and stored locally
on my computer. A reference in the form of a link will be included in the notes.
</p>

<p>
Putting down the phone and opening the computer again, I will see something like
this on the screen:
</p>

<div id="org8f56e02" class="figure">
<p><img src="https://www.hoowl.se/../images/posts/jami-bot/20230410_20h25m47s_grim.png" alt="20230410_20h25m47s_grim.png" />
</p>
<p><span class="figure-number">Figure 2: </span>Screenshot of the Emacs instance running <code>jami-bot</code>: to the left the capture and to the right the screenshot of the conversation (also sent via Jami).</p>
</div>

<p>
On the left is the capture buffer which includes the individual messages and the
image I sent shown inline. Additional meta information like the capture date is
also included. Files sent separately as a single message, such as the screenshot
in the next entry, are captured as links to the locally downloaded file and tagged as
<code>FILE</code>. In principle, further automatic processing (e.g. OCR) could easily be
integrated. In clear text, the first example capture looks like this:
</p>

<pre class="example" id="org229d3d7">
* Demonstration of a multi-message capture :)
:PROPERTIES:
:CREATED: [2023-04-10 Mon 18:26]
:END:
This is the first line to be added.
But there can more!
#+ATTR_ORG: :width 400
[[../../jami/20230410-1826_image1_1.png]]
Like cute cat pictures 😻
That's it!
</pre>

<p>
Any received file will also be added to the variable <code>org-stored-links</code> and can
then be easily inserted as link in any Org mode document using <code>C-c C-l</code>. For
me, this has become the easiest and quickest way to transfer specific files from
my mobile phone to my computer and into my notes.
</p>
</div>
</section>
<section id="outline-container-org3dc8c16" class="outline-2">
<h2 id="org3dc8c16">Advantages and disadvantages of using Jami and <code>jami-bot</code></h2>
<div class="outline-text-2" id="text-org3dc8c16">
<p>
<a href="https://docs.jami.net/user/faq.html#what-makes-jami-different-from-other-communication-platforms">Jami is a distributed and private messenger</a> that is part of the GNU project and
<a href="https://savoirfairelinux.com">mainly developed by Savoir-faire Linux</a>. <i>Private</i> in this context does not only
refer to the fact that messages, calls and video chats are encrypted, but that
<a href="https://docs.jami.net/user/faq.html#what-information-do-i-need-to-provide-to-create-a-jami-account">you need to provide essentially no personal information to create a Jami
account</a>. <i>Distributed</i> means that you can have encrypted (group) chats without
requiring any server to exchange them through &ndash; which even <a href="https://docs.jami.net/user/lan-only.html">works between
devices on the same local network while the internet is down</a>.
</p>

<p>
Jami is easy to deploy on most OS and is available for different mobile devices
as well. That coupled with that fact that it was rather straightforward to
interface from Emacs made it a ideal candidate for this experiment.
</p>

<p>
However, Jami has some rougher edges from a user&rsquo;s perspective (that is to say,
my personal one). While the mobile Android client has improved significantly
over the past years, it still might quietly fail to sync up with other clients.
In those cases, only a restart of the App seems to help reliably. Other quirks
can be slightly annoying at times: pasting from the clipboard, for example,
wipes the current message draft on my Android client &ndash; and I tend to insert
links as the last step of composing a message.
</p>

<p>
Similarly, also the desktop daemon, <code>jamid</code>, which runs in the background while
<code>jami-bot</code> interacts with it, sometimes needs a friendly <code>killall jamid</code>
followed by <code>M-x jami-bot-register</code> to re-initiate the service. In particular,
network state changes, i.e. a temporary loss of connectivity seems to cause a
drop from the Jami network which Jami does not recover quickly from.
</p>

<p>
One more thing to be aware of is that <code>jami-bot</code> only reacts to messages being
<i>received</i>. So if the daemon (and/or the GUI app) is already running before
<code>jami-bot</code> is registered and started, some messages might slip by unnoticed.
Should you not yet have received them yet though, for example because the daemon
lost the connection, you can simply follow the killall-and-register procedure
outlined above and you <i>will</i> capture any missed messages.
</p>

<p>
Personally, I can live with these compromises.
</p>

<p>
One last thing to consider is security: I am not aware of any recent security
audit of Jami. Either way, bugs affecting the security of the messenger likely
exist. Personally, I assume that by disabling unneeded features such as phone
and video calls on my account, disallowing connections with unknown accounts and
limiting my accounts exposure will keep it sufficiently secure for me. Timely
updates are a given of course. <code>(org-)jami-bot</code> should only marginally increase
the attack surface as long as you use it with trusted devices and accounts and
do not extend it with functions that directly execute parts of the message
received as code. In case you discover any potential security risks with the code I
provide or the way I interface with Jami, please <a href="mailto:hanno@hoowl.se">let me know</a>!
</p>

<p>
In any case, <a href="https://ssd.eff.org/module/your-security-plan">you should make your own threat model</a> for your use case and situation.
</p>

<p>
That said, let&rsquo;s look into setting things up!
</p>
</div>
</section>
<section id="outline-container-org8dbb38e" class="outline-2">
<h2 id="org8dbb38e">Setup</h2>
<div class="outline-text-2" id="text-org8dbb38e">
<p>
We will need to have the Jami daemon, <code>jamid</code>, installed on the local system. On
Debian, this can be done by simply running <code>sudo apt install jami</code> which will
also install the GUI application. The latter is not strictly necessary but can
be more comfortable to use during the account setup. The version installed
through <code>apt</code>, however, is likely older than what is provided on <a href="https://jami.net/">the official
Jami download pages</a> &ndash; consider updating should you run into any connectivity
issues later.
</p>

<p>
Jami is controlled by <code>jami-bot</code> via
<a href="https://www.freedesktop.org/wiki/Software/dbus/">a protocol called <i>D-Bus</i></a>.
If you are using a Linux-based system such as Ubuntu, you are almost certainly
already running D-Bus and an Emacs with built-in support. If you are using Mac
OSX, you need to install D-Bus first (as far as I know). Even MS Windows can run
D-Bus. If you succeed with any of the latter options, please let me know &ndash;
however, this is somewhat outside the scope of this post.
</p>

<p>
Then you will need to create a Jami account. The easiest is to make a completely
new one only for <code>jami-bot</code>, even if you already have a Jami account. Simply use
the GUI or, for more advanced users and/or headless machines, <a href="https://hoowl.se/jami-bot.html">follow the steps
outlined in the first post of this series</a>. You also need to add any user that
should be able to interact with <code>jami-bot</code> as a contact and have the request be
accepted on the other side &ndash; only then can you start exchanging messages.
</p>

<p>
By default, <code>jami-bot</code> will react to any message sent to any local Jami account
but will ignore message sent <i>from</i> local accounts (to avoid feedback loops). In
case you have several local accounts and would like to limit <code>jami-bot</code> to only
one of them, you can configure the variable <code>jami-bot-account-user-names</code>.
</p>
</div>
<div id="outline-container-org236b79e" class="outline-3">
<h3 id="org236b79e"><code>jami-bot</code> and <code>org-jami-bot</code></h3>
<div class="outline-text-3" id="text-org236b79e">
<p>
You will also need to install the <a href="https://gitlab.com/hperrey/jami-bot"><code>jami-bot</code></a> and <a href="https://gitlab.com/hperrey/org-jami-bot"><code>org-jami-bot</code></a> packages in
Emacs. These will eventually be made available via e.g. MELPA but currently, you
need to install from source repository linked above. Once that is done, simply
<code>require</code> the <code>org-jami-bot</code> package to load them:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">require</span> '<span class="org-constant">org-jami-bot</span>)
</code></pre>
</div>

<p>
In order to capture messages automatically and without user interaction, we need
to set up an appropriate capture template. Let us start by setting an
associated key:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">setq</span> org-jami-bot-capture-key <span class="org-string">"J"</span>)
</code></pre>
</div>

<p>
Just make sure that this does not conflict with any other already defined
template in <code>org-capture-templates</code>.
</p>

<p>
If you just want to get started right way with the default setup for <code>org-jami-bot</code>, simply run
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(org-jami-bot-default-setup)
(jami-bot-register)
</code></pre>
</div>
<p>
and skip ahead to the next section! If you would like to understand the
configuration a little bit better or make adjustments, read on!
</p>
</div>
</div>
<div id="outline-container-org8d9e36c" class="outline-3">
<h3 id="org8d9e36c">Setup explained</h3>
<div class="outline-text-3" id="text-org8d9e36c">
<p>
For the actual template, use <i>initial content</i> (<code>%i</code>), define the key via the
above variable, and set the property <code>:immediate-finish</code> to file the capture
away directly. In the code below, you might want to replace
<code>org-default-notes-file</code> with another location:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">if</span> (assoc org-jami-bot-capture-key org-capture-templates)
    (message <span class="org-string">"Capture template referred to by \"%s\" key already defined!"</span>
             org-jami-bot-capture-key)
  (add-to-list 'org-capture-templates
             `(,org-jami-bot-capture-key <span class="org-string">"Jami message"</span> entry (file org-default-notes-file)
               <span class="org-string">"%i"</span> <span class="org-builtin">:immediate-finish</span> t)))
</code></pre>
</div>
</div>
</div>
<div id="outline-container-org8a1f8d2" class="outline-3">
<h3 id="org8a1f8d2">Extending  <code>jami-bot</code> commands for capture</h3>
<div class="outline-text-3" id="text-org8a1f8d2">
<p>
Anytime you send a Jami message that starts with an exclamation mark, <code>jami-bot</code>
will interpret this as a command that will trigger a special action. However,
<code>jami-bot</code> comes only with a rudimentary set of commands. These are extended via
<code>org-jami-bot</code> and need to be registered so that <code>jami-bot</code> knows about them:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">setq</span> jami-bot-command-function-alist (append jami-bot-command-function-alist
  '((<span class="org-string">"!today"</span> . org-jami-bot--command-function-today)
    (<span class="org-string">"!schedule"</span> . org-jami-bot--command-function-schedule)
    (<span class="org-string">"!start"</span> . org-jami-bot--command-function-start)
    (<span class="org-string">"!done"</span> . org-jami-bot--command-function-done))))
</code></pre>
</div>
<p>
This maps the command strings to the functions that handle them. The latter will
be explained in more detail in the next section!
</p>

<p>
As this list of commands is easily forgotten while on the road, you can always
send the command <code>!help</code> via Jami to receive a summary of all known commands and
their docstrings as reply. Of course, you can easily add additional mappings to
the list above. Just be sure that you do not overwrite the default commands
already listed in <code>jami-bot-command-function-alist</code>, or you would lose e.g.
the <i>!help</i> command.
</p>

<p>
Finally, we also want non-command messages captured, whether it is a plain text
message or a file being sent. This is accomplished by adding corresponding
<i>hooks</i> that will be run when <code>jami-bot</code> processes such messages:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(add-hook 'jami-bot-text-message-functions 'org-jami-bot--capture-plain-messsage)
(add-hook 'jami-bot-data-transfer-functions 'org-jami-bot--capture-file)
</code></pre>
</div>

<p>
While we are at it, you might want to adjust the directory to which files are
being downloaded to from its default value:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">setq</span> jami-bot-download-path <span class="org-string">"~/jami/"</span>)
</code></pre>
</div>

<p>
Finally, we need to register <code>jami-bot</code> so it listens to incoming messages:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(jami-bot-register)
</code></pre>
</div>

<p>
This is all the setup we need! Now it is time to fire up Jami on your phone or
any other device and capture messages!
</p>
</div>
</div>
</section>
<section id="outline-container-org52858b3" class="outline-2">
<h2 id="org52858b3">First steps</h2>
<div class="outline-text-2" id="text-org52858b3">
<p>
Once you have <code>jami-bot</code> and <code>org-jami-bot</code> configured, check that the account
you want to send captures to is shown as present in Jami (indicated by a green
dot in the profile). Send a simple command such as <code>!help</code> or <code>!ping</code> first. On
the computer running <code>jami-bot</code>, you should see a message appear in the
minibuffer indicating that the message was received. Shortly after, you should
get a response via Jami.
</p>

<p>
After that, try a capture: simply send a text message (without starting it with
an exclamation mark). You should see the response &ldquo;captured&rdquo; after only a
moment. The message should be filed at the location you specified in your
capture template (<code>org-default-notes-file</code> by default).
</p>

<p>
Try sending an image or starting a multi-message capture (by sending <code>!start</code>)
next. If all works as intended, you might want to adjust or extend the format of
the capture &ndash; so let us look into the code handling the captures!
</p>
</div>
</section>
<section id="outline-container-orgaadbd4e" class="outline-2">
<h2 id="orgaadbd4e">Code explained: <code>org-jami-bot</code> capture functions</h2>
<div class="outline-text-2" id="text-orgaadbd4e">
<p>
This section is mostly for the curious or those that would like to extend
<code>org-jami-bot</code> to scratch their own itch. If you want to go even deeper, a
previous post explained <code>jami-bot</code> which might be useful in case you want to
explore more of Jami&rsquo;s functionality.
</p>

<p>
<b>Note</b>: the code discussed below is what I originally wrote &ndash; it probably has
evolved since and the newest version will be hosted at this repository:
<a href="https://gitlab.com/hperrey/org-jami-bot">https://gitlab.com/hperrey/org-jami-bot</a>
</p>

<p>
One central function is the capture processor for any plain text message that
is not a command:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="linenr"> 1: </span>(<span class="org-keyword">defun</span> <span class="org-function-name">org-jami-bot--capture-plain-messsage</span> (account conversation msg)
<span class="linenr"> 2: </span>  <span class="org-doc">"Capture body in MSG and replies to original message.
<span class="linenr"> 3: </span>
<span class="linenr"> 4: </span>CONVERSATION and ACCOUNT specify the corresponding ids that the
<span class="linenr"> 5: </span>message belongs to."</span>
<span id="coderef-buf" class="coderef-off"><span class="linenr"> 6: </span>  (<span class="org-keyword">let*</span> ((buf (format <span class="org-string">"*jami-capture-%s-%s*"</span> account conversation))</span>
<span class="linenr"> 7: </span>         (continue (get-buffer buf))
<span class="linenr"> 8: </span>         (body (cadr (assoc-string <span class="org-string">"body"</span> msg)))
<span class="linenr"> 9: </span>         (lines (string-lines body))
<span class="linenr">10: </span>         <span class="org-comment-delimiter">;; </span><span class="org-comment">use inactive timestamps
<span class="linenr">11: </span></span>         (timefmt (concat <span class="org-string">"["</span> (substring (cdr org-time-stamp-formats) 1 -1) <span class="org-string">"]"</span>)))
<span id="coderef-insert" class="coderef-off"><span class="linenr">12: </span>    (<span class="org-keyword">with-current-buffer</span> (get-buffer-create buf)</span>
<span class="linenr">13: </span>      (insert (<span class="org-keyword">if</span> continue
<span class="linenr">14: </span>                  <span class="org-comment-delimiter">;; </span><span class="org-comment">multi message capture
<span class="linenr">15: </span></span>                  (concat body <span class="org-string">"\n"</span>)
<span class="linenr">16: </span>                <span class="org-comment-delimiter">;; </span><span class="org-comment">single message capture
<span class="linenr">17: </span></span>                (format <span class="org-string">"* %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n%s"</span>
<span class="linenr">18: </span>                        (car lines) (format-time-string timefmt) (string-join (cdr lines) <span class="org-string">"\n"</span>))))
<span id="coderef-reply" class="coderef-off"><span class="linenr">19: </span>      (jami-bot-reply-to-message</span>
<span class="linenr">20: </span>       account
<span class="linenr">21: </span>       conversation
<span class="linenr">22: </span>       msg
<span class="linenr">23: </span>       (<span class="org-keyword">if</span> continue
<span class="linenr">24: </span>           <span class="org-string">"message added. Finish capture with \"!done\""</span>
<span id="coderef-capture" class="coderef-off"><span class="linenr">25: </span>         (<span class="org-keyword">if</span> (<span class="org-keyword">and</span> (org-capture-string</span>
<span class="linenr">26: </span>                   (buffer-string)
<span class="linenr">27: </span>                   org-jami-bot-capture-key)
<span class="linenr">28: </span>                  (kill-buffer buf))
<span class="linenr">29: </span>             <span class="org-string">"captured!"</span>
<span class="linenr">30: </span>           <span class="org-string">"error during org-capture :("</span>))))))
</code></pre>
</div>

<p>
It consists of three parts: starting on line <a href="#coderef-buf" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-buf');" onmouseout="CodeHighlightOff(this, 'coderef-buf');">6</a> it sets up helper
variables, from line <a href="#coderef-insert" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-insert');" onmouseout="CodeHighlightOff(this, 'coderef-insert');">12</a> onward it sets up the text to be inserted
into a capture buffer and after line <a href="#coderef-reply" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-reply');" onmouseout="CodeHighlightOff(this, 'coderef-reply');">19</a> constructs the reply. The capture template is chosen according to the value of <code>org-jami-bot-capture-key</code>
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org025d5a0"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">org-jami-bot-capture-key</span> <span class="org-string">"J"</span>
  <span class="org-doc">"Key for the org-capture template to call for Jami messages"</span>)
</code></pre>
</div>


<p>
However, the actual capture (line <a href="#coderef-capture" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-capture');" onmouseout="CodeHighlightOff(this, 'coderef-capture');">25</a>) is only performed if the
capture buffer did not already exist &ndash; if it did, the message has to be part of
a multi-message capture process. In that case, the capture buffer will remain
until killed by <code>org-jami-bot--command-function-done</code> (triggered by the <code>!done</code>
command). This function, and the one to initiate such a multi-message capture
and sets up the capture buffer, are defined below:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">org-jami-bot--command-function-start</span> (account conversation msg)
  <span class="org-doc">"Initiate a multi-message capture.

It starts with body in MSG by creating a capture buffer for
CONVERSATION and ACCOUNT.

Further plain text messages processed by
`</span><span class="org-doc"><span class="org-constant">org-jami-bot--capture-plain-messsage</span></span><span class="org-doc">' or files received by
`</span><span class="org-doc"><span class="org-constant">org-jami-bot--capture-file</span></span><span class="org-doc">' will be added to this capture
buffer. The actual capture needs to happen through a separate
function, e.g. `</span><span class="org-doc"><span class="org-constant">org-jami-bot--command-function-done</span></span><span class="org-doc">'. Return a
reply string informing correspondent about how to finish capture
by sending '</span><span class="org-doc"><span class="org-constant">!done</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">let*</span> ((buf (format <span class="org-string">"*jami-capture-%s-%s*"</span> account conversation))
         (body (cadr (assoc-string <span class="org-string">"body"</span> msg)))
         (lines (string-lines body))
         <span class="org-comment-delimiter">;; </span><span class="org-comment">use inactive timestamps
</span>         (timefmt (concat <span class="org-string">"["</span> (substring (cdr org-time-stamp-formats) 1 -1) <span class="org-string">"]"</span>)))
    (<span class="org-keyword">with-current-buffer</span> (get-buffer-create buf)
      (insert (<span class="org-keyword">if</span> (string-empty-p buf)
                  (format <span class="org-string">"* Multi-message note capture %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n"</span>
                          (format-time-string timefmt) (format-time-string timefmt))
                (format <span class="org-string">"* %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n%s"</span>
                        (car lines) (format-time-string timefmt) (string-join (cdr lines) <span class="org-string">"\n"</span>))))
      <span class="org-string">"Multi-message capture started. Finish capture with \"!done\""</span>)))

(<span class="org-keyword">defun</span> <span class="org-function-name">org-jami-bot--command-function-done</span> (account conversation msg)
  <span class="org-doc">"Finish multi-message capture and return a confirmation string.

        Requires a capture buffer set up for CONVERSATION and
        ACCOUNT, for example through
        `</span><span class="org-doc"><span class="org-constant">org-jami-bot--command-function-start</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">let*</span> ((buf (format <span class="org-string">"*jami-capture-%s-%s*"</span> account conversation))
         (continue (get-buffer buf))
         (body (cadr (assoc-string <span class="org-string">"body"</span> msg))))
    (<span class="org-keyword">if</span> continue
        (<span class="org-keyword">with-current-buffer</span> (get-buffer-create buf)
          (<span class="org-keyword">if</span> (<span class="org-keyword">and</span> (org-capture-string
                    (buffer-string)
                    org-jami-bot-capture-key)
                   (kill-buffer buf))
              <span class="org-string">"capture finished!"</span>
            <span class="org-string">"error during org-capture :("</span>))
      <span class="org-string">"No capture to finish. Start multi-message capture with \"!start\""</span>)))
</code></pre>
</div>
<p>
Note that the command functions do not need to actually <i>send</i> the reply
message: this is done by the <code>jami-bot</code> function that will perform the message
processing and calls above functions.
</p>


<p>
Very similarly, we can also capture file transfers. The actual download is
handled by <code>jami-bot</code> already, so we only need to capture a link and add that to
<code>org-stored-links</code>:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">org-jami-bot--capture-file</span> (account conversation msg dlname)
  <span class="org-doc">"Capture downloaded file and reply to original message.

DLNAME specifies local file name downloaded from MSG in
CONVERSATION for jami ACCOUNT."</span>
  (<span class="org-keyword">let*</span> ((buf (format <span class="org-string">"*jami-capture-%s-%s*"</span> account conversation))
         (continue (get-buffer buf))
         (displayname (cadr (assoc-string <span class="org-string">"displayName"</span> msg)))
         <span class="org-comment-delimiter">;; </span><span class="org-comment">use inactive timestamps
</span>         (timefmt (concat <span class="org-string">"["</span> (substring (cdr org-time-stamp-formats) 1 -1) <span class="org-string">"]"</span>)))
    (<span class="org-keyword">with-current-buffer</span> (get-buffer-create buf)
      (insert (<span class="org-keyword">if</span> continue
                  <span class="org-comment-delimiter">;; </span><span class="org-comment">multi message capture
</span>                  (concat
                   <span class="org-string">"#+ATTR_ORG: :width 400\n"</span>
                   (org-link-make-string (file-relative-name dlname)) <span class="org-string">"\n"</span>)
                <span class="org-comment-delimiter">;; </span><span class="org-comment">single message capture
</span>                (format <span class="org-string">"* FILE %s :FILE:\n:PROPERTIES:\n:CREATED: %s\n:END:\n\n#+ATTR_ORG: :width 400\n%s\n"</span>
                        (org-link-make-string (file-relative-name dlname) displayname)
                        (format-time-string timefmt)
                        (org-link-make-string (file-relative-name dlname)))))
      <span class="org-comment-delimiter">;; </span><span class="org-comment">store link for easy linking
</span>      (<span class="org-keyword">push</span> (list dlname displayname) org-stored-links)
      (jami-bot-reply-to-message
       account
       conversation
       msg
       (<span class="org-keyword">if</span> continue
           <span class="org-string">"file added. Finish capture with \"!done\""</span>
         (<span class="org-keyword">if</span> (<span class="org-keyword">and</span> (org-capture-string
                   (buffer-string)
                   org-jami-bot-capture-key)
                  (kill-buffer buf))
             <span class="org-string">"captured!"</span>
           <span class="org-string">"error during org-capture :("</span>))))))
</code></pre>
</div>
</div>
<div id="outline-container-org28369c3" class="outline-3">
<h3 id="org28369c3">Special purpose commands</h3>
<div class="outline-text-3" id="text-org28369c3">
<p>
While the above command are rather generic, I also have written some that are
more tailored to my workflow. Below I define a function that is mapped to the
<code>!today</code> command and captures the messages as a todo entry that is scheduled for
today.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">org-jami-bot--command-function-today</span> (account conversation msg)
  <span class="org-doc">"Capture body of message as todo entry scheduled today.

 Returns a reply string as confirmation. MSG is the full message
 in CONVERSATION id for ACCOUNT id."</span>
  (<span class="org-keyword">let*</span> ((body (cadr (assoc-string <span class="org-string">"body"</span> msg)))
         (lines (string-lines body))
         <span class="org-comment-delimiter">;; </span><span class="org-comment">use inactive timestamps
</span>         (timefmt (concat <span class="org-string">"["</span> (substring (cdr org-time-stamp-formats) 1 -1) <span class="org-string">"]"</span>)))

          (<span class="org-keyword">if</span> (org-capture-string
               (format <span class="org-string">"* TODO %s\nSCHEDULED: %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n%s"</span>
                       (car lines)
                       (format-time-string (car org-time-stamp-formats))
                       (format-time-string timefmt)
                       (string-join (cdr lines) <span class="org-string">"\n"</span>))
            org-jami-bot-capture-key)
           <span class="org-string">"captured and scheduled!"</span>
        <span class="org-string">"error during org-capture :("</span>)))
</code></pre>
</div>

<p>
Sometimes, I remember something that I have to do tomorrow or some other day in
the future. In that case, I can use the <code>!schedule</code> command which does some
additional parsing on the received message: it takes the first string in a
message immediately following command as a date e.g. &ldquo;2023-03-19&rdquo; or &ldquo;monday&rdquo;
and schedules a entry consisting of the following lines on that particular date.
The date string is parsed through <code>org-read-date</code> and supports the same syntax.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">org-jami-bot--command-function-schedule</span> (account conversation msg)
  <span class="org-doc">"Capture body as todo entry and schedule it on the date given after the command.

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 `</span><span class="org-doc"><span class="org-constant">org-read-date</span></span><span class="org-doc">' and supports the same
string-to-date conversations. Returns a reply string as
confirmation. ACCOUNT and CONVERSATION are not used."</span>
  (<span class="org-keyword">let*</span> ((body (cadr (assoc-string <span class="org-string">"body"</span> msg)))
         (lines (string-lines body))
         (swhen (org-read-date nil nil (car lines)))
         <span class="org-comment-delimiter">;; </span><span class="org-comment">inactive timestamp
</span>         (timefmt (concat <span class="org-string">"["</span> (substring (cdr org-time-stamp-formats) 1 -1) <span class="org-string">"]"</span>)))
    (<span class="org-keyword">if</span> (org-capture-string
         (format <span class="org-string">"* TODO %s\nSCHEDULED: %s\n:PROPERTIES:\n:CREATED: %s\n:END:\n%s"</span>
                 (cadr lines)
                 swhen
                 (format-time-string timefmt)
                 (string-join (cdr lines) <span class="org-string">"\n"</span>))
            org-jami-bot-capture-key)
           (format <span class="org-string">"captured and scheduled on %s!"</span> swhen)
        <span class="org-string">"error during org-capture :("</span>)))
</code></pre>
</div>
</div>
</div>
</section>
<section id="outline-container-orgda1554c" class="outline-2">
<h2 id="orgda1554c">Where next?</h2>
<div class="outline-text-2" id="text-orgda1554c">
<p>
Having a Jami bot running in Emacs has already helped me to dump notes, images
and references into my second brain when my phone was the only digital device at
hand.
</p>

<p>
I am also using <a href="https://syncthing.net/">syncthing</a> and
<a href="https://orgzly.com/">Orgzly</a> on my mobile devices. However, sending a quick
message <code>!today buy oat milk</code> or some such feels a lot more natural and quicker
than first navigating to the right place, setting a date before even entering
what I was thinking of.
</p>

<p>
And for such a simple todo entry this might be mostly a matter of taste.
However, I will soon follow up with a post on how to use <code>org-capture-ref</code> to
capture an URL &ndash; including bibliographic information in BibTeX format and tags.
This has been a great way for me to reduce the number of open tabs on my phone
and finally transfer those interesting links and articles into my notes without
too much effort or yet another app.
</p>

<p>
Automatic archiving of web articles, text recognition in images or maybe even
speech recognition from audio recordings &ndash; <a href="mailto:hanno@hoowl.se">let me know
what you come up with</a> 😺
</p>
</div>
</section>
]]>
</description></item>
<item>
<title>Getting started writing interactive fiction with Inform7 on the command line</title>
<link>https://www.hoowl.se/getting_started_writing_interactive_fiction_with_inform7.html</link>
<pubDate>Sat, 30 Apr 2022 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/getting_started_writing_interactive_fiction_with_inform7.html</guid>
<description>
<![CDATA[<p>
Although I have fond memories of <a href="https://en.wikipedia.org/wiki/Hexuma">playing the text adventure Hexuma</a> (and some
similar titles) in the 90s, I only started to get interested in interactive
fiction as a story-telling medium a little while ago. I really enjoy short
stories and the way they create entire worlds for the purpose of a short-lived,
pointed narrative. And interactive fiction can add a whole explorative dimension
to story telling. At least in principle, the barrier of entry for new IF authors
is relatively low, at least compared to many other types of games.
</p>

<p>
There exist a large variety of languages to create IF in and the choice of
language will influence what type of story or interactivity what can hope to
achieve. <a href="http://inform7.com/index.html">Inform</a> is one of the more flexible ones but also intriguing as it draws
from ideas in literal programming and linguistics.
</p>

<p>
As <a href="https://intfiction.org/t/inform-7-v10-1-0-is-now-open-source/55674">Inform has recently been released as free and open software</a>, I was eager to
try it out on my <a href="https://mntre.com/media/reform_md/2020-05-08-the-much-more-personal-computer.html">reform2 laptop</a> which runs Debian GNU/Linux on an NXP i.MX8MQ
processor with 4x ARM Cortex-A53 cores. To my pleasant surprise, it worked right
out of the box!
</p>

<p>
The instructions below are for Inform version 10.1.0 released on April 28th 2022
and are using the command-line interface (CLI) only. If that is not your cup of
tea, there are also various graphical development environments out there, for
example <a href="https://github.com/ptomato/inform7-ide">inform7-ide</a>.
</p>
<section id="outline-container-org20ab379" class="outline-2">
<h2 id="org20ab379">Installing Inform</h2>
<div class="outline-text-2" id="text-org20ab379">
<p>
The instructions below are the summary of what is provided on <a href="https://github.com/ganelson/inform">Inform&rsquo;s git
repository pages</a>. You will likely need the typical prerequisites for compiling
code packaged for your flavor of distribution, e.g. <code>build-essential</code> on
Debian-based systems<sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup>. On my
system, I already had everything installed that Inform needed. If you run into
errors during the build process, you might have to look into missing
dependencies.
</p>

<p>
To build Inform, we also need Inweb and Intest. Both are installed in similar
fashion:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr">1: </span>git clone https://github.com/ganelson/inweb.git
<span class="linenr">2: </span>bash inweb/scripts/first.sh linux
<span id="coderef-inweb" class="coderef-off"><span class="linenr">3: </span>inweb/Tangled/inweb -help</span>
<span class="linenr">4: </span>git clone https://github.com/ganelson/intest.git
<span class="linenr">5: </span>bash intest/scripts/first.sh
</code></pre>
</div>
<p>
The call to <code>inweb</code> on line <a href="#coderef-inweb" class="coderef" onmouseover="CodeHighlightOn(this, 'coderef-inweb');" onmouseout="CodeHighlightOff(this, 'coderef-inweb');">3</a> is just to test the compilation &ndash; if everything worked, you should see the help message of the Inweb CLI.
</p>

<p>
Once these two are in place, we can install Inform itself:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="linenr">1: </span>git clone https://github.com/ganelson/inform.git
<span class="linenr">2: </span><span class="org-builtin">cd</span> inform/
<span class="linenr">3: </span>bash scripts/first.sh
<span class="linenr">4: </span>inblorb/Tangled/inblorb -help
<span class="linenr">5: </span>../intest/Tangled/intest inform7 -show Acidity
</code></pre>
</div>

<p>
Again, the last lines are only for testing the installation. If everything went
smoothly, we are ready to create our first project!
</p>
</div>
</section>
<section id="outline-container-org862af82" class="outline-2">
<h2 id="org862af82">The Story&rsquo;s source code</h2>
<div class="outline-text-2" id="text-org862af82">
<p>
Let&rsquo;s use one of the examples from the Inform documentation: <a href="http://inform7.com/book/WI_17_18.html#e298">A pot of petunias</a>!
</p>
<div class="org-src-container">
<pre class="src src-text"><code>"Pot of Petunias"

Wide Open Field is a room. "A big field under a big sky. The clouds are puffy, the trees are handsome."

Some clouds and some trees are scenery in Wide Open Field. The description of the clouds is "That one looks like Yoda's head." The description of the trees is "You've never been much good at botany, so it's anyone's guess what kind they are."

A rock is in Wide Open Field. The description of the rock is "It looks like it's been here from the dawn of time."

The broken flower pot is a thing. The description of the broken flower pot is "It contains the remains of some abused petunias."

At 9:01 am:
    move the broken flower pot to the location;
    say "Quite unexpectedly, a flower pot falls from the sky and breaks open on the ground. Good thing you weren't standing six inches to the left.";
    set pronouns from the broken flower pot.

Test me with "x it / x it / x it".
</code></pre>
</div>

<p>
Inform uses <i>projects</i> to store each story, its resources and build files in. In
the following, I create the simple directory structure for the first project and
set up some environmental variables to make it easier to adjust paths and such:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-variable-name">GAME_PROJECT</span>=<span class="org-string">"petunias"</span>
<span class="org-variable-name">GAME_PROJECT_DIR</span>=<span class="org-string">"$HOME/src/$GAME_PROJECT"</span>

mkdir -p <span class="org-string">"$GAME_PROJECT_DIR/Source"</span>
</code></pre>
</div>

<p>
This creates a directory <code>~/src/petunias</code> with a subfolder <code>Source</code>. Save the above story into a file <code>story.ni</code> in the <code>Source</code> folder.
</p>

<div class="notes" id="org1e2b285">
<p>
<i>Please note</i>: If you want to use the project with a graphical user interface such as <code>inform7-ide</code> you should choose a project folder ending in <code>.inform</code>, for example <code>petunias.inform</code>, as this is the typical format expected by Inform IDEs. Inform itself, however, does not care.
</p>

</div>

<p>
As a last detail, each project should have a unique identifier. Create one using
the <code>uuid</code> utility<sup><a id="fnr.2" class="footref" href="#fn.2" role="doc-backlink">2</a></sup> (or use a python one-liner<sup><a id="fnr.3" class="footref" href="#fn.3" role="doc-backlink">3</a></sup>) and store it in
<code>~/src/petunias/uuid.txt</code>:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-variable-name">UUID</span>=<span class="org-string">"$(</span><span class="org-sh-quoted-exec">uuid</span><span class="org-string">)"</span>
<span class="org-builtin">echo</span> -n $<span class="org-variable-name">UUID</span> &gt; <span class="org-string">"$GAME_PROJECT_DIR/uuid.txt"</span>
<span class="org-builtin">echo</span> $<span class="org-variable-name">UUID</span>
</code></pre>
</div>

<pre class="example">

03f452f8-c955-11ec-bd44-0019b808dd97
</pre>

<p>
When editing manually, be sure that the <code>uuid.txt</code> file does not end in a line break or some parsers will break when reading the file!
</p>
</div>
</section>
<section id="outline-container-orge5ee416" class="outline-2">
<h2 id="orge5ee416">Compiling our story</h2>
<div class="outline-text-2" id="text-orge5ee416">
<p>
Now we are ready to compile our story using Inform. The compilation will in fact
consist of two steps: first, <code>inform7</code> will translate the story&rsquo;s source into
Inform6 code. Then <code>inform6</code> will be used to compile into one of the standard
virtual machine codes used in interactive fiction: <a href="https://en.wikipedia.org/wiki/Glulx">Glulx</a> or <a href="https://en.wikipedia.org/wiki/Z-machine">Z-machine</a>. The
choice depends on which interpreter you (or your players) want to use.
</p>

<p>
This post is rather brief; a lot more details can be found in <a href="https://intfiction.org/t/command-line-inform-7-how-to-use-ni-inform6-and-cblorb-by-cli/50108">this very thorough
description of the process on the interactive fiction forum</a>. Despite some of the
information there being slightly outdated, you will learn a lot about the
different formats and options of the process reading it!
</p>

<p>
For convenience and slightly shorter commands, I define a variable to keep the
path to the Inform source code:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-variable-name">ISRC</span>=<span class="org-string">"$HOME/src/inform"</span>
</code></pre>
</div>


<p>
Now compile the project with <code>inform7</code>:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-string">"$ISRC/inform7/Tangled/inform7"</span> -no-progress -external <span class="org-string">"$HOME/Inform"</span> -project <span class="org-string">"$GAME_PROJECT_DIR/"</span>
</code></pre>
</div>

<pre class="example">
Inform 7 v10.1.0 has started.
I've now read your source text, which is 170 words long.
I've also read Basic Inform by Graham Nelson, which is 7691 words long.
I've also read English Language by Graham Nelson, which is 2328 words long.
I've also read Standard Rules by Graham Nelson, which is 32130 words long.

  The 170-word source text has successfully been translated. There were 1 room
    and 5 things.
Inform 7 has finished.
</pre>

<p>
The <code>-external</code> switch is optional. The directory specified here is the default
for additional external extensions and is it being created automatically when
running Inform on a project (as we just did). If you&rsquo;d rather see extensions
stored elsewhere, simply adjust that path.
</p>

<p>
For a release version, add the <code>-release</code> switch. You can also add <code>-debug</code> for
additional debugging features. For a full list of CLI options, run
</p>

<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-string">"$ISRC/inform7/Tangled/inform7"</span> --help
</code></pre>
</div>

<p>
Check out the project&rsquo;s folder: <code>inform7</code> has just generated a bunch of files and folders.
</p>

<p>
Next step is the compilation of the just generated Inform6 code to (gl)ulx:
</p>
<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-string">"$ISRC/inform6/Tangled/inform6"</span> -E2wSDG <span class="org-string">"$GAME_PROJECT_DIR/Build/auto.inf"</span> <span class="org-string">"$GAME_PROJECT_DIR/Build/output.ulx"</span>
</code></pre>
</div>

<pre class="example" id="org66fd477">
Inform 6.36 for Linux (24th January 2022)
In:  1 source code files             94387 syntactic lines
 81099 textual lines               2394870 characters (ISO 8859-1 Latin1)
Allocated:
  8818 symbols                     2885088 bytes of memory
Out:   Glulx story file 1.220501 (665K long):
    20 classes                          42 objects
   232 global vars                   86156 variable/array space
    96 verbs                           322 dictionary entries
   179 grammar lines (version 2)       251 grammar tokens (unlimited)
   101 actions                          37 attributes (maximum 56)
    39 common props (maximum 253)       18 individual props (unlimited)
133667 characters used in text      104309 bytes compressed (rate 0.780)
     0 abbreviations (maximum 64)     3230 routines (unlimited)
 81298 instructions of code          45940 sequence points
110080 bytes writable memory used   570368 bytes read-only memory used
680448 bytes used in machine    1073061376 bytes free in machine
Compiled with 3236 suppressed warnings
Completed in 2.00 seconds
</pre>

<p>
The format is specified through the command line switches <code>-E2wSDG</code>; Change
these to <code>-E2w~S~DG</code> for release version where debugging and strict testing is
explicitly disabled (through the <code>~</code> character). Replacing <code>G</code> with <code>v8</code> would
compile to z8 instead for glulx. For a full list of options, run <code>inform6</code> with
<code>--help</code> or <code>-h2</code> for general help or a compilation switch overview,
respectively.
</p>

<p>
This created the file <code>$GAME_PROJECT_DIR/Build/output.ulx</code>. The next and final
step is to package everything up into a standard IF game file.
</p>
</div>
</section>
<section id="outline-container-orga813a4c" class="outline-2">
<h2 id="orga813a4c">Packaging the game</h2>
<div class="outline-text-2" id="text-orga813a4c">
<p>
Now it is time to combine the compiled game, meta information and potential media assets
into one game file that can be easily distributed. The process is controlled by
the file <code>$GAME_PROJECT_DIR/Release.blurb</code> and performed by <a href="https://ganelson.github.io/inform/inblorb/M-ui.html">inblorb</a> which is
part of the inform7 installation.
</p>

<div class="org-src-container">
<pre class="src src-bash"><code><span class="org-string">"$ISRC/inblorb/Tangled/inblorb"</span> <span class="org-string">"$GAME_PROJECT_DIR/Release.blurb"</span> <span class="org-string">"$GAME_PROJECT_DIR/Build/output.gblorb"</span>
cp <span class="org-string">"$GAME_PROJECT_DIR/Build/output.gblorb"</span> <span class="org-string">"$GAME_PROJECT_DIR/Release/Pot of Petunias.gblorb"</span>
</code></pre>
</div>

<pre class="example">
! inblorb 4 [executing on Sunday 1 May 2022 at 16:11.45]
! The blorb spell (safely protect a small object as though in a strong box).
Copy blorb to: [[/home/hanno/src/petunias/Release/Pot of Petunias.gblorb]]
! Completed: wrote blorb file with 1 picture(s), 0 sound(s)
</pre>
</div>
</section>
<section id="outline-container-org6bf0344" class="outline-2">
<h2 id="org6bf0344">Playing the game!</h2>
<div class="outline-text-2" id="text-org6bf0344">
<p>
Now it is finally time to play the game using your favorite interpreter! For example,
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>glulxe <span class="org-string">"$GAME_PROJECT_DIR/Release/Pot of Petunias.gblorb"</span>
</code></pre>
</div>

<pre class="example" id="org5333a72">
 Wide Open Field
──────────────────────────────────────────────────────


Pot of Petunias
An Interactive Fiction
Release 1 / Serial number 220501 / Inform 7 v10.1.0 / D

Wide Open Field
A big field under a big sky. The clouds are puffy, the trees are handsome.

You can see a rock here.

&gt;
</pre>

<p>
Have fun and happy hacking!
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/if.html">if</a>, <a href="https://www.hoowl.se/../tags/inform7.html">inform7</a></p>
</div>
</section>
<div id="footnotes">
<h2 class="footnotes">Footnotes: </h2>
<div id="text-footnotes">

<div class="footdef"><sup><a id="fn.1" class="footnum" href="#fnr.1" role="doc-backlink">1</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Install via <code>sudo apt install build-essential</code></p></div></div>

<div class="footdef"><sup><a id="fn.2" class="footnum" href="#fnr.2" role="doc-backlink">2</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">If not installed, install via <code>sudo apt install uuid</code></p></div></div>

<div class="footdef"><sup><a id="fn.3" class="footnum" href="#fnr.3" role="doc-backlink">3</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Using the build-in <code>uuid</code> library as suggested by <i>libele</i> (thanks!): <code>python3 -c "import uuid;print(str(uuid.uuid4()).upper())"</code></p></div></div>


</div>
</div>]]>
</description></item>
<item>
<title>Managing calendar events in Emacs</title>
<link>https://www.hoowl.se/khalel.html</link>
<pubDate>Fri, 17 Sep 2021 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/khalel.html</guid>
<description>
<![CDATA[<p>
Whether at home or at work, much of my usual work flow revolves around my Emacs
setup (and many, <i>many</i> open browser tabs). Until recently, I also switched
occasionally to Thunderbird for my emails &ndash; but now, I mostly use <code>mu4e</code> to
interact even with mail within Emacs. Some rough edges in the configuration
aside, I am really loving the tighter integration with my org-mode notes and my
familiar key chords.
</p>

<p>
However, I have been missing the other functionality that I used Thunderbird
for, namely accessing CalDAV calendars and a CardDAV address book. So why not
integrate those in Emacs as well? Today, I will start with listing, editing and
creating calendar events from within Emacs!
</p>
<section id="outline-container-org4f511b4" class="outline-2">
<h2 id="org4f511b4">Where to start</h2>
<div class="outline-text-2" id="text-org4f511b4">
<p>
Whenever one has a problem to solve, someone else has probably already
undertaken at least the first steps. Which is nice, as it turns out that getting
calendar event handling right is not so easy.
</p>

<p>
There is <a href="https://github.com/dengste/org-caldav">org-caldav</a> which seems to fit the bill perfectly: it offers direct
CalDAV sync for org-mode. As the synchronization is handled within org-caldav
itself, it needs to deal with conflict handling, deletions and storage of sync
information internally. I was worried that any misconfiguration or user mistake
could lead to loss of data (and missed meetings) and instead opted to set up a
tool chain composed of specialized tools for each step.
</p>
</div>
</section>
<section id="outline-container-org0918e2d" class="outline-2">
<h2 id="org0918e2d">Building on nifty tools the Unix way</h2>
<div class="outline-text-2" id="text-org0918e2d">
<p>
The first building block is <a href="https://github.com/pimutils/vdirsyncer">vdirsyncer</a>, a command-line tool capable of
synchronizing calendars and address books between a variety of servers and the
local file system. It stores calendars locally in directories where each event
is recorded in a single <code>.ics</code> file. These are human-readable plain text files
containing all fields and their data. Interaction with the remote server only
happens when triggered by running <code>vdirsyncer</code>. This makes it easy to track
changes or make backups and safe to experiment with.
</p>

<p>
To interact with the local calendar and parse the data therein, another tool
comes in handy: <a href="https://khal.readthedocs.io/en/latest/">khal</a>. This Python-based command allows to list, edit and create
events either directly from the command-line or via an interactive text-based
interface. <code>khal</code> is somewhat limited in what it can handle (in terms of
supported fields in calendar events, for example), but for my purposes of
managing one shared and one personal calendar it was ideal.
</p>

<p>
Adjusting the default upcoming event listing of <code>khal</code> slightly, one can
quite easily get something that already resembles the org-mode syntax:
</p>

<div class="org-src-container">
<pre class="src src-bash"><code>khal list --format <span class="org-string">"* {title} \n &lt;{start-date} {start-time}-{end-time}&gt; \n {location} \n {description}"</span> --day-format <span class="org-string">""</span> today 10d
</code></pre>
</div>

<p>
The output above shows the events in the coming 10 days from now. This
gives us something to work with and means we don&rsquo;t have to touch the <code>.ics</code>
files directly.
</p>

<p>
Last but not least, org-mode comes with export functions to <code>ics</code> with many
configurable options: <code>org-icalendar-export-to-ics</code>. This makes it possible to
create new events from org-mode, export to <code>ics</code> and import to the local
calendar store using <code>khal import</code>.
</p>

<p>
So I started playing with elisp to tie these components together and out came my
very first Emacs package: <code>khalel</code>!
</p>
</div>
</section>
<section id="outline-container-orgca03492" class="outline-2">
<h2 id="orgca03492"><code>khalel</code></h2>
<div class="outline-text-2" id="text-orgca03492">
<p>
<code>khalel</code> provides a relatively thin wrapper around the above mentioned tools
allowing to list, edit and create calendar events from within Emacs: Once
installed, you can import upcoming events through <code>M-x
khalel-import-upcoming-events</code> into an org-mode file, edit individual events
with <code>M-x khalel-edit-calendar-event</code> or create new ones by starting org-capture
(<code>C-c c</code> or <code>M-x org-capture</code>) and pressing <code>e</code> (default key) for a new calendar
event. To synchronize with remote calendars simply call <code>M-x
khalel-run-vdirsyncer</code>.
</p>

<p>
Along the way I discovered how easy org-mode makes it to wrap elisp code: If you
visit the org file with the imported events, you will notice links below each
event. Using these you can directly edit the corresponding calendar entry
with a single click or trigger a synchronization and import. Sweet! :)
</p>

<p>
This is how the resulting org-mode agenda and the calendar file with the
imported events looks like:
<img src="https://www.hoowl.se/./../images/posts/emacs/210917_khalel_screenshot.png" alt="210917_khalel_screenshot.png" />
</p>

<p>
Now I have have all my events at hand and where they belong: in the org agenda :)
</p>

<p>
If you want to give the (rather young) code a shot, you can find it here:
<a href="https://gitlab.com/hperrey/khalel">https://gitlab.com/hperrey/khalel</a>. The code has also been submitted to <code>melpa</code>
and awaits a review (which I am quite excited about!).
</p>

<p>
In the <code>khalel</code> repository you will also find more details on the configuration
of <code>vdirsyncer</code>, <code>khal</code> and <code>khalel</code> as well as on the inherent limitations of
this approach. If you do try this out, please share your experience with me! :)
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/pim.html">pim</a>, <a href="https://www.hoowl.se/../tags/programming.html">programming</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a>, <a href="https://www.hoowl.se/../tags/org.html">org</a></p>
</div>
</section>
]]>
</description></item>
<item>
<title>Confessions of a data sink</title>
<link>https://www.hoowl.se/confessions_of_a_data_sink.html</link>
<pubDate>Sun, 31 Jan 2021 00:00:00 +0100</pubDate>
<guid>https://www.hoowl.se/confessions_of_a_data_sink.html</guid>
<description>
<![CDATA[<p>
I have had many, <i>many</i> accounts during the 25 years that I have spent online.
Each had something of an identity connected to it but they all were shallow.
Whether it was an account on a BBS somewhere (that wasn&rsquo;t too expensive to dial
into), ICQ, slashdot &ndash; yes, even facebook &ndash; or whatever other online space I
was hanging out at at the time: they never had my name or any persistent nick
connected with them and only few people knew it was me &ndash; if any. Even though I
eagerly read through discussions and followed up on new posts in the hope that
they were offering up an argument I had been missing, I very rarely actually
posted anything myself.
</p>

<p>
In fact, I never created the accounts in order to post. I just wanted to observe
and maybe have the option to say something. Which I only did when I felt certain
and confident, for example on a solution to a technical problem. I would write
those posts with utmost care, checking every link and double-checking any code
fragment. And then, after posting, I would nervously watch for any reactions and
be all excited when one actually came in.
</p>

<p>
In other words, I had been a (nearly) anonymous data sink all these years.
</p>

<p>
This is probably not a surprise, as I am more of an introvert, and I don&rsquo;t think
that there is anything wrong with being one, or a data sink for that matter. But
recently, I have been wondering more <i>why</i> I had never been comfortable online.
I do enjoy discussions, I have published in writing before, and I had enjoyed
speaking publicly on various occasions. However, there is something special for
me about writing online.
</p>

<p>
For one, there is a low barrier-of-entry. While that is obviously part of the
magic of the cyberspace, I do get intimidated by potentially sending out a
half-finished sentence to all the world with just one misguided click or press
of the button. That anxiety can even freak me out when sending messages to my
personal friends, so it&rsquo;s certainly a concern with any public interaction.
<i>Press-enter-to-send</i> is always the first &ldquo;feature&rdquo; I disable in any chat client.
</p>

<p>
Then there is the permanence of anything said online. Which is probably &ndash; to an
extend &ndash; overrated and <a href="https://xkcd.com/137/">one should not dampen one&rsquo;s creative impulses simply to
better fit into a hypothetical future</a>. One learns by making mistakes (and
correcting them). But an odd feeling is not always easy for me to shake off when
it comes to any personal, digital traces I am about to leave.
</p>

<p>
Finally, I get anxious without the social and communicative clues that would
come with direct conversations. What sparks interest or bores usually becomes
quickly apparent if you are talking/texting with a single person or a small
group. I really appreciated those early direct modem connections with
schoolmates, first established just as a test, but then used for hours to talk
to each other through the keyboard. You could see each letter as it was being
typed which allowed for a rather personal conversation through pure text.
</p>

<p>
Fast forwarding to the vast online spaces of today, and I don&rsquo;t quite know who I
am talking with. Maybe I get an idea of the community before posting, and maybe
I reply to someone specific. But you never know who is going to respond. I
rarely feel confident enough in the substance of my contribution to engage in a
conversation &ndash; but this is to a large degree due to the <b>certainty</b> that there
is someone out there who is more knowledgeable on any given topic than I am.
</p>

<p>
While in personal conversations I could gauge the room, judge people&rsquo;s interest
and eagerness to communicate their own experiences and knowledge, these clues
are missing online (or are harder to interpret in any case). And the audience is
potentially much bigger and driven by entirely different motivations. This
intimidates me and makes me uneasy to articulate even firmly held beliefs.
Leaving the space always seems like the easier option. And why not, when nothing
connects me personally.
</p>

<p>
Well, I have made the conscious decision to show my face publicly and to stand
by my quirks. Having spaces that suit me personally and resonate with my
(digital) identity, such as this blog or platforms such as <a href="https://fosstodon.org">fosstodon.org</a> help
tremendously. And accepting that I do most of my writing for myself alone, or at
best as an offer to the random traveller passing by, is the underlying
assumption behind anything you will find here.
</p>

<p>
If you have come this far by reading the above lines, then I would really like
to know who you are and why you felt this was of interest to you. Please drop me
a line &ndash; or don&rsquo;t and simply move on in your own journey :)
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/thoughts.html">thoughts</a>, <a href="https://www.hoowl.se/../tags/mastodon.html">mastodon</a>, <a href="https://www.hoowl.se/../tags/blog.html">blog</a></p>
]]>
</description></item>
<item>
<title>The Day I Started Worrying and Deleted Google</title>
<link>https://www.hoowl.se/the_day_I_started_worrying_and_deleted_google.html</link>
<pubDate>Sun, 13 Sep 2020 00:00:00 +0200</pubDate>
<guid>https://www.hoowl.se/the_day_I_started_worrying_and_deleted_google.html</guid>
<description>
<![CDATA[
<div id="org58d52be" class="figure">
<p><img src="https://www.hoowl.se/images/posts/degoogle/delete_account.png" alt="delete_account.png" />
</p>
</div>

<p>
I have had my first Google account since around 2003 when Google introduced GMail and still required you to be invited by an existing user. There had of course been many alternative email services around, and they usually offered some tier that one didn&rsquo;t have to pay money for. But none could compete with Google&rsquo;s offering of near-infinite storage and clean web interface, when most other services had ad-cluttered pages, forced their footer into sent emails and were constantly at risk of refusing to receive new mails due to the few mega-bytes of space being full yet again. That&rsquo;s why Google introduced their new service with the slogan of &ldquo;never having to delete another email&rdquo;.
</p>

<p>
Ironically, Google soon after got into the headlines because its terms of service implied that even deleted mails would never really disappear from their servers. Google quickly clarified that this was purely because of how their systems were set up and retained their backups. However, not wanting to let go of any shred of their users&rsquo; data still <b>is</b> a characteristic of their business model. For a company that makes <b>a lot</b> of money as a gatekeeper to this data, this attitude should be hardly surprising. I think it is fair to say that Google&rsquo;s customers are in fact those that pay to have ads delivered to specific user demographics and Google&rsquo;s services have the function to keep the latter attentive, accessible and transparent.
</p>

<p>
I appreciated my GMail account for being rather reliable and low-maintenance. And Google kept on adding more services, from calendar to file storage. However, for me personally, this central role that the now Google <i>account</i> is supposed to take within Google&rsquo;s increasingly entangled ecosystem is what alienates me most.
</p>

<p>
My emails are already a very sensitive and telling part of my personal data: they expose whom I have contact with, what I order and from where, and when and how often I read my mails. It requires some level of trust to keep with them on anyone&rsquo;s computer but mine &ndash; but as mentioned above, Google is into storing all details for as long as possible. Now add a calendar. Add storage and editing capabilities for documents. Add picture upload. Add video viewing history. And your every route tracked when using navigation services. This, combined with the meta-information of when these are accessed, from where and with whom they are shared, creates a thickly-woven picture of one&rsquo;s identity, personality and behavior<sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup>. And that doesn&rsquo;t even get to the more invasive offerings such as &ldquo;Google Assistant&rdquo;.
</p>

<p>
Of course, one doesn&rsquo;t have to use these services, and I (mostly) chose not to. But sometimes, there are no alternatives: while I created my GMail account voluntarily, I was pretty much forced into a Google-account when setting up my first non-Nokia smartphone. The typical operating system on modern devices such as tablets or smartphones is simply not designed to give the user the final word on what data is stored, where it is stored and whether or not it may leave the device. Despite some progress in the form of more fine-grained privacy settings, it is still common that these devices are entirely useless without at least a user account and continuous online check-ins<sup><a id="fnr.2" class="footref" href="#fn.2" role="doc-backlink">2</a></sup>. And of course the defaults transfer data freely, usually including WiFi passwords and login credentials in backups.
</p>

<p>
I am not even fundamentally opposed to such features in general. While I consider myself very privacy-conscious, I do believe that everyone should have the freedom to exchange access to their data for access to services &ndash; as long as this happens transparently, leaves control with the user and deletes all data when the user opts out again<sup><a id="fnr.3" class="footref" href="#fn.3" role="doc-backlink">3</a></sup>. Technically, this is difficult to realize, so this process requires trust. And when I don&rsquo;t even see any options to opt out, then I find it very difficult to trust <i>any</i> of the other options offered. Especially when I paid a significant amount for the device I hold in my hands and get increasingly frustrated with because I do not get to use it on my own terms.
</p>

<p>
Under these circumstances, how is one to trust a device that follows one <i>everywhere</i>, connects to every single account under the sun and is equipped with pretty much every sensor available in consumer technology?
</p>

<p>
I know I am not the only one wondering this. The <a href="https://conversationalist.org/2019/09/13/feminism-explains-our-toxic-relationships-with-our-smartphones/">conversationalist</a> framed our relation to our phone as an abusive relationship, and I regularly overhear friends and colleagues mistrusting their devices and going as far as withholding (Google) searches to not have their spontaneous search reflected in their feeds, ads and future search results. But this is a potentially very dangerous slope we are on: if we are already self-censoring when we are considering mundane things like products we might buy, how does it affect our social and democratic discourse? And what alternatives do we have as our access to information and discourse is largely guarded by a few de-facto monopolies<sup><a id="fnr.4" class="footref" href="#fn.4" role="doc-backlink">4</a></sup>?
</p>
<section id="outline-container-orgea3a5a4" class="outline-2">
<h2 id="orgea3a5a4">Step by step towards a self-determined digital life</h2>
<div class="outline-text-2" id="text-orgea3a5a4">
<p>
These questions leave me no peace and are a central reason why I decided to increasingly dismiss Google&rsquo;s offerings. The most difficult and yet most important is the choice of mobile phone: there are a only very few brands that <b>do</b> at least allow or even provide a Google-free operating system<sup><a id="fnr.5" class="footref" href="#fn.5" role="doc-backlink">5</a></sup>. When last making this choice, I did not want the former option that would require unofficial hacks, despite some of these being rather mature all things considered. Luckily, there was and is again a better option for the latter in the shape of the <a href="https://www.fairphone.com/en/">Fairphone 2</a> and now <a href="https://www.fairphone.com/en/2020/04/30/keeping-your-data-safe-with-e-os/">Fairphone 3</a>. I own the former since its first release in 2015/16 and am very fond of it. It runs Fairphone OpenOS which is based on the free and open parts of Android 7<sup><a id="fnr.6" class="footref" href="#fn.6" role="doc-backlink">6</a></sup>. I run selected apps via direct download or through the F-Droid store<sup><a id="fnr.7" class="footref" href="#fn.7" role="doc-backlink">7</a></sup>.
</p>

<p>
For all of the commonly-used Google services, I found replacements that often have one key feature seldom found in Google services: full offline support. This means that I, for example, download maps in <a href="https://osmand.net/">OSMAnd</a> (instead for Google Maps) and can access this anywhere, anytime without any internet connection. Since this usually covers the whole country I am interested in, I don&rsquo;t have to be particularly selective beforehand. And whether I want to save on battery when out in the woods or want to look up an address while having no connection when traveling, OSMAnd is there for me. The same is true for my dictionary, music collection, podcasts, and notes. If you are looking for alternatives, <a href="https://switching.software/">switching.software</a> and <a href="https://www.privacytools.io/">privacytools.io</a> are very good sources.
</p>

<p>
That doesn&rsquo;t mean that I have to manually synchronize anything. <a href="https://syncthing.net">Syncthing</a> runs on my desktop, phone and a Raspberry Pi at home and keeps data and music up-to-date whenever I am home. The Raspberry Pi also hosts my <a href="https://radicale.org/">calendar and contacts</a> and allows me to stream music to my stereo system via Bluetooth or WiFi (instead for whatever that Google thing is called). My browser of choice is <a href="https://www.mozilla.org/en-US/firefox/">Firefox</a> (instead for Google Chrome) and the search engine I usually start with is <a href="https://duckduckgo.com/">DuckDuckGo</a>.
</p>

<p>
As mail host, there are an abundance of services that I trust more than I trust or want to support Google. I both use my own domain and have accounts in techcollectives such as <a href="https://riseup.net">riseup.net</a>, <a href="https://fripost.org/">fripost.org</a> and <a href="https://hcoop.net/">hcoop.net</a>.
</p>

<p>
And today I logged into my Google account for the last time and deleted it.
</p>


<div id="org05496eb" class="figure">
<p><img src="https://www.hoowl.se/images/posts/degoogle/account_deleted.png" alt="account_deleted.png" />
</p>
</div>

<p>
Which, I have to admit, was a very pleasant user experience and went smoothly. Especially when compared to the hoops that Amazon makes one jump through deleting one&rsquo;s account.
</p>
</div>
</section>
<section id="outline-container-org36974da" class="outline-2">
<h2 id="org36974da">Epilog</h2>
<div class="outline-text-2" id="text-org36974da">
<p>
With all this in place, Google is far from being out of my life though: most of my friends store my contact details with Google, ring me from their Google phones, or receive my mails in their GMail accounts. But it doesn&rsquo;t stop there: most websites embed Google-services such as Google Analytics or Google Ads which track me throughout my web journeys<sup><a id="fnr.8" class="footref" href="#fn.8" role="doc-backlink">8</a></sup>. Even government online services use and <i>require</i> Google code to run on my machine when they use reCAPTCHA to &rsquo;make sure I am human&rsquo;.
</p>

<p>
And that is just what is still obvious to see. Google also operates infrastructure that is much less visible but still yields data to them. For example, the university I work at has the students&rsquo; mail accounts hosted on Google&rsquo;s servers. You could not tell this from their mail addresses, however.
</p>

<p>
So while I perceive the step to remove my direct ties with Google and to replace them with free and open software as a significant one to regain self-determined use of my devices, it does not remove Google from my digital life entirely. Google is here to stay and we as a society better find ways to deal with it and other IT monopolists without surrendering ourselves and our freedom.
</p>

<div class="notes" id="org870edb2">
<p>
<b>Important disclaimer</b>: Whether removing Google from your digital life improves or negatively affects your privacy, security and access to services depends entirely on your individual use case and your choices of what to replace Google with. You should choose alternatives with care and after researching possible implications to your privacy and security. Always consider <a href="https://ssd.eff.org/en/module/your-security-plan">your threat model</a> if you are facing particular high personal risks.
</p>

</div>

<p>
<b>Comments?</b> Leave them as reply to <a href="https://fosstodon.org/web/statuses/104859408265623568">the corresponding post on Mastodon</a>.
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/degoogle.html">degoogle</a>, <a href="https://www.hoowl.se/../tags/privacy.html">privacy</a>, <a href="https://www.hoowl.se/../tags/thoughts.html">thoughts</a></p>
</div>
</section>
<div id="footnotes">
<h2 class="footnotes">Footnotes: </h2>
<div id="text-footnotes">

<div class="footdef"><sup><a id="fn.1" class="footnum" href="#fnr.1" role="doc-backlink">1</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">One could use different accounts to separate these, which I have done at times. But I never believed that Google would not be easily able to connect these though similar access patterns and other commonalities.</p></div></div>

<div class="footdef"><sup><a id="fn.2" class="footnum" href="#fnr.2" role="doc-backlink">2</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Microsoft must be mentioned here for managing to make this anti-feature a thing even on regular computers since at least Windows 10. As does Apple on OSX where the hardware is even more strongly coupled to one&rsquo;s user account.</p></div></div>

<div class="footdef"><sup><a id="fn.3" class="footnum" href="#fnr.3" role="doc-backlink">3</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Preferably, this deal would also be not as heavily to the advantage of the service provider as it is currently common practice.</p></div></div>

<div class="footdef"><sup><a id="fn.4" class="footnum" href="#fnr.4" role="doc-backlink">4</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">To name the biggest offenders: Google, Amazon, Apple, Facebook, Microsoft.</p></div></div>

<div class="footdef"><sup><a id="fn.5" class="footnum" href="#fnr.5" role="doc-backlink">5</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Neither Apple nor Microsoft qualify in this context as both, in different ways, impose restrictions on the users and eagerly collect their data.</p></div></div>

<div class="footdef"><sup><a id="fn.6" class="footnum" href="#fnr.6" role="doc-backlink">6</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">It even received two major updates, first being released with Android 5.</p></div></div>

<div class="footdef"><sup><a id="fn.7" class="footnum" href="#fnr.7" role="doc-backlink">7</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">These choices bear their own privacy and security implications. Note the disclaimer at the end of this article.</p></div></div>

<div class="footdef"><sup><a id="fn.8" class="footnum" href="#fnr.8" role="doc-backlink">8</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Browser add-ons such as <a href="https://privacybadger.org/">EFF&rsquo;s Privacy Badger</a>, <a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/">uBlock Origin</a>, <a href="https://addons.mozilla.org/en-US/firefox/addon/cookie-autodelete/">Cookie AutoDelete</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/noscript/">NoScript</a> can make a huge differences here though. Check them out if you don&rsquo;t know them yet!</p></div></div>


</div>
</div>]]>
</description></item>
<item>
<title>Exploring the inter-process messaging bus /D-Bus/ using Elisp</title>
<link>https://www.hoowl.se/exploring_dbus_from_emacs.html</link>
<pubDate>Sun, 16 Apr 2023 22:50:00 +0200</pubDate>
<guid>https://www.hoowl.se/exploring_dbus_from_emacs.html</guid>
<description>
<![CDATA[<p>
As a user, you typically expect your software application to seamlessly interact
with other processes and bits of the local system. For example, you might want
it to immediately react to new USB devices being connected or mute itself on an
incoming (video) call. One pretty neat way of accomplishing this is via <a href="https://www.freedesktop.org/wiki/Software/dbus/">the
inter-process messaging bus D-Bus</a><sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup>.
</p>

<p>
D-Bus has been around since almost two decades and is likely already installed on
your system if you are running a Linux-based OS. Many of the applications and
services running on your computer will be providing various <i>methods</i> and
<i>signals</i> via/ D-Bus that can be used to interact with them. A <i>method</i> can be
called via a D-Bus message and will be answered by the application with a
response. <i>Signals</i>, on the other hand, can be subscribed to, and will be
triggered on given events such as the before-mentioned connection of a new USB
device. A nice feature of D-Bus is that it provides <i>introspection</i>: you can explore any
registered application on the bus and discover its methods and signals <i>from within
D-Bus</i>.
</p>

<p>
In this post, I will give an example on how one can use <code>dbus.el</code>, the Elisp
D-Bus bindings that Emacs ships with, to interact with other local processes.
The application I want control from within Emacs is the distributed, private messenger
<a href="https://jami.net/">GNU Jami</a>.
</p>

<p>
My goal is to create a chat-bot that can trigger actions on commands received
via text-messages and handle received files. Specifically, I want to use Org
mode&rsquo;s <code>org-capture</code> mechanism to store notes, references and action items into
my todo lists. This post, however, will only explore Jami&rsquo;s D-Bus interface to
create an account, add a contact and send and react to messages. If you are not
interested in the low-level mechanics or D-Bus itself, then feel free to skip
head to the <code>jami-bot</code> or read about how to use the bot to capture notes in Org
mode! Otherwise, let&rsquo;s dive into D-Bus &ndash; after making sure we have our tools ready!
</p>
<section id="outline-container-orgc884664" class="outline-2">
<h2 id="orgc884664">Prerequisites</h2>
<div class="outline-text-2" id="text-orgc884664">
<p>
Besides Emacs<sup><a id="fnr.2" class="footref" href="#fn.2" role="doc-backlink">2</a></sup> and a system running D-Bus, we will need to have the Jami daemon,
<code>jamid</code>, installed on the local system. On Debian, this can be done by simply
running <code>sudo apt install jami</code> which will also install the GUI application. The
latter is not strictly necessary but can be more comfortable to use during some
of the steps. The version installed through <code>apt</code>, however, is likely older than
what is provided on the official download pages &ndash; consider updating should you
run into any connectivity issues later.
</p>

<p>
Another useful &ndash; but optional &ndash; tool is <a href="https://wiki.gnome.org/Apps/DFeet">the graphical D-Bus debugger, D-Feet</a>.
If you feel that exploring long lists of D-Bus nodes via Elisp function calls is
a little daunting (or tedious), this tool will allow you to click through to the
information you want very quickly.
</p>
</div>
</section>
<section id="outline-container-org905d11d" class="outline-2">
<h2 id="org905d11d">Getting started with the D-Bus API in Emacs</h2>
<div class="outline-text-2" id="text-org905d11d">
<p>
<a href="https://www.gnu.org/software/emacs/manual/html_mono/dbus.html">Emacs comes with its own D-Bus API</a> which is documented quite well and includes
several examples. This makes it easy to interact with any services on D-Bus
through a simple Elisp function call. But first, the <code>dbus</code> package needs to be
loaded:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">require</span> '<span class="org-constant">dbus</span>)
</code></pre>
</div>

<p>
Now, we can already find out what services are available via D-Bus. Generally,
there are two separate busses to consider: the <code>system</code> bus and the <code>session</code>
bus. Anything started by the user would typically reside on the latter while the
former has services such as network, power management and the like. As Jami is a
service started by the user, it is found on the <code>session</code> bus. <code>dbus-list-names</code>
can list anything registered there:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-list-names <span class="org-builtin">:session</span> )
</code></pre>
</div>

<p>
I needed to shorten the list considerably despite my system running a rather
lightweight desktop environment only. So you might see a considerably longer
list. You find things like my terminal program (<code>org.xfce.Terminal5</code>) or the
currently running Firefox profile (<code>org.mozilla.firefox.ZGVmYXVsdC1lc3I_</code>). What
I am looking for is <code>cx.ring.Ring</code> which is Jami &ndash; the messenger was called
&ldquo;GNU Ring&rdquo; until not so long ago and the API has not been updated yet.
</p>

<p>
Alternatively, you can also check whether or not a service is registered by pinging it:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-ping <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span>)
</code></pre>
</div>

<p>
On my system, this actually has the side effect of starting the Jami daemon
(<code>jamid</code>) even if it was not running before. The daemon is what is running in
the background and handling all interactions with the various network
connections Jami establishes to allow messages, calls and video chats. So it is
essential for using Jami and even the GUI builds upon it. If this command
returns <code>nil</code>, check your Jami installation and that <code>jamid</code> is running.
</p>


<p>
Let us take a closer look into the Jami D-Bus interface. It is structured into a
number of interfaces that correspond to different aspects of the messenger:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-introspect-get-all-nodes <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring"</span>)
</code></pre>
</div>

<p>
The different names indicate what each node does, e.g. handling calls, video or
plugins. This feature of being able to list parts of the API and even &ndash; as we
will see later &ndash; find the arguments expected by methods is called
<i>introspection</i> and a nice feature of D-Bus. It is similar to how Emacs allows
you to explore its functions and variables from within the running software. So
if you, for example, wonder how to use the function
<code>dbus-introspect-get-all-nodes</code>, you can from within Emacs press <code>C-h f</code> to open
the help for functions, write <code>dbus-introspect-get-all-nodes</code> (or use
tab-complete) and press enter to see the signature, a short explanation and the
source code to the function. The former is <code>(dbus-introspect-get-all-nodes BUS
SERVICE PATH)</code>. The session was already explained above. The service consists of
a name separated by dots while the path denotes an object on the D-Bus separated
by slashes like a path on a filesystem. For the examples below, the service is
always <code>cx.ring.Ring</code>.
</p>
</div>
</section>
<section id="outline-container-orgf422f7b" class="outline-2">
<h2 id="orgf422f7b">The anatomy of Jami&rsquo;s D-Bus interface</h2>
<div class="outline-text-2" id="text-orgf422f7b">
<p>
Despite the ability for introspection in D-Bus, it will likely be necessary to
look into the API as defined in the Jami source code. You can find these in the
<a href="https://git.jami.net/savoirfairelinux/jami-daemon/-/blob/dd317b6060bb37730d7e8ae7d18ff3e83fb33216/bin/dbus/cx.ring.Ring.ConfigurationManager.xml">jami-daemon/bin/dbus/cx.ring.Ring.ConfigurationManager.xml</a> file in the Jami source
repository. This document includes helpful documentation for many of the nodes,
i.e. methods and signals. There is only so much one can guess from the node&rsquo;s
name alone. I have unfortunately not found a way to retrieve these additional
docstrings from within D-Bus &ndash; so having the above file open as a reference is
useful for the following exercise.
</p>

<p>
Of the above mentioned interfaces, only the <code>ConfigurationManager</code> is needed in
the beginning. While it would be cool to automatically answer calls and e..g
record and transcribe them, I have not yet had the time to dig into the
<code>CallManager</code> sufficiently. But the methods and signals of the
<code>ConfigurationManager</code> will already allow us to create an account, add a contact
and receive and send messages.
</p>
</div>
<div id="outline-container-org822561d" class="outline-3">
<h3 id="org822561d">ConfigurationManager</h3>
<div class="outline-text-3" id="text-org822561d">
<p>
The interface of the <code>ConfigurationManager</code> is rather extensive. It consists of
<i>methods</i> and <i>signals</i>. The former are callable and often yield a response
while the latter are something that one subscribes to in order to get notified
of e.g. state changes, received messages and the like. To get a complete list of
the interface, one can use the following introspection function:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-introspect-get-interface <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span> <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>)
</code></pre>
</div>

<p>
The returned list will be very long but we will only need a fraction of these commands in the following.
</p>
</div>
</div>
<div id="outline-container-orge762f6e" class="outline-3">
<h3 id="orge762f6e">Jami account management via D-Bus</h3>
<div class="outline-text-3" id="text-orge762f6e">
<p>
The <code>ConfigurationManager</code> exposes all methods required to setup and manage Jami
accounts. If you want, you could also start up the GUI application and set up an
account there instead &ndash; whether you use the GUI or D-Bus, accounts will be
visible from either client, even simultaneously<sup><a id="fnr.3" class="footref" href="#fn.3" role="doc-backlink">3</a></sup>.
</p>

<p>
First, let us list the already existing accounts (if any) by calling the method
<code>getAccountList</code> on the <code>ConfigurationManager</code> interface:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"getAccountList"</span>)
</code></pre>
</div>

<p>
The function <code>dbus-call-method</code> we will use quite extensively in the following
as it is one of the main ways to interact with an application over D-Bus. The
above method <code>getAccountList</code> does not take any arguments; otherwise, they would
be provided as additional arguments to <code>dbus-call-mehod</code>. As I do not yet have
any Jami accounts set up, the returned value is empty.
</p>

<p>
As with most messengers, an account in Jami has a ton of configuration
parameters that can be modified. So let us have a look at the template that is
being used to fill these settings before we actually create a new account. There
are two types of account: &ldquo;SIP&rdquo; or &ldquo;RING&rdquo;. We want to use the latter:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"getAccountTemplate"</span>
                  <span class="org-string">"RING"</span>)
</code></pre>
</div>


<p>
The method for creating a new account is called <code>addAccount</code> (of course). We can use introspection to find out what arguments it takes:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-introspect-get-method <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span> <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span> <span class="org-string">"addAccount"</span> )
</code></pre>
</div>

<p>
From this, we see that it takes and array <i>details</i> of type <code>string string</code> as
input and returns the ID of the created account. What exactly these <i>details</i>
are would be a bit mysterious though without the clues we found in the account
template above. After looking through the documentation for <a href="https://www.gnu.org/software/emacs/manual/html_mono/dbus.html#Type-Conversion">the mapping between
D-Bus types and Elisp</a> we can construct a proper call to the method and
configure key settings of the account in one go:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"addAccount"</span>
                  '(<span class="org-builtin">:array</span>
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.type"</span> <span class="org-builtin">:string</span> <span class="org-string">"RING"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.alias"</span> <span class="org-string">"jami-bot"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.displayName"</span> <span class="org-string">"jami-bot"</span>)))
</code></pre>
</div>

<p>
The alias/display name are <b>not</b> the same as the user name: the latter is
registered and unique on the Jami name server while the former can be changed at
any time and are only shown to one&rsquo;s contacts. The ID returned by the method
call is a shortened ID that is used by the Jami daemon to identify the account
internally. On the Jami network, the account is referred to a by a longer hash
(the <i>address</i>, always present) or by a <i>name</i> which can be registered and maps
to the address. I hope the examples below will make this a little clearer.
</p>

<p>
Let&rsquo;s first see that the account is now present by checking the list of accounts:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"getAccountList"</span>)
</code></pre>
</div>

<p>
Great! The account settings can be retrieved using a call to the method
<code>getAccountDetails</code> and the account ID as argument. In the row
<code>Account.username</code> you should see the full address that identifies the user on
the network:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"getAccountDetails"</span>
                  <span class="org-string">"d362747388cf970f"</span>)
</code></pre>
</div>

<p>
To change any of these settings, simply call <code>setAccountDetails</code>. According to
the documentation, this should work with an incomplete list of details and only
affect those that are explicitly set. However, it my experiments, almost all
details missing in the argument were simply wiped &ndash; including, but not limited
to, <code>Account.enabled</code> which controls whether or not the account will be visible
on the network. This might be a bug in Jami, an artifact of the D-Bus API in
Emacs or caused by the way I call the method. In any case, better set all the
details at once as with the lengthy call below:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"setAccountDetails"</span>
                  <span class="org-string">"d362747388cf970f"</span>
                  '(<span class="org-builtin">:array</span>
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.accountDiscovery"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.accountPublish"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.activeCallLimit"</span> <span class="org-builtin">:string</span> <span class="org-string">"-1"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.alias"</span> <span class="org-builtin">:string</span> <span class="org-string">"jami-bot"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.allModeratorEnabled"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.allowCertFromContact"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.allowCertFromHistory"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.allowCertFromTrusted"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.archiveHasPassword"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.audioPortMax"</span> <span class="org-builtin">:string</span> <span class="org-string">"32766"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.audioPortMin"</span> <span class="org-builtin">:string</span> <span class="org-string">"16384"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.autoAnswer"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.defaultModerators"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.deviceName"</span> <span class="org-builtin">:string</span> <span class="org-string">"reform"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.dhtProxyListUrl"</span> <span class="org-builtin">:string</span> <span class="org-string">"https://config.jami.net/proxyList"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.displayName"</span> <span class="org-builtin">:string</span> <span class="org-string">"jami-bot"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.dtmfType"</span> <span class="org-builtin">:string</span> <span class="org-string">"overrtp"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.enable"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.hostname"</span> <span class="org-builtin">:string</span> <span class="org-string">"bootstrap.jami.net"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.localInterface"</span> <span class="org-builtin">:string</span> <span class="org-string">"default"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.localModeratorsEnabled"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.mailbox"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.managerUri"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.managerUsername"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.peerDiscovery"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.presenceSubscribeSupported"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.proxyEnabled"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.proxyServer"</span> <span class="org-builtin">:string</span> <span class="org-string">"dhtproxy.jami.net:[80-95]"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.publishedAddress"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.publishedSameAsLocal"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.rendezVous"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.ringtoneEnabled"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.ringtonePath"</span> <span class="org-builtin">:string</span> <span class="org-string">"default.opus"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.sendReadReceipt"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.type"</span> <span class="org-builtin">:string</span> <span class="org-string">"RING"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.upnpEnabled"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.useragent"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.videoEnabled"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.videoPortMax"</span> <span class="org-builtin">:string</span> <span class="org-string">"65534"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"Account.videoPortMin"</span> <span class="org-builtin">:string</span> <span class="org-string">"49152"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"DHT.PublicInCalls"</span> <span class="org-builtin">:string</span> <span class="org-string">"false"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"DHT.port"</span> <span class="org-builtin">:string</span> <span class="org-string">"0"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"RingNS.uri"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TLS.certificateFile"</span> <span class="org-builtin">:string</span> <span class="org-string">"/home/hanno/.local/share/jami/d362747388cf970f/ring_device.crt"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TLS.certificateListFile"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TLS.password"</span> <span class="org-builtin">:string</span> <span class="org-string">""</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TLS.privateKeyFile"</span> <span class="org-builtin">:string</span> <span class="org-string">"/home/hanno/.local/share/jami/d362747388cf970f/ring_device.key"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TURN.enable"</span> <span class="org-builtin">:string</span> <span class="org-string">"true"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TURN.password"</span> <span class="org-builtin">:string</span> <span class="org-string">"ring"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TURN.realm"</span> <span class="org-builtin">:string</span> <span class="org-string">"ring"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TURN.server"</span> <span class="org-builtin">:string</span> <span class="org-string">"turn.jami.net"</span>)
                    (<span class="org-builtin">:dict-entry</span> <span class="org-builtin">:string</span> <span class="org-string">"TURN.username"</span> <span class="org-builtin">:string</span> <span class="org-string">"ring"</span>)))
</code></pre>
</div>
</div>
</div>
</section>
<section id="outline-container-orgf92bb38" class="outline-2">
<h2 id="orgf92bb38">Establishing contact</h2>
<div class="outline-text-2" id="text-orgf92bb38">
<p>
Now let us put the newly created account to use and start a chat with another
Jami user. For that, we will need to know the full address such as, for example,
<code>badac18e13ec1a6e1266600e457859afebfb9c46</code>. As this is error prone to type in
and tedious to spell out, the GUI application allows you to scan a QR code from
your friend&rsquo;s phone instead. Alternatively, you can register a name on the
network that is easier to remember but must be unique. Let us assume that the
person we would like to contact has done so and search for the user name on the
network.
</p>
</div>
<div id="outline-container-org92dfb43" class="outline-3">
<h3 id="org92dfb43">Searching for profiles and signal handling</h3>
<div class="outline-text-3" id="text-org92dfb43">
<p>
The name lookup is one of the features of Jami that is handled in two steps:
first, we trigger the lookup by a call to the corresponding method,
<code>lookupName</code>. Then, we have to wait until Jami signals that the name is indeed
registered and has been found. This is done via the signal <code>registeredNameFound</code>
which we have to subscribe to by registering a function.
</p>

<p>
What does this function have to look like? That depends on the signal in question. Via introspection, we can find out what the expected function arguments are:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-introspect-get-signal <span class="org-builtin">:session</span>
                            <span class="org-string">"cx.ring.Ring"</span>
                            <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                            <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                            <span class="org-string">"registeredNameFound"</span>)
</code></pre>
</div>

<p>
For our purposes, a simple function like this will do for now:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-registeredNameFound-handler</span> (account status address name)
  (message <span class="org-string">"jami received profile for account: %s, status: %s, address: %s, name: %s"</span> account status address name))
</code></pre>
</div>

<p>
It will simply dump its arguments into the <code>*Messages*</code> buffer. Let us register the function for the signal <code>registeredNameFound</code>:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-register-signal <span class="org-builtin">:session</span>
                      <span class="org-string">"cx.ring.Ring"</span>
                      <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                      <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                      <span class="org-string">"registeredNameFound"</span>
                      #'my-registeredNameFound-handler)
</code></pre>
</div>

<p>
The function <code>dbus-register-signal</code> returns an object that can be used via <code>dbus-unregister-object</code> to remove the registration again.
</p>

<p>
Now we can trigger the name lookup for <code>my-best-friend</code><sup><a id="fnr.4" class="footref" href="#fn.4" role="doc-backlink">4</a></sup>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"lookupName"</span>
                  <span class="org-string">""</span> <span class="org-string">"ns.jami.net"</span> <span class="org-string">"my-best-friend"</span>)
</code></pre>
</div>

<p>
The lookup is done on Jami&rsquo;s default name server <code>ns.jami.net</code>, but you could put your own here, if so desired.
</p>

<p>
Shortly after, you should see something like this in your <code>*Messages*</code> buffer:
</p>
<pre class="example" id="org3c5d064">
jami received profile for account: , status: 0, address: b8e0350a62caf0173d18e0d1256a8cf2ea88e75b, name: my-best-friend
</pre>

<p>
The status flag  is explained in the docstring to the corresponding signal in <a href="https://git.jami.net/savoirfairelinux/jami-daemon/-/blob/dd317b6060bb37730d7e8ae7d18ff3e83fb33216/bin/dbus/cx.ring.Ring.ConfigurationManager.xml#L349">the cx.ring.Ring.ConfigurationManager.xml file</a>:
</p>

<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">


<colgroup>
<col  class="org-left" />

<col  class="org-right" />

<col  class="org-left" />
</colgroup>
<tbody>
<tr>
<td class="org-left">SUCCESS</td>
<td class="org-right">0</td>
<td class="org-left">everything went fine. Name/address pair was found.</td>
</tr>

<tr>
<td class="org-left">INVALID<sub>NAME</sub></td>
<td class="org-right">1</td>
<td class="org-left">provided name is not valid.</td>
</tr>

<tr>
<td class="org-left">NOT<sub>FOUND</sub></td>
<td class="org-right">2</td>
<td class="org-left">everything went fine. Name/address pair was not found.</td>
</tr>

<tr>
<td class="org-left">ERROR</td>
<td class="org-right">3</td>
<td class="org-left">An error happened</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="outline-container-org18ff120" class="outline-3">
<h3 id="org18ff120">Adding a contact</h3>
<div class="outline-text-3" id="text-org18ff120">
<p>
If you have either found a contact by looking up the name or know a contact&rsquo;s ID string, you can now add that contact to our account:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"addContact"</span>
                  <span class="org-string">"d362747388cf970f"</span>
                  <span class="org-string">"b8e0350a62caf0173d18e0d1256a8cf2ea88e75b"</span>)
</code></pre>
</div>

<p>
This will send a contact request to the person in question and &ndash; if accepted &ndash;
initiate a conversation. Alternatively, you could also start a new conversation
(<code>startConversation</code>) and add members to it (<code>addConversationMember</code>), if you
would like to use a group chat instead.
</p>
</div>
</div>
</section>
<section id="outline-container-org2da64d5" class="outline-2">
<h2 id="org2da64d5">Conversations and sending messages</h2>
<div class="outline-text-2" id="text-org2da64d5">
<p>
For any account, you can list the available conversations:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"getConversations"</span>
                  <span class="org-string">"d362747388cf970f"</span>)
</code></pre>
</div>

<p>
To see who is a member of the conversation, use <code>getConversationMembers</code> and provide account and conversation as arguments:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"getConversationMembers"</span>
                  <span class="org-string">"d362747388cf970f"</span>
                  <span class="org-string">"70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252"</span>)
</code></pre>
</div>

<p>
Knowing its ID, we can send a message to a conversation via <code>sendMessage</code>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span>
                  <span class="org-string">"cx.ring.Ring"</span>
                  <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                  <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                  <span class="org-string">"sendMessage"</span>
                  <span class="org-string">"d362747388cf970f"</span>
                  <span class="org-string">"70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252"</span>
                  <span class="org-string">"this is the first line of the message\nThis is the second line"</span>
                  <span class="org-string">""</span>
                  <span class="org-builtin">:int32</span> 0)
</code></pre>
</div>

<p>
The last two arguments are the <i>commitId</i> and the <i>flag</i>. The former is used to
refer to an existing message (a <i>commit</i> since <code>git</code> is used behind the scenes
to manage conversation data). In the above example, we are sending a single, new
message, so the commitId is empty and the flag set to 0. If a commitId would
have been given, then this would have been sent as a reply to that message
instead. If the flag is set to &rsquo;1&rsquo; then this would have been an edit to an
existing message.
</p>

<p>
You can find out the id of a previous message by searching for it. I have,
however, not looked much into that feature. Alternatively, one can wait for a
signal that a new message arrived (<code>messageReceived</code>) and retrieve both message
and its id from a registered handler. As a more trivial example, we can dump the meta information and contents of the message to the minibuffer and <code>*Messages*</code> buffer:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-messageReceived-handler</span> (account conversation message)
  (message <span class="org-string">"jami received msg: account: %s, conversation: %s, msg: %s"</span> account conversation message))

(dbus-register-signal <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span> <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span> <span class="org-string">"messageReceived"</span> #'my-messageReceived-handler)
</code></pre>
</div>

<p>
Some example messages:
</p>
<blockquote>
<p>
jami received msg: account: d362747388cf970f, conversation: 70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252, msg: ((author b8e0350a62caf0173d18e0d1256a8cf2ea88e75b) (body Test!) (id f23a3db43597ad1bdd7e8027cd3e6ca2f4ad7820) (linearizedParent d899f9926e8bedc907b85e6f60bf0f03672c5881) (parents d899f9926e8bedc907b85e6f60bf0f03672c5881) (timestamp 1678032406) (type text/plain))
</p>


<p>
jami received msg: account: d362747388cf970f, conversation: 70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252, msg: ((author b8e0350a62caf0173d18e0d1256a8cf2ea88e75b) (displayName IMG<sub>20230304</sub><sub>115628.jpg</sub>) (fileId 15af639ad4cab741e15717cb867cb613f0e8c4ff<sub>3740082762842406.jpg</sub>) (id 15af639ad4cab741e15717cb867cb613f0e8c4ff) (linearizedParent eab0a2c8e2f1ca5f7de8991375dafb8962178cae) (parents eab0a2c8e2f1ca5f7de8991375dafb8962178cae) (sha3sum 457c749572575f6827b2ae0860d065911adc959a0d57d4ccc47ab26cc93e7c07a01122e28344ee7745aca8ea85216ba6c6aba8cf42aaaebd68ff33fa6ad66929) (tid 3740082762842406) (timestamp 1678033015) (totalSize 2971303) (type application/data-transfer+json))
</p>
</blockquote>

<p>
In the first example, we have received a simple text (&rsquo;Test!&rsquo;). The message contains, amongst others, fields for the author, message id, and timestamp. In the second example, the message is a file transfer with additional fields such as the <i>id</i> and <i>fileId</i>. Knowing these, we can download the file to the local machine using <code>downloadFile</code>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-introspect-get-method <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span> <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span> <span class="org-string">"downloadFile"</span>)
</code></pre>
</div>

<p>
Following the example above, this could be:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(dbus-call-method <span class="org-builtin">:session</span> <span class="org-string">"cx.ring.Ring"</span> <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span> <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span> <span class="org-string">"downloadFile"</span> <span class="org-string">"d362747388cf970f"</span> <span class="org-string">"70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252"</span> <span class="org-string">"15af639ad4cab741e15717cb867cb613f0e8c4ff"</span> <span class="org-string">"15af639ad4cab741e15717cb867cb613f0e8c4ff_3740082762842406.jpg"</span> <span class="org-string">"/home/hanno/tmp/jami.jpg"</span>)
</code></pre>
</div>

<p>
Timestamps are given in seconds and can be converted into calendrical information using <code>decode-time</code>:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(decode-time 1678032925)
</code></pre>
</div>
<p>
(25 15 17 5 3 2023 0 nil 3600)
</p>

<p>
The values are <code>(SECONDS MINUTES HOUR DAY MONTH YEAR DOW DST UTCOFF)</code>, respectively.
</p>
</div>
</section>
<section id="outline-container-org4b9256d" class="outline-2">
<h2 id="org4b9256d">Defining a helper routine for accessing Jami methods via D-Bus</h2>
<div class="outline-text-2" id="text-org4b9256d">
<p>
To save some keystrokes, we can define a helper routine with all the constant parts:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-dbus-call-cfgmgr-method</span> (method <span class="org-type">&amp;rest</span> args)
  <span class="org-doc">"Call jami D-Bus METHOD on the cfgmgr interface with arguments ARGS."</span>
  (apply #'dbus-call-method `(<span class="org-builtin">:session</span>
                    <span class="org-string">"cx.ring.Ring"</span>
                    <span class="org-string">"/cx/ring/Ring/ConfigurationManager"</span>
                    <span class="org-string">"cx.ring.Ring.ConfigurationManager"</span>
                    ,method ,@(<span class="org-keyword">when</span> args args))))
</code></pre>
</div>

<p>
That means that sending a message would become:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(jami-dbus-call-cfgmgr-method <span class="org-string">"sendMessage"</span>
                              <span class="org-string">"d362747388cf970f"</span>
                              <span class="org-string">"70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252"</span>
                              <span class="org-string">"this is the first line of the message\nThis is the second line"</span>
                              <span class="org-string">""</span>
                              <span class="org-builtin">:int32</span> 0)
</code></pre>
</div>

<p>
Or, with yet another helper function, we can get this even shorter:
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">jami-send-message</span> (account conversation text <span class="org-type">&amp;optional</span> reply)
  <span class="org-doc">"Add TEXT to CONVERSATION via ACCOUNT. REPLY optionally specifies
a message id."</span>
  (jami-dbus-call-cfgmgr-method <span class="org-string">"sendMessage"</span>
                                account
                                conversation
                                text
                                `(,@(<span class="org-keyword">if</span> reply reply <span class="org-string">""</span>))
                                <span class="org-builtin">:int32</span> 0))
</code></pre>
</div>

<p>
Which would allow to send a message with a call to:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(jami-send-message <span class="org-string">"d362747388cf970f"</span>
                   <span class="org-string">"70bcc0ce73be4091b5e159dc22d6cc5bb4e1a252"</span>
                   <span class="org-string">"this is the first line of the message\nThis is the second line"</span>)
</code></pre>
</div>
</div>
</section>
<section id="outline-container-orgeef40a0" class="outline-2">
<h2 id="orgeef40a0">Final thoughts</h2>
<div class="outline-text-2" id="text-orgeef40a0">
<p>
D-Bus is a powerful tool to interact with services running on the system. Its
introspection mechanism provides a lot of the necessary information to use this
API &ndash; however, additional documentation is almost a must, especially when
dealing with more complex arguments to methods. Accessing services on D-Bus from
Emacs is easy enough, though converting different data types took me some trial
and error.
</p>

<p>
Jami is unusual as it exposes most if not all important functions via D-Bus. In
fact, <a href="https://docs.jami.net/developer/apis-of-jami.html">in Jami&rsquo;s case, D-Bus is the best-supported API</a> to write clients with.
Despite the sparse documentation outside the API definition, the API is simple
enough and Jami can be scripted with only a few lines of code to do text
messaging from within Emacs!
</p>

<p>
While this post does not really drive that last point home, the next one in this
series will demonstrate <a href="https://hoowl.se/jami-bot.html">a Jami chat bot written in Elisp</a>!
</p>


<p>
Tags: <a href="https://www.hoowl.se/../tags/emacs.html">emacs</a>, <a href="https://www.hoowl.se/../tags/programming.html">programming</a>, <a href="https://www.hoowl.se/../tags/jami.html">jami</a>, <a href="https://www.hoowl.se/../tags/lisp.html">lisp</a>, <a href="https://www.hoowl.se/../tags/dbus.html">dbus</a></p>
</div>
</section>
<div id="footnotes">
<h2 class="footnotes">Footnotes: </h2>
<div id="text-footnotes">

<div class="footdef"><sup><a id="fn.1" class="footnum" href="#fnr.1" role="doc-backlink">1</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Not sure what the correct
capitalization of D-Bus is, there seem to be a variation of conventions around.
I will stick with D-Bus over DBUS or dbus</p></div></div>

<div class="footdef"><sup><a id="fn.2" class="footnum" href="#fnr.2" role="doc-backlink">2</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Emacs needs to be compiled with D-Bus support &ndash; which is the
default.</p></div></div>

<div class="footdef"><sup><a id="fn.3" class="footnum" href="#fnr.3" role="doc-backlink">3</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">If you ever encounter
issues where GUI and daemon do not seem to be in sync, try to close the GUI and
kill the daemon (<code>killall jamid</code>) before restarting Jami.</p></div></div>

<div class="footdef"><sup><a id="fn.4" class="footnum" href="#fnr.4" role="doc-backlink">4</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">With appologies to the person that might actually register this name at some point.</p></div></div>


</div>
</div>]]>
</description></item>
</channel>
</rss>
