<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/atom.xsl" type="text/xsl"?><feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:thr="http://purl.org/syndication/thread/1.0"
	xml:lang="en-US"
	><title>Sacha Chua - category - spookfox</title>
	<subtitle>Emacs, sketches, and life</subtitle>
	<link rel="self" type="application/atom+xml" href="https://sachachua.com/blog/category/spookfox/feed/atom/index.xml" />
  <link rel="alternate" type="text/html" href="https://sachachua.com/blog/category/spookfox" />
  <id>https://sachachua.com/blog/category/spookfox/feed/atom/index.xml</id>
  <generator uri="https://11ty.dev">11ty</generator>
	<updated>2024-01-19T13:12:17Z</updated>
<entry>
		<title type="html">Running the current Org Mode Babel Javascript block from Emacs using Spookfox</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2024-01-19T13:12:17Z</updated>
    <published>2024-01-19T13:12:17Z</published>
    <category term="emacs" />
<category term="org" />
<category term="spookfox" />
		<id>https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/</id>
		<content type="html"><![CDATA[<p>
I often want to send Javascript from Emacs to the web browser. It's handy for testing code snippets or working with data on pages that require Javascript or authentication. I could start Google Chrome or Mozilla Firefox with their remote debugging protocols, copy the websocket URLs, and talk to the browser through  something like Puppeteer, but it's so much easier to use the <a href="https://github.com/bitspook/spookfox">Spookfox</a> extension for Mozilla to execute code in the active tab.
<code>spookfox-js-injection-eval-in-active-tab</code> lets you evaluate Javascript and get the results back in Emacs Lisp.
</p>

<p>
I wanted to be able to execute code even more
easily. This code lets me add a <code>:spookfox t</code>
parameter to Org Babel Javascript blocks so that I
can run the block in my Firefox active tab.
For example, if I have <code>(spookfox-init)</code> set up, Spookfox connected, and <a href="https://planet.emacslife.com">https://planet.emacslife.com</a> in my active tab, I can use it with the following code:
</p>


<div class="org-src-container">
<pre class="src src-org"><span class="org-org-block-begin-line">#+begin_src js :eval never-export :spookfox t :exports results</span>
<span class="org-org-block">[...document.querySelectorAll(</span><span class="org-org-block"><span class="org-string">'.post &gt; h2'</span></span><span class="org-org-block">)].slice(0,5).map((o) =&gt; </span><span class="org-org-block"><span class="org-string">'- '</span></span><span class="org-org-block"> + o.textContent.trim().replace(</span><span class="org-org-block"><span class="org-string">/[ \n]+/</span></span><span class="org-org-block">g, </span><span class="org-org-block"><span class="org-string">' '</span></span><span class="org-org-block">) + </span><span class="org-org-block"><span class="org-string">'\n'</span></span><span class="org-org-block">).join(</span><span class="org-org-block"><span class="org-string">''</span></span><span class="org-org-block">)</span>
<span class="org-org-block-end-line">#+end_src</span>
</pre>
</div>


<ul class="org-ul">
<li>Mario Jason Braganza: Updated to Emacs 29.2</li>
<li>Irreal: Zamansky: Learning Elisp #16</li>
<li>Tim Heaney: Lisp syntax</li>
<li>Erik L. Arneson: Many Posts of Interest for January 2024</li>
<li>William Denton: Basic citations in Org (Part 4)</li>
</ul>

<p>
</p><figure><video controls="1" src="https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/2024-01-19_08.16.31.webm" type="video/webm"><a href="https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/2024-01-19_08.16.31.webm">Download the video</a></video><figcaption><div>Evaluating a Javascript block with :spookfox t</div></figcaption></figure>
<p></p>

<p>
To do this, we wrap some advice around the <code>org-babel-execute:js</code> function that's called by <code>org-babel-execute-src-block</code>.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-babel-execute:js-spookfox</span> (old-fn body params)
  <span class="org-doc">"Maybe execute Spookfox."</span>
  (<span class="org-keyword">if</span> (assq <span class="org-builtin">:spookfox</span> params)
      (spookfox-js-injection-eval-in-active-tab
       body t)
    (funcall old-fn body params)))
(<span class="org-keyword">with-eval-after-load</span> 'ob-js
  (advice-add 'org-babel-execute:js <span class="org-builtin">:around</span> #'my-org-babel-execute:js-spookfox))
</pre>
</div>


<p>
I can also run the block in Spookfox without adding the parameter if I make an interactive function:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-eval-org-block</span> ()
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">let</span> ((block (org-element-context)))
    (<span class="org-keyword">when</span> (<span class="org-keyword">and</span> (eq (org-element-type block) 'src-block)
               (string= (org-element-property <span class="org-builtin">:language</span> block) <span class="org-string">"js"</span>))
      (spookfox-js-injection-eval-in-active-tab
       (nth 2 (org-src&#45;&#45;contents-area block))
       t))))
</pre>
</div>


<p>
I can add that as an Embark context action:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">with-eval-after-load</span> 'embark-org
  (define-key embark-org-src-block-map <span class="org-string">"f"</span> #'my-spookfox-eval-org-block))
</pre>
</div>


<p>
In Javascript buffers, I want the ability to send the current line, region, or buffer too, just like nodejs-repl does.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-send-region</span> (start end)
  (<span class="org-keyword">interactive</span> <span class="org-string">"r"</span>)
  (spookfox-js-injection-eval-in-active-tab (buffer-substring start end) t))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-send-buffer</span> ()
  (<span class="org-keyword">interactive</span>)
  (my-spookfox-send-region (point-min) (point-max)))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-send-line</span> ()
  (<span class="org-keyword">interactive</span>)
  (my-spookfox-send-region (line-beginning-position) (line-end-position)))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-send-last-expression</span> ()
  (<span class="org-keyword">interactive</span>)
  (my-spookfox-send-region (<span class="org-keyword">save-excursion</span> (nodejs-repl&#45;&#45;beginning-of-expression)) (point)))

(<span class="org-keyword">defvar-keymap</span> my-js-spookfox-minor-mode-map
  <span class="org-builtin">:doc</span> <span class="org-doc">"Send parts of the buffer to Spookfox."</span>
  <span class="org-string">"C-x C-e"</span> 'my-spookfox-send-last-expression
  <span class="org-string">"C-c C-j"</span> 'my-spookfox-send-line
  <span class="org-string">"C-c C-r"</span> 'my-spookfox-send-region
  <span class="org-string">"C-c C-c"</span> 'my-spookfox-send-buffer)

(<span class="org-keyword">define-minor-mode</span> <span class="org-function-name">my-js-spookfox-minor-mode</span> <span class="org-doc">"Send code to Spookfox."</span>)
</pre>
</div>


<p>
I usually edit Javascript files with <a target="_blank" href="https://elpa.gnu.org/packages/js2-mode.html">js2-mode</a>, so I can use <code>my-js-spookfox-minor-mode</code> in addition to that.
</p>

<p>
I can turn the minor mode on automatically for <code>:spookfox t</code> source blocks. There's no <code>org-babel-edit-prep:js</code> yet, I think, so we need to define it instead of advising it.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">org-babel-edit-prep:js</span> (info)
  (<span class="org-keyword">when</span> (assq <span class="org-builtin">:spookfox</span> (nth 2 info))
    (my-js-spookfox-minor-mode 1)))
</pre>
</div>


<p>
Let's try it out by sending the last line repeatedly:
</p>

<p>
</p><figure><video controls="1" src="https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/2024-01-19_08.26.35.webm" type="video/webm"><a href="https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/2024-01-19_08.26.35.webm">Download the video</a></video><figcaption><div>Sending the current line</div></figcaption></figure>
<p></p>

<p>
I used to do this kind of interaction with <a href="https://github.com/skeeto/skewer-mode">Skewer</a>, which also has some extra stuff for evaluating CSS and HTML. Skewer hasn't been updated in a while, but maybe I should also check that out again to see if I can get it working.
</p>

<p>
Anyway, now it's just a little bit easier to tinker with Javascript!
</p>
<div><a href="https://sachachua.com/blog/2024/01/running-the-current-org-mode-babel-javascript-block-in-spookfox/index.org">View org source for this post</a></div>
<div class="note">This is part of my <a href="https://sachachua.com/dotemacs#spookfox-babel">Emacs configuration.</a></div><p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2024%2F01%2Frunning-the-current-org-mode-babel-javascript-block-in-spookfox%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">EmacsConf backstage: Using Spookfox to publish YouTube and Toobnix video drafts</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2023/12/emacsconf-backstage-using-spookfox-to-publish-youtube-and-toobnix-video-drafts/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2023-12-12T20:09:03Z</updated>
    <published>2023-12-12T20:09:03Z</published>
    <category term="emacsconf" />
<category term="emacs" />
<category term="spookfox" />
<category term="youtube" />
<category term="video" />
		<id>https://sachachua.com/blog/2023/12/emacsconf-backstage-using-spookfox-to-publish-youtube-and-toobnix-video-drafts/</id>
		<content type="html"><![CDATA[<p>
I ran into quota limits when uploading videos to YouTube with a command-line tool, so I uploaded videos by selecting up to 15 videos at a time using the web-based interface. Each video was a draft, though, and I was having a hard time updating its visibility through the API. I think it eventually worked, but in the meantime, I used this very hacky hack to look for the "Edit Draft" button and click through the screens to publish them.
</p>

<p>
</p><details open=""><summary>emacsconf-extract-youtube-publish-video-drafts-with-spookfox: Look for drafts and publish them.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-extract-youtube-publish-video-drafts-with-spookfox</span> ()
  <span class="org-doc">"Look for drafts and publish them."</span>
  (<span class="org-keyword">while</span> (not (eq (spookfox-js-injection-eval-in-active-tab
                   <span class="org-string">"document.querySelector('.edit-draft-button div') != null"</span> t) <span class="org-builtin"><span class="org-warning">:false</span></span><span class="org-warning">))</span>
    (<span class="org-keyword">progn</span>
      (spookfox-js-injection-eval-in-active-tab
       <span class="org-string">"document.querySelector('.edit-draft-button div').click()"</span> t)
      (sleep-for 2)
      (spookfox-js-injection-eval-in-active-tab
       <span class="org-string">"document.querySelector('#step-title-3').click()"</span> t)
      (<span class="org-keyword">when</span> (spookfox-js-injection-eval-in-active-tab
             <span class="org-string">"document.querySelector('tp-yt-paper-radio-button[name=\"PUBLIC\"] #radioLabel').click()"</span> t)
        (spookfox-js-injection-eval-in-active-tab
         <span class="org-string">"document.querySelector('#done-button').click()"</span> t)
        (<span class="org-keyword">while</span> (not (eq  (spookfox-js-injection-eval-in-active-tab
                          <span class="org-string">"document.querySelector('#close-button .label') == null"</span> t)
                         <span class="org-builtin">:false</span>))
          (sleep-for 1))

        (spookfox-js-injection-eval-in-active-tab
         <span class="org-string">"document.querySelector('#close-button .label').click()"</span> t)
        (sleep-for 1)))))
</pre></div></details>
<p></p>

<p>
Another example of a hacky Spookfox workaround was publishing the
unlisted videos. I couldn't figure out how to properly authenticate
with the Toobnix (Peertube) API to change the visibility of videos.
Peertube uses AngularJS components in the front end, so using
<code>.click()</code> on the input elements didn't seem to trigger anything. I
found out that I needed to use <code>.dispatchEvent(new Event('input'))</code> to
tell the dropdown for the visibility to display the options. <a href="https://stackoverflow.com/questions/67633828/angular-and-vanila-js-set-input-value">source</a>
</p>

<p>
</p><details open=""><summary>emacsconf-extract-toobnix-publish-video-from-edit-page: Messy hack to set a video to public and store the URL.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-extract-toobnix-publish-video-from-edit-page</span> ()
  <span class="org-doc">"Messy hack to set a video to public and store the URL."</span>
  (<span class="org-keyword">interactive</span>)
  (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('label[for=privacy]').scrollIntoView(); document.querySelector('label[for=privacy]').closest('</span><span class="org-string"><span class="org-constant">.form-group</span></span><span class="org-string">').querySelector('</span><span class="org-string"><span class="org-constant">input</span></span><span class="org-string">').dispatchEvent(new Event('</span><span class="org-string"><span class="org-constant">input</span></span><span class="org-string">'));"</span> t)
  (sit-for 1)
  (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('span[title=\"Anyone can see this video\"]').click()"</span> t)
  (sit-for 1)
  (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('</span><span class="org-string"><span class="org-constant">button.orange-button</span></span><span class="org-string">').click()"</span> t)(sit-for 3)
  (emacsconf-extract-store-url)
  (shell-command <span class="org-string">"xdotool key Alt+Tab sleep 1 key Ctrl+w Alt+Tab"</span>))
</pre></div></details>
<p></p>

<p>
It's a little nicer using Spookfox to automate browser interactions
than using xdotool, since I can get data out of it too. I could also
have used Puppeteer from either Python or NodeJS, but it's nice
staying with Emacs Lisp. Spookfox has some Javascript limitations
(can't close windows, etc.), so I might still use bits of xdotool or
Puppeteer to work around that. Still, it's nice to now have an idea of
how to talk to AngularJS components.
</p>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2023%2F12%2Femacsconf-backstage-using-spookfox-to-publish-youtube-and-toobnix-video-drafts%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">EmacsConf backstage: Making a (play)list, checking it twice</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2023/12/emacsconf-backstage-making-a-play-list-checking-it-twice/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2023-12-12T15:50:37Z</updated>
    <published>2023-12-12T15:50:37Z</published>
    <category term="emacs" />
<category term="emacsconf" />
<category term="spookfox" />
<category term="youtube" />
<category term="video" />
		<id>https://sachachua.com/blog/2023/12/emacsconf-backstage-making-a-play-list-checking-it-twice/</id>
		<content type="html"><![CDATA[<p>
I wanted the <a href="https://www.youtube.com/playlist?list=PLomc4HLgvuCUdrW3JkugtKv8xPelUoOyP">EmacsConf 2023 Youtube</a> and
<a href="https://toobnix.org/w/p/nMXCCJ25wxKUtbuQiwkakA">Toobnix playlists</a>
to mostly reflect the schedule of the conference by track, with talks
followed by their Q&amp;A sessions (if recorded).
</p>
<div id="outline-container-org8e7129c" class="outline-2">
<h3 id="org8e7129c">The list</h3>
<div class="outline-text-2" id="text-org8e7129c">
<p>
I used Emacs Lisp to generate a list of videos in the order I wanted.
That Sunday closing remarks aren't actually in the playlists because
they're combined with the Q&amp;A for my session on how we run Emacsconf.
</p>

<p>
</p><details><summary>emacsconf-extract-check-playlists: Return a table for checking playlist order.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-extract-check-playlists</span> ()
  <span class="org-doc">"Return a table for checking playlist order."</span>
  (<span class="org-keyword">let</span> ((pos 0))
    (seq-mapcat (<span class="org-keyword">lambda</span> (o)
                  (delq
                   nil
                   (list
                    (<span class="org-keyword">when</span> (emacsconf-talk-file o <span class="org-string">"&#45;&#45;main.webm"</span>)
                      (<span class="org-keyword">cl-incf</span> pos)
                      (list pos
                            (plist-get o <span class="org-builtin">:title</span>)
                            (org-link-make-string
                             (plist-get o <span class="org-builtin">:youtube-url</span>)
                             <span class="org-string">"YouTube"</span>)
                            (org-link-make-string
                             (plist-get o <span class="org-builtin">:toobnix-url</span>)
                             <span class="org-string">"Toobnix"</span>)))
                    (<span class="org-keyword">when</span> (emacsconf-talk-file o <span class="org-string">"&#45;&#45;answers.webm"</span>)
                      (<span class="org-keyword">cl-incf</span> pos)
                      (list pos (concat <span class="org-string">"Q&amp;A: "</span> (plist-get o <span class="org-builtin">:title</span>))
                            (org-link-make-string
                             (plist-get o <span class="org-builtin">:qa-youtube-url</span>)
                             <span class="org-string">"YouTube"</span>)
                            (org-link-make-string
                             (plist-get o <span class="org-builtin">:qa-toobnix-url</span>)
                             <span class="org-string">"Toobnix"</span>))))))
                (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
</pre></div></details>
<p></p>

<div class="bordered" id="org2e50b18">
<table>


<colgroup>
<col class="org-right">

<col class="org-left">

<col class="org-left">

<col class="org-left">
</colgroup>
<tbody>
<tr>
<td class="org-right">1</td>
<td class="org-left">An Org-Mode based text adventure game for learning the basics of Emacs, inside Emacs, written in Emacs Lisp</td>
<td class="org-left"><a href="https://youtu.be/7R0yA0R1jsk">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/2oqbPJB8Wm3QSo4HCKAyVn">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">2</td>
<td class="org-left">Authoring and presenting university courses with Emacs and a full libre software stack</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=cklJ58i-HUY">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/mAnNW7jnPq5qhUPH2dzVQf">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">3</td>
<td class="org-left">Q&amp;A: Authoring and presenting university courses with Emacs and a full libre software stack</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=d3Q1BgLhlj0">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/tjHQVNVaTa8dd9cfAXXQ3W">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">4</td>
<td class="org-left">Teaching computer and data science with literate programming tools</td>
<td class="org-left"><a href="https://youtu.be/U15zUNBz2CU">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/b4eLjcLo9vcewVTzrv95L8">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">5</td>
<td class="org-left">Q&amp;A: Teaching computer and data science with literate programming tools</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=eP4CIw_L4Mw">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/fCxBagZrbR5QrgD9YcrtTJ">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">6</td>
<td class="org-left">Who needs Excel? Managing your students qualifications with org-table</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=UzDqOrFGWbw">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/p8K8mtayv2HYtw1gK3zUwR">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">7</td>
<td class="org-left">one.el: the static site generator for Emacs Lisp Programmers</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=AaRCuN0flRE">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/x2yYYWLHQe75FTV8sWiDmy">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">8</td>
<td class="org-left">Q&amp;A: one.el: the static site generator for Emacs Lisp Programmers</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=Uq5IbkI7G7A">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/2b6Jdhxd2PBekPRCNv9c21">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">9</td>
<td class="org-left">Emacs turbo-charges my writing</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=HxlEK6W7RyA">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/ke3UCJaJSLyQr7Emv8VxST">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">10</td>
<td class="org-left">Q&amp;A: Emacs turbo-charges my writing</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=CEflOQRvLiw">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/dC3RnBATHdZwgfjD9QKgZY">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">11</td>
<td class="org-left">Why Nabokov would use Org-Mode if he were writing today</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=-E_uNxwL2_I">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/bDou9TDETryMt18KcdB56A">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">12</td>
<td class="org-left">Q&amp;A: Why Nabokov would use Org-Mode if he were writing today</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=uPemMZV1r-0">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/7BVMF29fRn3JAeRsuCnKnz">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">13</td>
<td class="org-left">Collaborative data processing and documenting using org-babel</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=fz7-Kd83IjM">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/7AAwoawr5MXNSrqiHJQoak">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">14</td>
<td class="org-left">How I play TTRPGs in Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=KUMkj9HWiEY">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/oNkcCHdWCKXRv6KnUTAeEC">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">15</td>
<td class="org-left">Q&amp;A: How I play TTRPGs in Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=bC6KWTR1Zz4">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/rzufpMjdQyGnqNBKC7jKXp">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">16</td>
<td class="org-left">Org-Mode workflow: informal reference tracking</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=qx1yeJ1Exrw">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/cYpEatASFWXLzDfKH4Fhec">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">17</td>
<td class="org-left">(Un)entangling projects and repos</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=o9j4IwJsvPI">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/wLxyZBoFAad575Lp4PGyoF">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">18</td>
<td class="org-left">Emacs development updates</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=SPSoRZVJUf8">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/57HSebb9a9JZynh2B3ehze">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">19</td>
<td class="org-left">Emacs core development: how it works</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=2izQJiuL0vA">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/m4XmrmE9Geat54AKT1RQaH">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">20</td>
<td class="org-left">Top 10 ways Hyperbole amps up Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=BysjfL25Nlc">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/4Cpb89zHKgQjob3gHUs73C">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">21</td>
<td class="org-left">Using Koutline for stream of thought journaling</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=dO-gv898Vmg">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/vV7qtK176DVE6RLXrZ18Ee">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">22</td>
<td class="org-left">Parallel text replacement</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=fUbBIWOJFh4">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/t3G5zo35epS6HvVot9MdZv">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">23</td>
<td class="org-left">Q&amp;A: Parallel text replacement</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=lKpvJqRXu-E">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/hQtZZfYvzoUsDxSySigK33">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">24</td>
<td class="org-left">Eat and Eat powered Eshell, fast featureful terminal inside Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=KQ5Jt-63G9U">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/t4pPDtbXiZdHHEyWJVUtNs">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">25</td>
<td class="org-left">The browser in a buffer</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=mp6gaVjmKIU">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/1quXfJqC9bh9VxkA9UC21x">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">26</td>
<td class="org-left">Speedcubing in Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=Q5HPmyaiu4g">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/2DYX2o8kB1Rv8Mqaj7H1Dx">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">27</td>
<td class="org-left">Emacs MultiMedia System (EMMS)</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=kII413hkyis">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/ppdF62LysvxpXgZVaeF9wk">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">28</td>
<td class="org-left">Q&amp;A: Emacs MultiMedia System (EMMS)</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=KJGBASdI2JI">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/b5xiqdxWCGyCo2cdsK3v9h">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">29</td>
<td class="org-left">Programming with steno</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=McHurKmk-rQ">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/1xodScC6DPkfbnqG5FmbB3">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">30</td>
<td class="org-left">Mentoring VS-Coders as an Emacsian (or How to show not tell people about the wonders of Emacs)</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=44rt1f1llhQ">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/sV9eKtGiPYZi5urxjoqerv">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">31</td>
<td class="org-left">Q&amp;A: Mentoring VS-Coders as an Emacsian (or How to show not tell people about the wonders of Emacs)</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=Bpv9JxUO4GQ">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/fwef53HxTKFe8ox4rz6fv8">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">32</td>
<td class="org-left">Emacs saves the Web (maybe)</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=kqOZwsylo48">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/fvzGU4cQQ2meZVKNGEHMht">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">33</td>
<td class="org-left">Q&amp;A: Emacs saves the Web (maybe)</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=p5dsKRWrF3s">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/9XrHVFwLeTVNQnkerSLvUt">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">34</td>
<td class="org-left">Sharing Emacs is Caring Emacs: Emacs education and why I embraced video</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=-tx72HJNfOc">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/3b5XfkceUaRjJuN5Pumgee">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">35</td>
<td class="org-left">Q&amp;A: Sharing Emacs is Caring Emacs: Emacs education and why I embraced video</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=GbFFhj0pvCE">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/w5vRTCwyMHJM3WHAgnMpBZ">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">36</td>
<td class="org-left">MatplotLLM, iterative natural language data visualization in org-babel</td>
<td class="org-left"><a href="https://youtu.be/LhhFA5i_Os4">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/7bwq1vAqYzY24iEMYAdcB1">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">37</td>
<td class="org-left">Enhancing productivity with voice computing</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=Z7l1ImjXOWM">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/vYHj7iSYhUbTxDv93NvzzY">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">38</td>
<td class="org-left">Q&amp;A: Enhancing productivity with voice computing</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=7E6gtxlbk3I">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/dNTjvu2RDXBckTu8ZVsWvC">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">39</td>
<td class="org-left">LLM clients in Emacs, functionality and standardization</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=HN3Y75D4tEs">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/ck1LWXvRiAGNLWFA8s4Ymi">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">40</td>
<td class="org-left">Q&amp;A: LLM clients in Emacs, functionality and standardization</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=jLvuR3xjoOs">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/54Dvx1e93HvpyeHULcYo5Z">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">41</td>
<td class="org-left">Improving compiler diagnostics with overlays</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=g7mwN5QtcmA">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/5fJkawU4R9b1dJq5BcDykx">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">42</td>
<td class="org-left">Q&amp;A: Improving compiler diagnostics with overlays</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=pjnil_gZpf8">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/teTgghXfTj1cwwPhTzqfve">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">43</td>
<td class="org-left">Editor Integrated REPL Driven Development for all languages</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=1bk0pqpMCfQ">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/74srjNx1cgMr5MsJ9NWNNi">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">44</td>
<td class="org-left">REPLs in strange places: Lua, LaTeX, LPeg, LPegRex, TikZ</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=lGjfzfC1CH0">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/oAjqkLNfo9B63EE1G6cJJV">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">45</td>
<td class="org-left">Literate Documentation with Emacs and Org Mode</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=BAFZ-vTnfSo">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/8ak16Qy1tjeFEqmcnan6MQ">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">46</td>
<td class="org-left">Q&amp;A: Literate Documentation with Emacs and Org Mode</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=2g8Px71GEq8">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/6VJjyqjPnCw2Pd4hVZGYvB">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">47</td>
<td class="org-left">Windows into Freedom</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=7aVgVd2_HTs">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/4DeRkvJyKFdCBLWnHtsZW2">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">48</td>
<td class="org-left">Bringing joy to Scheme programming</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=F-H3YQywr-4">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/4moUfTEo2G8we5JuLGArWx">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">49</td>
<td class="org-left">Q&amp;A: Bringing joy to Scheme programming</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=GQY64ngbiF8">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/1t3hLzUmaNqtjE9aQXFbad">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">50</td>
<td class="org-left">GNU Emacs: A World of Possibilities</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=T5yZZK18w5w">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/jFaSuNYt2FqibtcAvmVdbF">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">51</td>
<td class="org-left">Q&amp;A: GNU Emacs: A World of Possibilities</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=bxBfbd4ezU8">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/bBtwrYpKWdffVRakQCf27F">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">52</td>
<td class="org-left">A modern Emacs look-and-feel without pain</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=E1u6DcHis9M">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/1DRDY8vZK3SW5M8zAPJQSp">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">53</td>
<td class="org-left">The Emacsen family, the design of an Emacs and the importance of Lisp</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=7SGcLpjC5CE">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/qgJ84RLV2FZYyeSusDskwU">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">54</td>
<td class="org-left">Q&amp;A: The Emacsen family, the design of an Emacs and the importance of Lisp</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=wHhqq30bR60">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/97CWyEHugrureSjCSWHGMy">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">55</td>
<td class="org-left">emacs-gc-stats: Does garbage collection actually slow down Emacs?</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=YA1RJxH4xfQ">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/ngenUPBLDDkZGmsxK8vimJ">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">56</td>
<td class="org-left">Q&amp;A: emacs-gc-stats: Does garbage collection actually slow down Emacs?</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=X2rsy-WXUFI">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/mkgAZPPGB8yLmNns3W193K">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">57</td>
<td class="org-left">hyperdrive.el: Peer-to-peer filesystem in Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=7tcpmZrvz9w">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/9wLA55XACiGnS3nNBNwsV5">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">58</td>
<td class="org-left">Q&amp;A: hyperdrive.el: Peer-to-peer filesystem in Emacs</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=C7IikdsdXtg">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/dTxH3GjrJi4hXVz7i72G34">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">59</td>
<td class="org-left">Writing a language server in OCaml for Emacs, fun, and profit</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=VhUIS55UbQs">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/jgMzmGyx4H1YDwc5n1eRZu">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">60</td>
<td class="org-left">Q&amp;A: Writing a language server in OCaml for Emacs, fun, and profit</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=L0w2_63c05A">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/umt4tcX8ufvM7xEVoe7KgC">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">61</td>
<td class="org-left">What I learned by writing test cases for GNU Hyperbole</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=maNQSKxXIzI">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/4XmcGSe3TQrJJNUqQXqK2B">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">62</td>
<td class="org-left">Q&amp;A: What I learned by writing test cases for GNU Hyperbole</td>
<td class="org-left"><a href="https://youtube.com/watch?v=EQFpZQoqtYI">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/qBmDg6Dm5ppCEdnKVEm7ih">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">63</td>
<td class="org-left">EmacsConf.org: How we use Org Mode and TRAMP to organize and run a multi-track conference</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=uTregv3rNl0">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/eX2dXG3xMtUHuuBz4fssGT">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">64</td>
<td class="org-left">Q&amp;A: EmacsConf.org: How we use Org Mode and TRAMP to organize and run a multi-track conference</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=nhCe5k_smZA">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/18zetSzZEs6cfARaKLqiPa">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">65</td>
<td class="org-left">Saturday opening remarks</td>
<td class="org-left"><a href="https://youtu.be/piEHmLVtG6A">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/wEZX2JkDFpFqNFXnYeQTyb">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">66</td>
<td class="org-left">Saturday closing remarks</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=6OOUfBF6t7k">YouTube</a></td>
<td class="org-left"><a href="https://www.youtube.com/playlist?list=PLomc4HLgvuCUdrW3JkugtKv8xPelUoOyP">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">67</td>
<td class="org-left">Sunday opening remarks</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=B3NKI5Mviq8">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/9zjMPEZz1nYokiY7rk4wYv">Toobnix</a></td>
</tr>

<tr>
<td class="org-right">68</td>
<td class="org-left">Sunday closing remarks</td>
<td class="org-left"><a href="https://www.youtube.com/watch?v=qqvelKB5v0c">YouTube</a></td>
<td class="org-left"><a href="https://toobnix.org/w/p/nMXCCJ25wxKUtbuQiwkakA">Toobnix</a></td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<div id="outline-container-org8a4082e" class="outline-2">
<h3 id="org8a4082e">YouTube</h3>
<div class="outline-text-2" id="text-org8a4082e">
<p>
 I bulk-added the Youtube videos to the playlist. The videos were not
in order because I uploaded some late submissions and forgotten
videos, which then got added to the end of the list.
</p>

<p>
I tried using the API to sort the playlist. This got it most of the
way there, and then I sorted the rest by hand. 
</p>

<p>
</p><details><summary>emacsconf-extract-youtube-api-sort-playlist: Try to roughly sort the playlist.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-extract-youtube-api-sort-playlist</span> (<span class="org-type">&amp;optional</span> dry-run-only)
  <span class="org-doc">"Try to roughly sort the playlist."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">setq</span> emacsconf-extract-youtube-api-playlist (seq-find (<span class="org-keyword">lambda</span> (o) (<span class="org-keyword">let-alist</span> o (string= .snippet.title (concat emacsconf-name <span class="org-string">" "</span> emacsconf-year))))
                                        (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">items</span> emacsconf-extract-youtube-api-playlists)))
  (<span class="org-keyword">setq</span> emacsconf-extract-youtube-api-playlist-items
        (emacsconf-extract-youtube-api-paginated-request (concat <span class="org-string">"https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet,contentDetails,status&amp;forMine=true&amp;order=date&amp;maxResults=100&amp;playlistId="</span>
                                                (url-hexify-string (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">id</span> emacsconf-extract-youtube-api-playlist)))))
  (<span class="org-keyword">let*</span> ((playlist-info emacsconf-extract-youtube-api-playlists)
         (playlist-items emacsconf-extract-youtube-api-playlist-items)
         (info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))
         (slugs (seq-map (<span class="org-keyword">lambda</span> (o) (plist-get o <span class="org-builtin">:slug</span>)) info))
         (position (1- (length playlist-items)))
         result)
    <span class="org-comment-delimiter">;; </span><span class="org-comment">sort items</span>
    (mapc (<span class="org-keyword">lambda</span> (talk)
            (<span class="org-keyword">when</span> (plist-get talk <span class="org-builtin">:qa-youtube-id</span>)
              <span class="org-comment-delimiter">;; </span><span class="org-comment">move the q &amp; a</span>
              (<span class="org-keyword">let</span> ((video-object (emacsconf-extract-youtube-find-url-video-in-list
                                   (plist-get talk <span class="org-builtin">:qa-youtube-url</span>)
                                   playlist-items)))
                (<span class="org-keyword">let-alist</span> video-object
                  (<span class="org-keyword">cond</span>
                   ((null video-object)
                    (message <span class="org-string">"Could not find video for %s"</span> (plist-get talk <span class="org-builtin">:slug</span>)))
                   <span class="org-comment-delimiter">;; </span><span class="org-comment">not in the right position, try to move it</span>
                   ((&lt; .snippet.position position)
                    (<span class="org-keyword">let</span> ((video-id .id)
                          (playlist-id .snippet.playlistId)
                          (resource-id .snippet.resourceId))
                      (message <span class="org-string">"Trying to move %s Q&amp;A to %d from %d"</span> (plist-get talk <span class="org-builtin">:slug</span>) position .snippet.position)
                      (add-to-list <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">result</span> (list (plist-get talk <span class="org-builtin">:slug</span>) <span class="org-string">"answers"</span> .snippet.position position))
                      (<span class="org-keyword">unless</span> dry-run-only
                        (plz <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">put</span> <span class="org-string">"https://www.googleapis.com/youtube/v3/playlistItems?part=snippet"</span>
                          <span class="org-builtin">:headers</span> <span class="org-highlight-quoted-quote">`</span>((<span class="org-string">"Authorization"</span> . ,(url-oauth-auth <span class="org-string">"https://youtube.googleapis.com/youtube/v3/"</span>))
                                     (<span class="org-string">"Accept"</span> . <span class="org-string">"application/json"</span>)
                                     (<span class="org-string">"Content-Type"</span> . <span class="org-string">"application/json"</span>))
                          <span class="org-builtin">:body</span> (json-encode
                                 <span class="org-highlight-quoted-quote">`</span>((id . ,video-id)
                                   (snippet
                                    (playlistId . ,playlist-id)
                                    (resourceId . ,resource-id)
                                    (position . ,position))))))))))
                (<span class="org-keyword">setq</span> position (1- position))))
            <span class="org-comment-delimiter">;; </span><span class="org-comment">move the talk if needed</span>
            (<span class="org-keyword">let</span> ((video-object
                   (emacsconf-extract-youtube-find-url-video-in-list
                    (plist-get talk <span class="org-builtin">:youtube-url</span>)
                    playlist-items)))
              (<span class="org-keyword">let-alist</span> video-object
                (<span class="org-keyword">cond</span>
                 ((null video-object)
                  (message <span class="org-string">"Could not find video for %s"</span> (plist-get talk <span class="org-builtin">:slug</span>)))
                 <span class="org-comment-delimiter">;; </span><span class="org-comment">not in the right position, try to move it</span>
                 ((&lt; .snippet.position position)
                  (<span class="org-keyword">let</span> ((video-id .id)
                        (playlist-id .snippet.playlistId)
                        (resource-id .snippet.resourceId))
                    (message <span class="org-string">"Trying to move %s to %d from %d"</span> (plist-get talk <span class="org-builtin">:slug</span>) position .snippet.position)
                    (add-to-list <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">result</span> (list (plist-get talk <span class="org-builtin">:slug</span>) <span class="org-string">"main"</span> .snippet.position position))
                    (<span class="org-keyword">unless</span> dry-run-only
                      (plz <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">put</span> <span class="org-string">"https://www.googleapis.com/youtube/v3/playlistItems?part=snippet"</span>
                        <span class="org-builtin">:headers</span> <span class="org-highlight-quoted-quote">`</span>((<span class="org-string">"Authorization"</span> . ,(url-oauth-auth <span class="org-string">"https://youtube.googleapis.com/youtube/v3/"</span>))
                                   (<span class="org-string">"Accept"</span> . <span class="org-string">"application/json"</span>)
                                   (<span class="org-string">"Content-Type"</span> . <span class="org-string">"application/json"</span>))
                        <span class="org-builtin">:body</span> (json-encode
                               <span class="org-highlight-quoted-quote">`</span>((id . ,video-id)
                                 (snippet
                                  (playlistId . ,playlist-id)
                                  (resourceId . ,resource-id)
                                  (position . ,position))))))
                    ))))
              (<span class="org-keyword">setq</span> position (1- position))))
          (nreverse info))
    result))
</pre></div></details>
<p></p>

<p>
 I needed to sort some of the videos manually. Trying to scroll by
dragging items to the top of the currently-displayed section of the
list was slow, and dropping the item near the top of the list so that
I could pick it up again after paging up was a little disorienting.
Fortunately, keyboard scrolling with page-up and page-down worked even
while dragging an item, so that was what I ended up doing: select the
item and then page-up while dragging.
</p>

<p>
YouTube doesn't display numbers for the playlist positions, but this
will add them. The numbers don't dynamically update when the list is
reordered, so I just re-ran the code after moving things around.
</p>

<p>
</p><details><summary>emacsconf-extract-youtube-spookfox-add-playlist-numbers: Number the playlist for easier checking.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-extract-youtube-spookfox-add-playlist-numbers</span> ()
  <span class="org-doc">"Number the playlist for easier checking.</span>
<span class="org-doc">Related: `</span><span class="org-doc"><span class="org-constant">emacsconf-extract-check-playlists</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">interactive</span>)
  (spookfox-js-injection-eval-in-active-tab <span class="org-string">"[...document.querySelectorAll('</span><span class="org-string"><span class="org-constant">ytd-playlist-video-renderer</span></span><span class="org-string">')].forEach((o, i) =&gt; { o.querySelector('</span><span class="org-string"><span class="org-constant">.number</span></span><span class="org-string">')?.remove(); let div = document.createElement('</span><span class="org-string"><span class="org-constant">div</span></span><span class="org-string">'); div.classList.add('</span><span class="org-string"><span class="org-constant">number</span></span><span class="org-string">'); div.textContent = i; o.prepend(div) }))"</span> t))
</pre></div></details>
<p></p>


<figure id="org82934b4">
<img src="https://sachachua.com/blog/2023/12/emacsconf-backstage-making-a-play-list-checking-it-twice/2023-12-11_12-57-25.png" alt="2023-12-11_12-57-25.png">

<figcaption><span class="figure-number">Figure 1: </span>Adding numbers to the Youtube playlist</figcaption>
</figure>

<p>
In retrospect, I could
probably have just cleared the playlist and then added the videos using the
in the right order instead of fiddling with inserting things.
</p>
</div>
</div>
<div id="outline-container-org9a7ad62" class="outline-2">
<h3 id="org9a7ad62">Toobnix (Peertube)</h3>
<div class="outline-text-2" id="text-org9a7ad62">
<p>
Toobnix (Peertube) doesn't seem to have a way to bulk-add videos to a
playlist (or even to bulk-set their visibility). I started trying to
figure out how to use the API, but I got stuck because my token didn't
seem to let me access unlisted videos or do other things that required
proper authentication. Anyway, I came up with this messy hack to open
the videos in sequence and add them to the playlist using Spookfox.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-extract-toobnix-set-up-playlist</span> ()
  (<span class="org-keyword">interactive</span>)
  (mapcar
   (<span class="org-keyword">lambda</span> (o)
     (<span class="org-keyword">when</span> (plist-get o <span class="org-builtin">:toobnix-url</span>)
       (browse-url (plist-get o <span class="org-builtin">:toobnix-url</span>))
       (read-key <span class="org-string">"press a key when page is loaded"</span>)
       (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('</span><span class="org-string"><span class="org-constant">.action-button-save</span></span><span class="org-string">').click()"</span> t)
       (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('</span><span class="org-string"><span class="org-constant">my-peertube-checkbox</span></span><span class="org-string">').click()"</span> t)
       (read-key <span class="org-string">"press a key when saved to playlist"</span>))
     (<span class="org-keyword">when</span> (plist-get o <span class="org-builtin">:qa-toobnix-url</span>)
       (browse-url (plist-get o <span class="org-builtin">:qa-toobnix-url</span>))
       (read-key <span class="org-string">"press a key when page is loaded"</span>)
       (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('</span><span class="org-string"><span class="org-constant">.action-button-save</span></span><span class="org-string">').click()"</span> t)
       (spookfox-js-injection-eval-in-active-tab <span class="org-string">"document.querySelector('</span><span class="org-string"><span class="org-constant">my-peertube-checkbox</span></span><span class="org-string">').click()"</span> t)
       (read-key <span class="org-string">"press a key when saved to playlist"</span>)))
   (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
</pre>
</div>

<p>
Maybe next year, I might be able to figure out how to use the APIs to
do this stuff automatically.
</p>

<p>
This code is in <a href="https://git.emacsconf.org/emacsconf-el/tree/emacsconf-extract.el">emacsconf-extract.el</a>.
</p>
</div>
</div>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2023%2F12%2Femacsconf-backstage-making-a-play-list-checking-it-twice%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry>
</feed>