<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wc="https://wilhelm.codes/ns/feed/1.0"><channel><title>Wilhelm Codes · Bash</title><link>https://wilhelm.codes/tags/bash/</link><description>Slinger of nibbles, bits and bytes. Over 25 years of professional experience as a software engineer. Love making glowing rectangles go "beep-boop".</description><generator>Hugo</generator><language>en-US</language><managingEditor>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</managingEditor><webMaster>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</webMaster><lastBuildDate>Sat, 11 Jan 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://wilhelm.codes/tags/bash/index.xml" rel="self" type="application/rss+xml"/><item><title>My Blog Publishing Setup &amp; Workflow</title><link>https://wilhelm.codes/blog/my-blog-publishing-setup/</link><pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/my-blog-publishing-setup/</guid><category>obsidian</category><category>hugo</category><category>bash</category><category>cloudflare</category><wc:kind>post</wc:kind><description>Late last year I parted ways with Hashnode as the platform of choice for my blog. They pivoted a bit too far into the generative AI space which — to me for a blogging platform — made little to no sense. What? You write your content with AI, which then trains their model only to spit out more AI-generated slop? A slop-based Ouroboros, if you will. Good luck with that, but I&amp;rsquo;ll pass thanks.</description><content:encoded><![CDATA[<p>Late last year I parted ways with <a href="https://hashnode.com/">Hashnode</a> as the platform of choice for my blog. They pivoted a bit too far into the generative AI space which —  to me for a blogging platform —  made little to no sense. What? You write your content with AI, which then trains their model only to spit out more AI-generated slop?  A slop-based <a href="https://en.wikipedia.org/wiki/Ouroboros">Ouroboros</a>, if you will. Good luck with that, but I&rsquo;ll pass thanks.</p>
<h2 id="first-a-small-rant">
  <a class="heading-link" href="#first-a-small-rant">First, a Small Rant<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Before you call me a <a href="https://en.wikipedia.org/wiki/Luddite">Luddite</a>, let&rsquo;s be clear here, I don&rsquo;t have an issue with the technology itself. I&rsquo;m an engineer. I think it all has its uses, but force-feeding it into almost every aspect of our lives with no regard to any social and environmental impacts with frequently no chance of being able to easily opt-out does not sit right with me one bit.</p>
<p>We&rsquo;ve seen this happen over the last decade with touchscreens, IoT, blockchain, NFTs, Web3 the list goes on. All promoted by the endless parade of interchangeable, nameless faceless talentless hacks and disingenuous grifters who only care about making &ldquo;BIG LINE GO UP&rdquo;.</p>
<p>It&rsquo;s all so tiring, so perhaps you can understand why I&rsquo;d immediately recoil in disgust and go my own way with an alternative I can control.</p>
<p>Or, to put it in even simpler terms:</p>
<p><img
  src="https://wilhelm.codes/blog/my-blog-publishing-setup/image-1_hu_dbfacd982ca699f4.webp"
  srcset="/blog/my-blog-publishing-setup/image-1_hu_dbfacd982ca699f4.webp 598w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="598"
  height="369"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h2 id="the-stack">
  <a class="heading-link" href="#the-stack">The &ldquo;Stack&rdquo;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>I suppose it&rsquo;s technically a &ldquo;stack&rdquo;, but it seems a bit funny to use the term when referencing a blog setup. Just as with my <a href="https://plantsm.art"><span class="wm-plant">Plant</span>🌱<span class="wm-smart">Smart</span></a> project, the goal is to automate as much as possible while keeping maintenance requirements and hard- and soft-dollar costs as low as possible. For this, I need only use a handful of tools to keep this space operational.</p>
<p>As far as the question over &ldquo;cost&rdquo;, outside of time spent, it&rsquo;s about ~$100 AUD per year for the domain name. This blog is a very small and inconsequential part of this domain, so the overall cost is absorbed by &ldquo;other stuff&rdquo;.</p>
<p>So, the greatest cost is&hellip; time? I guess 🤷</p>
<h3 id="obsidian">
  <a class="heading-link" href="#obsidian">Obsidian<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>While I won&rsquo;t be going into what <a href="">Obsidian</a> is and how it works, I will say it&rsquo;s been a personal boon in how I keep notes and track of ideas. I have a lot of fleeting / ephemeral thoughts I that would otherwise lose instantly if I didn&rsquo;t immediately jot them down using this app.</p>
<p>So, it makes perfect sense for me to use my personal <a href="https://help.obsidian.md/Getting+started/Create+a+vault">vault</a> to store all my blog content. The fact Obsidian content nothing more than Markdown files makes working with them using other bits of tech that much easier.</p>
<p>Oh, it&rsquo;s free btw.</p>
<h3 id="hugo">
  <a class="heading-link" href="#hugo">Hugo<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p><a href="https://gohugo.io/">This</a> is probably one of the more popular options when it comes to static site generators. It&rsquo;s easy to install, is incredibly extensible and has loads of documentation to help you along. The template syntax sits upon Golang&rsquo;s templating engine, so if you have a strong background as a Go developer like myself, you&rsquo;ll be right at home.</p>
<p>However, knowing Go isn&rsquo;t a hard requirement though it will make getting on top of things quite a bit easier.</p>
<p>What I enjoyed while learning about this project was I got to control every aspect of how I wanted my blog to look and function. Building the current design from the ground up with Hugo was incredibly fun.</p>
<p>Also, free. Very cool.</p>
<h3 id="cloudflare">
  <a class="heading-link" href="#cloudflare">Cloudflare<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>I already used Cloudflare to manage this domain&rsquo;s DNS settings, so it only made sense to publish my static site to Cloudflare Pages over, say, Vercel or Github Pages. There&rsquo;s nothing spectacular going on here outside of me just wanting to keep these two things in the same place.</p>
<p>That being said, I have no issues with migrating else where if the need should ever arise. Though, I doubt this blog will hit the 20k file limit of Cloudflare Pages any time soon.</p>
<p>Again, free for the purposes of this article. If you don&rsquo;t have a dedicated personal domain, you will be provided a subdomain attached to the <code>pages.dev</code> apex.</p>
<h2 id="the-good-stuff">
  <a class="heading-link" href="#the-good-stuff">The Good Stuff<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>How is this all put together for day-to-day usage? Luckily, the Cloudflare stuff runs on auto-pilot, so it&rsquo;s pretty much set-and-forget. I suppose you could say the same about the other components, but they&rsquo;re the parts I touch the most.</p>
<p>I have a single vault in Obsidian. It&rsquo;s where all my thoughts go. Stored within is a top-level <code>Blog/</code> directory. Care to hazard a guess as to what it may contain?</p>
<p><img
  src="https://wilhelm.codes/blog/my-blog-publishing-setup/image-2_hu_84ed8786abb466c3.webp"
  srcset="/blog/my-blog-publishing-setup/image-2_hu_84ed8786abb466c3.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="928"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Hugo has the concept of <a href="https://gohugo.io/content-management/page-bundles/">page bundles</a> where you can group all resources associated with your blog articles within a single directory. You&rsquo;ll notice above all directories mirror the blog posts hosted on this site. In my case, we can consider the directory names to be the human-friendly slugs you see in your address bar that point to the associated content. Within these directories, you&rsquo;d see any other resource I may link to; source code, images, etc&hellip; The best thing about this is I only have to perform a relative reference to these resources. If I want to link to <code>image-1.png</code>, I only have to reference it as <code>![](/blog/my-blog-publishing-setup/image-1.png)</code> without worrying about specifying an absolute path; very nice.</p>
<p>When I want to write a new article, I first create a new directory here. I may already have the title of the article in mind, but it can be in flux until I decide to publish it. In the new directory, I create a file called <code>index.md</code>. This file contains all the content of the associated article written using Markdown.</p>
<p>I then use an Obsidian <a href="https://help.obsidian.md/Plugins/Templates">template</a> dedicated to new blog posts and apply it to the new file. This ensures I have all <a href="https://jekyllrb.com/docs/front-matter/">front matter</a> properties ready to go. This makes configuring my posts in the editor that much easier.</p>
<p><img
  src="https://wilhelm.codes/blog/my-blog-publishing-setup/image-3_hu_7267be29c468e715.webp"
  srcset="/blog/my-blog-publishing-setup/image-3_hu_7267be29c468e715.webp 736w, /blog/my-blog-publishing-setup/image-3_hu_1ad568a578b3cf1f.webp 1104w, /blog/my-blog-publishing-setup/image-3_hu_3acce599e1729f38.webp 1472w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="478"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>All that&rsquo;s left is writing my articles! Well, not really. I need a way to synchronise these files with my local Hugo instance. As you can see <a href="https://github.com/wilhelm-murdoch/wilhelm.codes/tree/main/content/blog">here</a>, the content mirrors what I have stored in my Obsidian vault.</p>
<p>This can be easily addressed with 2 small apps:</p>
<ol>
<li><code>fswatch</code> which monitors a specified directory for changes and allows you to trigger subsequent commands for specific file system events. Basically, I want to watch for any changes in my Obsidian vaults <code>Blog/</code> directory.</li>
<li><code>rsync</code> to dynamically keep the target Hugo directory in sync with the Obsidian source directory.</li>
</ol>
<p>I, of course, have this placed in a script that I have running in the background while I write. This is a great help during the drafting phase as I have Hugo dynamically processing and displaying these changes locally. This allows me to easily proof my work visually as I carry on writing.</p>
<p>Here&rsquo;s the full script:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">SOURCE</span><span class="o">=</span><span class="si">${</span><span class="nv">SOURCE</span><span class="p">:=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> SOURCE
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DESTINATION</span><span class="o">=</span><span class="si">${</span><span class="nv">DESTINATION</span><span class="p">:=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">2</span><span class="si">}</span><span class="s2">&#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> DESTINATION
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">fswatch -o <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SOURCE</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r event<span class="p">;</span> <span class="k">do</span> 
</span></span><span class="line"><span class="cl">    rsync -av --delete <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SOURCE</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DESTINATION</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>I keep this running whenever I&rsquo;m writing a new article. Obsidian saves in almost realtime, so as I type the changes almost immediately sync across to the local Hugo server so I can review my changes.</p>
<p>You effectively run it as:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ ./sync.sh /path/to/obsidian/blog/ /path/to/hugo/content/</span></span></code></pre></div>
</div>
<p>For <code>rsync</code> to work as intended and keep the destination directory completely in sync with the source directory, remember to add a trailing slash to both directory arguments.</p>
<h3 id="no-auto-commit">
  <a class="heading-link" href="#no-auto-commit">No Auto-Commit?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Nope. Not here. There are a few other peeps with blogs out there that &ldquo;commit on change&rdquo;, but I prefer to have a bit more control over what actually gets published. For that, I simply use plain-old git commands to publish my new article:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ git add -A
</span></span><span class="line"><span class="cl">$ git commit -m <span class="s1">&#39;some banal new bullshit screed&#39;</span>
</span></span><span class="line"><span class="cl">$ git push origin main</span></span></code></pre></div>
</div>
<p>Cloudflare monitors the associated repository, picks up the change and the auto-magic-ally builds and pushes everything to the public eye.</p>
<p>With this flow, I can see my changes locally in near realtime and push to &ldquo;production&rdquo; only when I&rsquo;m happy with the results.</p>
<h2 id="in-closing-">
  <a class="heading-link" href="#in-closing-">In Closing &hellip;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>I&rsquo;m stoked to have my very own place again that I have complete control over. I&rsquo;ve thoroughly enjoyed creating this new blog design and I may even open-source it one day.</p>
<p>That said, I&rsquo;ve made a very loose commitment to post a new article every week this year of our lord 2025 and I&rsquo;m hoping this will make things heaps simpler. I only want to worry about what I&rsquo;m going to write and just get the content out there.</p>
<p>I sincerely hope I can keep this self-imposed weekly commitment. 😬👍</p>]]></content:encoded></item><item><title>Breaking Up Log Output Using Bash</title><link>https://wilhelm.codes/blog/breaking-up-log-output-with-bash/</link><pubDate>Thu, 09 Jan 2025 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/breaking-up-log-output-with-bash/</guid><category>bash</category><category>snippets</category><category>tutorials</category><wc:kind>post</wc:kind><description>I tend to look at a lot of log output throughout the day as part of my role as platform engineer. This obviously extends to any backend, or ops-related, role. One little niggle that always gets to me is tailing output where the lines only change when something interesting happens.</description><content:encoded><![CDATA[<p>I tend to look at a <em>lot</em> of log output throughout the day as part of my role as platform engineer. This obviously extends to any backend, or ops-related, role. One little niggle that always gets to me is tailing output where the lines only change when something interesting happens.</p>
<p>I have no idea if time is actually passing. Obviously, I can eyeball timestamps if available, but there are times when so many lines zip through the buffer that it&rsquo;s easy to lose track or even go cross-eyed.</p>
<p>It can be tricky to notice that lines are still being tailed if the output doesn&rsquo;t drastically change in some meaningful or noticeable way. Something I like to is intercept each line and then output some kind of divider whenever <code>n</code> lines have been added to the buffer.</p>
<p>It&rsquo;s as simple as:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">lines</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -r line<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">	<span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">line</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="o">((</span>lines++<span class="o">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="o">((</span>lines % <span class="nv">5</span> <span class="o">==</span> 0<span class="o">))</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">		<span class="nb">echo</span> <span class="s2">&#34;----------&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>Effectively, all this does is:</p>
<ol>
<li>Intercept each line of output being piped into the script.</li>
<li>Keep a running tally of lines we&rsquo;ve intercepted so far; <code>$lines++</code>.</li>
<li>If the current tally is divisible by <code>n</code>, or in this case <code>5</code>, spit out an additional line containing a divider.</li>
<li>Keep doing this forever until the process is terminated.</li>
</ol>
<p>If you were to save this in a file named as <code>divider.sh</code> and set it to execute with something like <code>chmod a+x</code> you could test it by doing something like:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="k">while</span> true<span class="p">;</span> <span class="k">do</span> <span class="nb">echo</span> <span class="s2">&#34;emitting a noop&#34;</span><span class="p">;</span> sleep 1<span class="p">;</span> <span class="k">done</span> <span class="p">|</span> ./divider.sh</span></span></code></pre></div>
</div>
<p>And you&rsquo;ll see something like the following:</p>
<div class="code-block">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">----------
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">emitting a noop
</span></span><span class="line"><span class="cl">----------</span></span></code></pre></div>
</div>
<p>This is pretty dumb, but now you can see that output is still being placed in your terminal buffer. I&rsquo;ve set it to every <code>5</code> lines, but you can update the script to make that configurable. You could also play a sound when the script places a divider in the buffer using something like <code>tput bel</code>.</p>
<p>Anyway&hellip; Enjoy.</p>]]></content:encoded></item><item><title>How to Mass-Unfollow Instagram Accounts</title><link>https://wilhelm.codes/blog/how-to-mass-unfollow-instagram-accounts/</link><pubDate>Tue, 07 Jan 2025 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/how-to-mass-unfollow-instagram-accounts/</guid><category>bash</category><category>api</category><category>tutorials</category><category>snippets</category><wc:kind>post</wc:kind><description>I&amp;rsquo;m not super-active on Instagram these days and Threads was a real let down. I have a dormant Mastodon account, but find myself being quite happy on Bluesky at the moment. Who knows how long that will last. Having recently read about Meta creating loads of fake AI-based accounts, I thought it was time to do some spring cleaning.</description><content:encoded><![CDATA[<p>I&rsquo;m not super-active on Instagram these days and Threads was a real let down. I have a dormant Mastodon account, but find myself being quite happy on <a href="https://bsky.app/profile/wilhelm.codes">Bluesky</a> at the moment. Who knows how long that will last. Having recently read about Meta creating loads of <a href="https://www.404media.co/metas-ai-profiles-are-indistinguishable-from-terrible-spam-that-took-over-facebook/">fake AI-based accounts</a>, I thought it was time to do some spring cleaning.</p>
<h2 id="before-we-begin">
  <a class="heading-link" href="#before-we-begin">Before we begin<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Today, I went ahead and made my Instagram profile private. I have no desire to delete my posts just yet, but decided to go ahead and unfollow everyone as a start. There are several ways to do this:</p>
<ul>
<li>API calls using an SDK, which requires a developer account.</li>
<li>Manually deleting &ldquo;handraulically&rdquo; via the native app or browser, but that can quickly become tedious if you have more than a hundred or so follows.</li>
<li>Random &ldquo;GreaseMonkey&rdquo; scripts off the web that may, or may not, work properly if at all.</li>
<li>Reverse-engineering XHR calls from your browser and writing a simple Bash script to automate the process.</li>
</ul>
<p>If you&rsquo;ve read some of my <a href="https://wilhelm.codes/blog/why-cant-i-hold-all-these-slack-emojis/">previous</a> <a href="https://wilhelm.codes/blog/liberating-custom-slack-emojis/">blog</a> <a href="https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/">posts</a>, you probably can guess which one I&rsquo;ll be going with.</p>
<h3 id="requirements">
  <a class="heading-link" href="#requirements">Requirements<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>This guide assumes you have <em>some</em> programming or scripting knowledge.</p>
<p>You will need the following on your machine:</p>
<ul>
<li>A shell terminal with Bash, or ZSH, support.</li>
<li><code>curl</code> to make API calls from the command line.</li>
<li><code>jq</code> to easily parse JSON-based responses from said API calls. This can easily be installed using <a href="https://brew.sh/">Homebrew</a> or some other supported package manager.</li>
<li>A modern browser that supports a developer tools console to intercept XHR calls.</li>
</ul>
<p>It&rsquo;s worth mentioning that I am using a MacBook with the Chrome browser, but other webkit-based browsers should support similar functionality.</p>
<h2 id="fire-up-the-browser">
  <a class="heading-link" href="#fire-up-the-browser">Fire up the browser!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Open up your browser, or new tab, and navigate to your Instagram profile. Open your browser&rsquo;s developer console and navigate to the &ldquo;Network&rdquo; tab. Clear out whatever requests are currently listed and then filter by &ldquo;Fetch/XHR&rdquo;.</p>
<h3 id="your-following-list">
  <a class="heading-link" href="#your-following-list">Your following list<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>We need to intercept an XHR request that returns a JSON body containing some of your followers. Click the &ldquo;following&rdquo; link and monitor the resulting requests that pop in the list on the &ldquo;Network&rdquo; tab.</p>
<p><img
  src="https://wilhelm.codes/blog/how-to-mass-unfollow-instagram-accounts/image-1_hu_6f1c3624c92cee84.webp"
  srcset="/blog/how-to-mass-unfollow-instagram-accounts/image-1_hu_6f1c3624c92cee84.webp 736w, /blog/how-to-mass-unfollow-instagram-accounts/image-1_hu_d061d7b50a984152.webp 1104w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="219"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>You <em>should</em> see something like <code>following/count=12...</code> in the list. Right-click that item and select &ldquo;Copy as cURL&rdquo;. Paste the new value in your clipboard into a scratch file for reference later.</p>
<p><img
  src="https://wilhelm.codes/blog/how-to-mass-unfollow-instagram-accounts/image-2_hu_eac316580918723e.webp"
  srcset="/blog/how-to-mass-unfollow-instagram-accounts/image-2_hu_eac316580918723e.webp 736w, /blog/how-to-mass-unfollow-instagram-accounts/image-2_hu_5cf36f632fab2260.webp 1104w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="331"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>Be aware this command will contain all the headers required to make requests from your terminal on your behalf. Do NOT share these details with anyone. I haven&rsquo;t checked how long until the tokens within the auth headers expire, so assume they are long(ish)-lived and treat them accordingly.</p>
<h3 id="an-unfollow-request">
  <a class="heading-link" href="#an-unfollow-request">An unfollow request<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Next, we need to intercept an XHR request to <em>unfollow</em> someone. Going back to your following list, select a random user and unfollow them while once again keeping an eye on the &ldquo;Network&rdquo; tab.</p>
<p><img
  src="https://wilhelm.codes/blog/how-to-mass-unfollow-instagram-accounts/image-3_hu_3c8e9df334aa9ff5.webp"
  srcset="/blog/how-to-mass-unfollow-instagram-accounts/image-3_hu_3c8e9df334aa9ff5.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="508"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>You&rsquo;re going to see something like the following; an XHR event making a <code>POST</code> request to &ldquo;destroy&rdquo; your chosen follower. Once again, you&rsquo;ll right-click and &ldquo;Copy and cURL&rdquo;. Paste this into your scratch file as well for future reference.</p>
<p><img
  src="https://wilhelm.codes/blog/how-to-mass-unfollow-instagram-accounts/image-4_hu_b009422977c7ada3.webp"
  srcset="/blog/how-to-mass-unfollow-instagram-accounts/image-4_hu_b009422977c7ada3.webp 736w, /blog/how-to-mass-unfollow-instagram-accounts/image-4_hu_fbde267ec81cb211.webp 1104w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="285"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h2 id="putting-it-all-together">
  <a class="heading-link" href="#putting-it-all-together">Putting it all together<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Now that you have your two types of requests, we&rsquo;ll need to script the following steps:</p>
<ul>
<li>Execute a <code>curl</code> request to fetch a JSON body containing the accounts you follow, one page of results at a time.</li>
<li>Use <code>jq</code> to extract their Instagram IDs.</li>
<li>Store the results in a variable named&hellip; drumroll, pls&hellip; <code>$ids</code>.</li>
<li>Iterate through each <code>$id</code>.</li>
<li>Execute a <code>curl</code> request to unfollow each <code>$id</code>.</li>
</ul>
<p>Here is the meat of the script. I have included only the necessary headers required to successfully make these requests on your Instagram account&rsquo;s behalf. You&rsquo;ll need to swap out the <code>...</code> with your own values.</p>
<p>You may also notice that I&rsquo;ve change the <code>count=...</code> parameter to <code>100</code>. It may be able to go higher, but I haven&rsquo;t tested it.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">ids</span><span class="o">=</span><span class="k">$(</span>
</span></span><span class="line"><span class="cl">  curl <span class="s1">&#39;https://www.instagram.com/api/v1/friendships/.../following/?count=100&amp;hl=en&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;cookie: ...&#39;</span>           <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-asbd-id: ...&#39;</span>        <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-csrftoken: ...&#39;</span>      <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-ig-app-id: ...&#39;</span>      <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-ig-www-claim: ...&#39;</span>   <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-requested-with: ...&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-web-session-id: ...&#39;</span> <span class="p">|</span> jq -r <span class="s1">&#39;.users[].id&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -r id<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> -n <span class="s2">&#34;Unfollowing user &#39;</span><span class="si">${</span><span class="nv">id</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  curl -s -o /dev/null <span class="s2">&#34;https://www.instagram.com/api/v1/friendships/destroy/</span><span class="si">${</span><span class="nv">id</span><span class="si">}</span><span class="s2">/?hl=en&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;cookie: ...&#39;</span>           <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-asbd-id: ...&#39;</span>        <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-csrftoken: ...&#39;</span>      <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-ig-app-id: ...&#39;</span>      <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-ig-www-claim: ...&#39;</span>   <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-requested-with: ...&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    -H <span class="s1">&#39;x-web-session-id: ...&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    --data-raw <span class="s2">&#34;container_module=profile&amp;nav_chain=PolarisProfilePostsTabRoot%3AprofilePage%3A1%3Avia_cold_start%2CPolarisProfilePostsTabRoot%3AprofilePage%3A2%3Aunexpected&amp;user_id=</span><span class="si">${</span><span class="nv">id</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s1">&#39; ... done!&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span> <span class="o">&lt;&lt;&lt;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">ids</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Finished&#34;</span></span></span></code></pre></div>
</div>
<p>You can save this as a file and make it executable, but I just paste this bad boy into your terminal. The above script will remove <code>100</code> accounts at a time. Just keep executing the script until you no longer see results like the following:</p>
<div class="code-block">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Unfollowing user &#39;111&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;222&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;333&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;444&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;555&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;666&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;777&#39;... done
</span></span><span class="line"><span class="cl">Unfollowing user &#39;888&#39;... done
</span></span><span class="line"><span class="cl">Finished</span></span></code></pre></div>
</div>
<h3 id="caveats-gotchas--other-things">
  <a class="heading-link" href="#caveats-gotchas--other-things">Caveats, gotchas &amp; other things<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>I&rsquo;ve only had a few hundred accounts that I followed, so I didn&rsquo;t feel the need to make this a bullet-proof solution. There&rsquo;s quite a bit missing and things can go wrong. Here are some things to consider off the top of my head :</p>
<ul>
<li>The session tokens will expire eventually, so you will have to refer back to your &ldquo;Network&rdquo; tab in your developer tools console to get new ones.</li>
<li>As this example only unfollows a static amount at a time, re-executing the script may become tedious for very large follow numbers. Perhaps, you can modify this script to add some basic pagination.</li>
<li>There is no error handling here, so keep an eye out for potential rate limiting or expired token issues with the Instagram API.</li>
<li>Constantly updating session header values can be made a bit simpler by resorting to variables instead.</li>
</ul>
<h2 id="in-closing-">
  <a class="heading-link" href="#in-closing-">In closing &hellip;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Cleaning out old, unused or unwanted social media accounts can be liberating. I&rsquo;ve recently gutted my Twitter presence and I haven&rsquo;t been on Facebook in years, though I still do use Messenger to keep in touch with a handful of people. I&rsquo;d prefer using Signal for this, but trying to convince other people to install yet another messaging app is like pulling teeth.</p>
<p>Anyway, I actually enjoy writing these kinds of posts. What other social networks should I cover? LinkedIn? Threads? What are some ways you can improve and build on the above? Lemme know in the comment section below.</p>]]></content:encoded></item><item><title>Learning Bash Through Pointless Fun</title><link>https://wilhelm.codes/blog/learning-bash-through-pointless-fun/</link><pubDate>Wed, 05 Oct 2022 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/learning-bash-through-pointless-fun/</guid><category>bash</category><wc:kind>post</wc:kind><description>Responding to company chat messages in a mocking and sarcastic tone is one of my favourite past times. Classic engineer snark as it were. We&amp;rsquo;re all in on the joke. However, the artful nuances of snark via online chat tend to get confused by others; Did they mean to sound sarcastic?</description><content:encoded><![CDATA[<p>Responding to company chat messages in a mocking and sarcastic tone is one of my favourite past times. Classic engineer snark as it were. We&rsquo;re all in on the joke. However, the artful nuances of snark via online chat tend to get confused by others; Did they <em>mean</em> to sound sarcastic?</p>
<p>Let&rsquo;s leave out all doubt regarding our god-tier levels of snarkiness.</p>
<p>Today, we&rsquo;re going to write a laughably-simple Bash script that lets our coworkers know <em><a href="https://knowyourmeme.com/memes/mocking-spongebob">exactly</a></em> what we think. Along the way, you&rsquo;ll learn a little bit more about Bash such as:</p>
<ol>
<li>Setting some default flags.</li>
<li>Reading text from <code>stdin</code>.</li>
<li>Parameter expansion.</li>
<li>Looping through each character of a string.</li>
<li>Creating random numbers.</li>
<li>Swapping character types using the <code>tr</code> command.</li>
<li>And, finally, making your long-suffering coworkers&rsquo; eyes roll.</li>
</ol>
<hr>
<h2 id="here-we-go">
  <a class="heading-link" href="#here-we-go">Here We Go!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>First, I&rsquo;m going to just paste the entire script here and then we&rsquo;ll go through all the important bits line-by-line:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -eo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">read</span> text
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="o">((</span> <span class="nv">i</span><span class="o">=</span>0<span class="p">;</span> i &lt; <span class="s2">&#34;</span><span class="si">${#</span><span class="nv">text</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> i++ <span class="o">))</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="k">$((</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">RANDOM</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">%</span> <span class="m">2</span> <span class="k">))</span> -eq <span class="m">0</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">text</span><span class="p">:</span><span class="si">${</span><span class="nv">i</span><span class="si">}</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">text</span><span class="p">:</span><span class="si">${</span><span class="nv">i</span><span class="si">}</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> tr <span class="s1">&#39;[:upper:]&#39;</span> <span class="s1">&#39;[:lower:]&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">echo</span></span></code></pre></div>
</div>
<p>Alright, let&rsquo;s take it from the top. All Bash scripts should start with a <a href="https://en.wikipedia.org/wiki/Shebang_(Unix)">“shebang line”</a>. This tells your terminal which environments and runtimes your script should run under. There are more traditional versions of this — <code>#!/bin/bash</code>, for instance — but, our way is considered to be the most portable overall.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env bash</span></span></span></code></pre></div>
</div>
<p>Next, we setup some environmental flags. I use at least the following for <em>all</em> of my personal scripts:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">set</span> -eo pipefail</span></span></code></pre></div>
</div>
<p>Here is what they do:</p>
<ol>
<li><code>set -e</code>: Instructs Bash to immediately exit if any command has a non-zero exit status. This is how it works in most languages, but with Bash, it just keeps on trying to execute subsequent commands. This is generally acceptable on the command line, but not in a script. If we encounter an error, we want to exit immediately.</li>
<li><code>set -o pipefail</code>: This tells Bash <em>not</em> to mask errors that may appear in a pipeline of commands. We want any failed command&rsquo;s exit code in a pipeline to bubble up to the script itself and then exit with that code.</li>
</ol>
<p>Speaking of pipelines, we want to be able to pipe text to this script so we can do something like the following:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;wilhelm, i asked you to patch the server.&#34;</span> <span class="p">|</span> ./spongebob
</span></span><span class="line"><span class="cl">wiLHELm, I AsKEd yOu TO PatCh The seRvEr.</span></span></code></pre></div>
</div>
<p>Let&rsquo;s halt the script and wait for user input and then assign that input to variable <code>text</code>:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">read</span> text</span></span></code></pre></div>
</div>
<p>Now that we have some text, we need to start randomly-swapping between upper and lower case characters. In order to do that, we need to know the number of characters in our string so we can build a nice loop:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">for</span> <span class="o">((</span> <span class="nv">i</span><span class="o">=</span>0<span class="p">;</span> i &lt; <span class="s2">&#34;</span><span class="si">${#</span><span class="nv">text</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> i++ <span class="o">))</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ... sweet code goes here </span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>We could do something in a sub-shell here like <code>$(echo &quot;${text}&quot; | wc -c)</code> to get the character count, but why do that when we could get the same result with <code>&quot;${#text}&quot;</code>. This lovely bit of <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">“shell parameter expansion”</a> helps us avoid sub-commands and sub-shells.</p>
<p>Next, we want to be able to randomly swap the capitalisation of each character in the string to get the appropriate effect. A character is either upper- or lower-case, so minimum we need only to swap randomly between 2 values; <code>0</code> and <code>1</code>. We can get this effect in Bash by using the internal <a href="https://tldp.org/LDP/abs/html/randomvar.html"><code>${RANDOM}</code> function</a> like so with the <a href="https://tldp.org/LDP/abs/html/ops.html">modulo</a> ( or “mod” ) operator:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="k">$((</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">RANDOM</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">%</span> <span class="m">2</span> <span class="k">))</span> -eq <span class="m">0</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ... do something</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ... do the opposite</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div>
</div>
<p>We&rsquo;re now at the point where we want to modify the character associated with the <code>for</code> loop&rsquo;s current iteration, but how do we get it from the <code>text</code> variable? Once again we use some parameter expansion in the form of <code>${text:offset:length}</code>. We have the value for “offset” already; it&rsquo;s <code>${i}</code>. We only want a single character returned, so we use <code>1</code> for “length”.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">text</span><span class="p">:</span><span class="si">${</span><span class="nv">i</span><span class="si">}</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span></span></span></code></pre></div>
</div>
<p>This spits out the current character for each iteration of our loop. The <code>-n</code> in the <code>echo</code> statement simply stops Bash from adding a newline to the end of the result. Otherwise, you&rsquo;d get a line per character as output.</p>
<p>Finally, we want to do case swapping. For this, we pipe the output of the above <code>echo</code> command into the <code>tr</code> command. Within the above <code>if</code> statement, if our random number equals <code>0</code>, let&rsquo;s swap a <em>lowercase</em> <code>[:lower:]</code> character with an <em>uppercase</em> <code>[:upper:]</code>:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">text</span><span class="p">:</span><span class="si">${</span><span class="nv">i</span><span class="si">}</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span></span></span></code></pre></div>
</div>
<p>And, then, we do the opposite for any other result:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">text</span><span class="p">:</span><span class="si">${</span><span class="nv">i</span><span class="si">}</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> tr <span class="s1">&#39;[:upper:]&#39;</span> <span class="s1">&#39;[:lower:]&#39;</span></span></span></code></pre></div>
</div>
<p>You&rsquo;ll notice a final <code>echo</code> command at the bottom of the script. Thanks to the final <code>echo -n ...</code> command from the previous <code>for</code> loop, you may find your results prepended to your command prompt. This ensures a newline makes it to the end of your 🧽 output.</p>
<h2 id="testing-time">
  <a class="heading-link" href="#testing-time">Testing Time<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Save the script as <code>spongebob</code> and make it executable with <code>chmod a+x spongebob</code>. That should be it! Here are a few of my results:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s2">&#34;abandon all hope, ye who enter here.&#34;</span> <span class="p">|</span> ./spongebob
</span></span><span class="line"><span class="cl">abaNDON ALl Hope, yE wHO EnTer heRE.
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s2">&#34;Wilhelm, that last deployment failed. Could you roll it back, please?&#34;</span> <span class="p">|</span> ./spongebob
</span></span><span class="line"><span class="cl">WilhelM, THAT LASt dEployMeNT fAILED. coUlD YOu RoLL It BaCk, PlEaSE?
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s2">&#34;Wilhelm, I am your manager. Please, stop mocking me.&#34;</span> <span class="p">|</span> ./spongebob
</span></span><span class="line"><span class="cl">wIlHElM, i aM yOUr MANagER. PlEasE, sTOP mOCKINg mE.
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> <span class="s2">&#34;Wilhelm, should we use Kubernetes for this?&#34;</span> <span class="p">|</span> ./spongebob
</span></span><span class="line"><span class="cl">WilHELM, should We Use KuBerNeTEs <span class="k">for</span> THiS?</span></span></code></pre></div>
</div>
<h2 id="in-conclusion">
  <a class="heading-link" href="#in-conclusion">In Conclusion…<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Ok, obviously this was all a clever ploy to get you to learn a few more Bash things. Definitely do <em>not</em> use this new knowledge to frustrate and annoy your coworkers. Please, be considerate of other people&rsquo;s mental well being.
I mean, what I <em>meant</em> to say was:</p>
<figure class="pull-quote">
  <span class="pull-quote-mark" aria-hidden="true"><span class="icon "><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M108 72v72H40a8 8 0 0 1-8-8V72a8 8 0 0 1 8-8h60a8 8 0 0 1 8 8m108-8h-60a8 8 0 0 0-8 8v64a8 8 0 0 0 8 8h68V72a8 8 0 0 0-8-8" opacity=".2"/><path d="M100 56H40a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h60v8a32 32 0 0 1-32 32a8 8 0 0 0 0 16a48.05 48.05 0 0 0 48-48V72a16 16 0 0 0-16-16m0 80H40V72h60Zm116-80h-60a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h60v8a32 32 0 0 1-32 32a8 8 0 0 0 0 16a48.05 48.05 0 0 0 48-48V72a16 16 0 0 0-16-16m0 80h-60V72h60Z"/></g></svg></span></span>
  <blockquote>
    <p>DefInIteLY dO Not UsE THis nEw knoWleDGE to FRUStRATe aND aNnOY YoUR cOWorkers. pLeAsE, be ConsiDeRAtE oF oThEr peOPle's mEnTal well beiNG.</p>
  </blockquote>
  <figcaption>
    <cite>Me.</cite>
  </figcaption>
</figure>

<p>There are any number of ways you could change this script. Instead of liberal use of <code>echo</code>, you could just build a string assigned to a variable and spit that out at the end. Instead of using <code>else</code> you could use <code>continue</code> to skip the final <code>if</code> fallback. Try a few and see what changes.</p>
<p>For the purposes of this article I felt the above sequence of commands was clear enough for most people to follow along.</p>
<p>I hope you learned something!</p>]]></content:encoded></item><item><title>Filtering Docker Containers with jq</title><link>https://wilhelm.codes/blog/filtering-docker-containers-with-jq/</link><pubDate>Thu, 29 Sep 2022 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/filtering-docker-containers-with-jq/</guid><category>docker</category><category>js</category><category>json</category><category>bash</category><wc:kind>post</wc:kind><description>As is the case with any seasoned DevOps engineer, I have a set of tools in my kit that I simply cannot live without. If I had to distill them into a top-5 list it would be the following.</description><content:encoded><![CDATA[<p>As is the case with any seasoned DevOps engineer, I have a set of tools in my kit that I simply cannot live without. If I had to distill them into a top-5 list it would be the following.</p>
<ol>
<li>Bash for portability.</li>
<li><a href="https://stedolan.github.io/jq/">jq</a> to filter JSON objects from RESTful APIs.</li>
<li>AWS CLI as I cannot stand dealing with the web console.</li>
<li><code>curl</code> ( with <code>wget</code> as a reasonable fallback ) to interact with various APIs.</li>
<li><a href="https://www.terraform.io/">Terraform</a> for <em>most</em> of my IaC needs.</li>
</ol>
<p>It wouldn&rsquo;t be an exaggeration to say I <em>literally</em> use these tools <em>every</em> day. Specifically, the first 3 items. Furthermore, to say that manually sorting through Docker containers is a practice in tedium would be a massive understatement. Because of this, not a day passes where I am not grateful to have found <code>jq</code>.</p>
<p>In this case, my favourite duo <em>is</em> Docker and <code>jq</code>&hellip; which is what this article is about. Let&rsquo;s learn some fun filtering patterns to make your life a bit easier if you&rsquo;re stuck in a terminal all day like myself.</p>
<h2 id="setting-up">
  <a class="heading-link" href="#setting-up">Setting Up<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>First and foremost, you&rsquo;re going to need to install <code>jq</code> in your test environment. If you&rsquo;re on most common Linux distributions:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-get install jq</span></span></code></pre></div>
</div>
<p>Or, on MacOS:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew update <span class="o">&amp;&amp;</span> brew install jq</span></span></code></pre></div>
</div>
<p>I&rsquo;m assuming that if you&rsquo;re reading this you already have Docker installed in your test environment. If not, head out to their website and read their <a href="https://docs.docker.com/get-docker/">installation documentation</a>. It tends to vary wildly depending on your OS.</p>
<p>Let&rsquo;s spin up some containers we can play around with. I use <code>redis</code> for stuff like this:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">for</span> i in <span class="o">{</span>1..5<span class="o">}</span><span class="p">;</span> <span class="k">do</span> docker run -it --rm -d redis<span class="p">;</span> <span class="k">done</span></span></span></code></pre></div>
</div>
<h2 id="environmental-variables">
  <a class="heading-link" href="#environmental-variables">Environmental Variables<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<h3 id="sourceable-blocks">
  <a class="heading-link" href="#sourceable-blocks">Sourceable Blocks<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>There may be times I wish to collate all environmental variables associated with a set of containers and stick them a <code>.env</code> file to source later. Here we create a block for all the <code>redis</code> containers we just spun up:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[].Config.Env
</span></span></span><span class="line"><span class="cl"><span class="s1">  | flatten 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .[]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>This one will generate a lot of duplicates as all our test containers are effectively clones. What if we want a distinct set of instead? We can use the <code>unique</code> filter:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">	[
</span></span></span><span class="line"><span class="cl"><span class="s1">	  .[].Config.Env
</span></span></span><span class="line"><span class="cl"><span class="s1">	] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | flatten 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | unique 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .[]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>What if I want a set from specific containers?</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    [
</span></span></span><span class="line"><span class="cl"><span class="s1">	    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">      | select([.Id] | inside([
</span></span></span><span class="line"><span class="cl"><span class="s1">	      &#34;747c2c92855f88a8e9aa9709dcdf3a01f1677e70bf4c0bf0e520eb38ac502876&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">	      &#34;2cf70261ef62bc36d19aba02f8ef7b8d7aabfcb1e9f4593a919baa17f80aca5b&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">	    ]))
</span></span></span><span class="line"><span class="cl"><span class="s1">      | .Config.Env
</span></span></span><span class="line"><span class="cl"><span class="s1">	] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | flatten 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | unique 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .[]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>How about filtering by a specific ip address for the default <code>bridge</code> network? Using a different network? Just replace <code>bridge</code> with the alternate network name.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select(.NetworkSettings.Networks.bridge.IPAddress == &#34;172.17.0.2&#34;) 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .Config.Env 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .[]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>Multiple ip addresses?</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select([.NetworkSettings.Networks.bridge.IPAddress] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | inside([
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;172.17.0.2&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;172.17.0.3&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    ])) 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .Config.Env 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .[]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<h3 id="as-flags-instead">
  <a class="heading-link" href="#as-flags-instead">As Flags Instead<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Here&rsquo;s a strange one I&rsquo;ve had to do. What if we want to recreate a list of <code>--env</code> flags we could pass to other <code>docker run ...</code> commands?</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    [
</span></span></span><span class="line"><span class="cl"><span class="s1">        [
</span></span></span><span class="line"><span class="cl"><span class="s1">          .[].Config.Env
</span></span></span><span class="line"><span class="cl"><span class="s1">        ] 
</span></span></span><span class="line"><span class="cl"><span class="s1">      | flatten 
</span></span></span><span class="line"><span class="cl"><span class="s1">      | unique 
</span></span></span><span class="line"><span class="cl"><span class="s1">      | &#34;--env=\(.[])&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    ] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | join(&#34; &#34;)
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>You&rsquo;ll get a string that looks similar to the following.</p>
<div class="code-block">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">--env=GOSU_VERSION=1.14 --env=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin --env=REDIS_DOWNLOAD_SHA=f0e65fda74c44a3dd4fa9d512d4d4d833dd0939c934e946a5c622a630d057f2f --env=REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.0.4.tar.gz --env=REDIS_VERSION=7.0.4</span></span></code></pre></div>
</div>
<h2 id="filtering-by-relative-time">
  <a class="heading-link" href="#filtering-by-relative-time">Filtering By Relative Time<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>I&rsquo;ve had to use these several times for things like garbage collecting and cleaning up long-running containers. This uses a bit of Bash to help generate the relative timestamps. We use the <code>date</code> command for this, but if you&rsquo;re on a <code>darwin</code>-based OS you&rsquo;ll need to install <code>gdate</code> first via Homebrew with:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install coreutils</span></span></code></pre></div>
</div>
<p>Substitute the following calls to <code>date</code> with <code>gdate</code> below if on MacOS.</p>
<h3 id="newer-than-">
  <a class="heading-link" href="#newer-than-">Newer Than …<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Filtering out recent containers with relative time is fairly straight-forward. The following gives us all containers created in the last hour as JSON output from <code>docker inspect ...</code>:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">threshold</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;1 hour ago&#34;</span> +%s<span class="k">)</span>
</span></span><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq --argjson threshold <span class="s2">&#34;</span><span class="si">${</span><span class="nv">threshold</span><span class="si">}</span><span class="s2">&#34;</span> -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select((.State.StartedAt | split(&#34;.&#34;)[0] | &#34;\(.)Z&#34; | fromdate) &gt; $threshold)
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>Perhaps you just want the names of the containers instead:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">threshold</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;1 hour ago&#34;</span> +%s<span class="k">)</span>
</span></span><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq --argjson threshold <span class="s2">&#34;</span><span class="si">${</span><span class="nv">threshold</span><span class="si">}</span><span class="s2">&#34;</span> -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select((.State.StartedAt | split(&#34;.&#34;)[0] | &#34;\(.)Z&#34; | fromdate) &gt; $threshold)
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .Name[1:]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>Docker likes to put a <code>/</code> at the beginning of each generated container pet name, so we use <code>.Name[1:]</code> to snip it off.</p>
<p>What about just returning the associated ids?</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">threshold</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;1 hour ago&#34;</span> +%s<span class="k">)</span>
</span></span><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq --argjson threshold <span class="s2">&#34;</span><span class="si">${</span><span class="nv">threshold</span><span class="si">}</span><span class="s2">&#34;</span> -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select((.State.StartedAt | split(&#34;.&#34;)[0] | &#34;\(.)Z&#34; | fromdate) &gt; $threshold)
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .Id
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>Or, ip addresses:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">threshold</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;1 hour ago&#34;</span> +%s<span class="k">)</span>
</span></span><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq --argjson threshold <span class="s2">&#34;</span><span class="si">${</span><span class="nv">threshold</span><span class="si">}</span><span class="s2">&#34;</span> -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select((.State.StartedAt | split(&#34;.&#34;)[0] | &#34;\(.)Z&#34; | fromdate) &gt; $threshold)
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .NetworkSettings.Networks.bridge.IPAddress
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<h3 id="older-than">
  <a class="heading-link" href="#older-than">Older Than???<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>The same filters apply, but you&rsquo;re just flipping the compare operator from <code>&gt;</code> to <code>&lt;</code>:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">threshold</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;1 hour ago&#34;</span> +%s<span class="k">)</span>
</span></span><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq --argjson threshold <span class="s2">&#34;</span><span class="si">${</span><span class="nv">threshold</span><span class="si">}</span><span class="s2">&#34;</span> -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    .[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select((.State.StartedAt | split(&#34;.&#34;)[0] | &#34;\(.)Z&#34; | fromdate) &lt; $threshold)
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<h2 id="other-common-patterns">
  <a class="heading-link" href="#other-common-patterns">Other Common Patterns<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>There have been times where I need a block of container ip addresses so I can dynamically update some Nginx upstreams somewhere. With <code>jq</code> it&rsquo;s as easy as:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  .[].NetworkSettings.Networks.bridge.IPAddress
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>The output would look something like this:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">172.17.0.6
</span></span><span class="line"><span class="cl">172.17.0.5
</span></span><span class="line"><span class="cl">172.17.0.4
</span></span><span class="line"><span class="cl">172.17.0.3
</span></span><span class="line"><span class="cl">172.17.0.2</span></span></code></pre></div>
</div>
<p>Perhaps, I want to find a specific container id by it&rsquo;s ip address:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">	.[]
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select(.NetworkSettings.Networks.bridge.IPAddress == &#34;172.17.0.3&#34;)
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .Id
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>Or, just give me the names of all running containers:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  .[].Name[1:]
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>Which would give you something like:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zealous_hertz
</span></span><span class="line"><span class="cl">fervent_dijkstra
</span></span><span class="line"><span class="cl">focused_galileo
</span></span><span class="line"><span class="cl">zealous_poincare
</span></span><span class="line"><span class="cl">naughty_wescoff</span></span></code></pre></div>
</div>
<h2 id="cleaning-up-">
  <a class="heading-link" href="#cleaning-up-">Cleaning Up …<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>We&rsquo;re considerate people, so let&rsquo;s clean up after ourselves by killing all containers created using the <code>redis</code> image:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">kill</span> <span class="k">$(</span>docker inspect <span class="k">$(</span>docker ps -q<span class="k">)</span> <span class="p">|</span> jq -r <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">	.[] 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | select(.Config.Image == &#34;redis&#34;) 
</span></span></span><span class="line"><span class="cl"><span class="s1">  | .Id
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span><span class="k">)</span></span></span></code></pre></div>
</div>
<p>Keep in mind this will kill <em>all</em> <code>redis</code> containers.</p>
<h2 id="in-closing-">
  <a class="heading-link" href="#in-closing-">In Closing …<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Having a toolbox filled with utilities you can mix and match together is great if you spend most of your day working in a terminal on a glowing rectangle. Hopefully, I&rsquo;ve made a strong enough case to for Docker and <code>jq</code> as a great combo.</p>
<h2 id="but-wait-theres-more">
  <a class="heading-link" href="#but-wait-theres-more">But, Wait! There&rsquo;s More!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Why copy and paste these commands in your terminal, when you could just <a href="https://github.com/wilhelm-murdoch/dq">download and install</a> a handy little Bash script to do it all for you? <code>dq</code> has all the above built-in as well as heaps more bells and whistles.</p>
<p>Here are a few command examples if you&rsquo;re interested:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dq older-than <span class="m">4</span> months --only-return ips --network pebkac
</span></span><span class="line"><span class="cl">dq newer-than <span class="m">2</span> fortnights --only-return ids
</span></span><span class="line"><span class="cl">dq filter <span class="s1">&#39;.[].Name[1:]&#39;</span>
</span></span><span class="line"><span class="cl">dq find-by-ip-address 172.17.0.2 --only-return names --network foo</span></span></code></pre></div>
</div>]]></content:encoded></item><item><title>Why Can't I Hold All These Slack Emojis?</title><link>https://wilhelm.codes/blog/why-cant-i-hold-all-these-slack-emojis/</link><pubDate>Wed, 28 Sep 2022 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/why-cant-i-hold-all-these-slack-emojis/</guid><category>slack</category><category>emoji</category><category>bash</category><category>api</category><wc:kind>post</wc:kind><description>Previously, last blog post I wrote covered how to make a quick escape with your precious hoard of custom Slack emojis. It was fairly well-received, but didn&amp;rsquo;t quite cover the next step in the migration process; how do you upload your millions of little images to your new Slack workspace?</description><content:encoded><![CDATA[<p>Previously, <a href="https://wilhelm.codes/liberating-custom-slack-emojis">last blog post</a> I wrote covered how to make a quick escape with your precious hoard of custom Slack emojis. It was fairly well-received, but didn&rsquo;t quite cover the next step in the migration process; how do you upload your millions of little images to your <em>new</em> Slack workspace?</p>
<h2 id="gathering-requirements">
  <a class="heading-link" href="#gathering-requirements">Gathering Requirements<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Since we&rsquo;re doing some destructive operations, we&rsquo;ll need to make sure our Slack user has the appropriate permissions to manage emoji; adding &amp; deleting. This means using a different section of the Slack interface which uses an entirely seperate set of API endpoints.</p>
<p>You&rsquo;ll still be using the following, which can be found using instructions from <a href="https://wilhelm.codes/liberating-custom-slack-emojis#heading-some-investigative-work">the previous article</a>:</p>
<ul>
<li>An API request token.</li>
<li>A session cookie.</li>
<li>A workspace, or team, id.</li>
</ul>
<p>In addition, you will need your workspaces&rsquo;s subdomain, or URL. This can easily be found within the Slack app itself:</p>
<p><img
  src="https://wilhelm.codes/blog/why-cant-i-hold-all-these-slack-emojis/image-1_hu_dacba861f5d90b40.webp"
  srcset="/blog/why-cant-i-hold-all-these-slack-emojis/image-1_hu_dacba861f5d90b40.webp 607w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="607"
  height="490"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h2 id="testing-your-findings">
  <a class="heading-link" href="#testing-your-findings">Testing Your Findings<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>This is an incredibly straight-forward process as it&rsquo;s quite similar to what we already know. The primary difference is this is a <code>multipart/form-data</code> upload. So, instead of shipping of a JSON payload, it&rsquo;s a form with associated fields.</p>
<p>With the information gathered above, you can upload directly to Slack using a simple cURL command like so:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s --compressed <span class="s2">&#34;https://&lt;domain&gt;.slack.com/api/emoji.add&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s1">&#39;content-type: multipart/form-data&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;cookie: d=&lt;cookie&gt;;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -F <span class="s2">&#34;token=&lt;token&gt;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -F <span class="s2">&#34;name=&lt;name&gt;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -F <span class="nv">mode</span><span class="o">=</span>data <span class="se">\
</span></span></span><span class="line"><span class="cl">  -F <span class="s2">&#34;image=@&lt;local-emoji-path&gt;&#34;</span></span></span></code></pre></div>
</div>
<p>Replace the following:</p>
<ul>
<li><code>&lt;domain&gt;</code> is your workspace&rsquo;s, or team&rsquo;s, private URL.</li>
<li><code>&lt;cookie&gt;</code> is your session cookie value.</li>
<li><code>&lt;token&gt;</code> is your request token.</li>
<li><code>&lt;name&gt;</code> will be the named reference of your new emoji.</li>
<li><code>&lt;path&gt;</code> is the relative, or absolute, local path of your emoji file to upload.</li>
</ul>
<p>The JSON response to this request will have the following structure if all goes well:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">JSON</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span></span></span></code></pre></div>
</div>
<p>And, if something went wrong:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">JSON</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> 
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;err_code&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p>That&rsquo;s it! Now that we know how to upload from the command line.</p>
<h2 id="the-implementation">
  <a class="heading-link" href="#the-implementation">The Implementation<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>If you&rsquo;re only uploading a handful of emojis, it might make sense to just do it via Slack&rsquo;s UI. However, this can get a bit tedious if you have dozens, or even hundreds, to upload.  We can  automate things even further by:</p>
<ul>
<li>Gathering all supported images from a specific source directory.</li>
<li>Iterating through our findings and bulk-upload them all in one go.</li>
</ul>
<p>So, let&rsquo;s do just that. But, first, let&rsquo;s set up some environmental variables so we can easily configure out script:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_DOMAIN</span><span class="p">:=</span><span class="si">}</span></span></span></code></pre></div>
</div>
<p>Slack only supports <code>png</code>, <code>gif</code> and <code>jpg</code> image formats, so lets see what we can find from our present working directory:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">files</span><span class="o">=</span><span class="k">$(</span>find . -iname <span class="se">\*</span>.gif -o -iname <span class="se">\*</span>.png -o -iname <span class="se">\*</span>.png -maxdepth 1<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">files</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">0</span></span></span></code></pre></div>
</div>
<p>Exit if we can&rsquo;t find any results. No need to continue if we haven&rsquo;t a thing to upload.</p>
<p>Next, we just iterate through our findings using <code>read</code> and some more <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">Bash parameter expansion</a> to determine what will be the new emoji&rsquo;s reference name:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">files</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r path<span class="p">;</span> <span class="k">do</span> 
</span></span><span class="line"><span class="cl">  <span class="nv">name</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">path</span><span class="p">%.*</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>We can easily parse the response bodies with <code>jq</code> for some error checking and we&rsquo;re good to go:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.ok&#34;</span><span class="k">)</span> <span class="o">==</span> <span class="nb">false</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.error&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div>
</div>
<p>We now have all the information we need to upload each file in bulk. Put it all together and you&rsquo;ve got a working bulk emoji uploader:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -eo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_DOMAIN</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">files</span><span class="o">=</span><span class="k">$(</span>find . -iname <span class="se">\*</span>.gif -o -iname <span class="se">\*</span>.png -o -iname <span class="se">\*</span>.png -maxdepth 1<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">files</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">files</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r path<span class="p">;</span> <span class="k">do</span> 
</span></span><span class="line"><span class="cl">  <span class="nv">name</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">path</span><span class="p">%.*</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nv">result</span><span class="o">=</span><span class="k">$(</span>
</span></span><span class="line"><span class="cl">    curl -s --compressed <span class="s2">&#34;https://</span><span class="si">${</span><span class="nv">SLACK_DOMAIN</span><span class="si">}</span><span class="s2">.slack.com/api/emoji.add&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -H <span class="s1">&#39;content-type: multipart/form-data&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -H <span class="s2">&#34;cookie: d=</span><span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="si">}</span><span class="s2">;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -F <span class="s2">&#34;token=</span><span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -F <span class="s2">&#34;name=</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -F <span class="nv">mode</span><span class="o">=</span>data <span class="se">\
</span></span></span><span class="line"><span class="cl">      -F <span class="s2">&#34;image=@</span><span class="si">${</span><span class="nv">path</span><span class="si">}</span><span class="s2">&#34;</span> 
</span></span><span class="line"><span class="cl">  <span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.ok&#34;</span><span class="k">)</span> <span class="o">==</span> <span class="nb">false</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.error&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;uploaded :</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">:!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>There you go! To test it out yourself, save this as an executable script and make sure your present working directory has some supported images in it. Pass through the your environmental variables when executing and you&rsquo;re off:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nv">SLACK_DOMAIN</span><span class="o">=</span>*** <span class="nv">SLACK_COOKIE</span><span class="o">=</span>*** <span class="nv">SLACK_TOKEN</span><span class="o">=</span>*** ./upload.sh
</span></span><span class="line"><span class="cl">uploaded :stonks:!
</span></span><span class="line"><span class="cl">uploaded :boop:!
</span></span><span class="line"><span class="cl">uploaded :derp:!
</span></span><span class="line"><span class="cl">uploaded :booyah:!
</span></span><span class="line"><span class="cl">uploaded :merp-flakes:!</span></span></code></pre></div>
</div>
<p>Nice! 😊</p>
<h2 id="once-again-something-better">
  <a class="heading-link" href="#once-again-something-better">Once Again, Something Better!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>While perfectly functional, there&rsquo;s not a lot of flexibility. No error checking, filtering or confirmation checks. If you&rsquo;re looking for something a bit more fleshed out, <a href="https://github.com/wilhelm-murdoch/slack-emoji-toolkit">look no further</a>!</p>
<h2 id="the-final-piece-">
  <a class="heading-link" href="#the-final-piece-">The Final Piece &hellip;<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>So far, we&rsquo;ve gone over how to download and upload large sets of emoji, but what if you want to nuke them from orbit? The final article in this series will cover how to bulk delete while using advanced filters to pin-point specific sets of emoji you wish to remove.</p>
<p>Hope you&rsquo;ve learned something useful!</p>]]></content:encoded></item><item><title>Liberating Custom Slack Emojis</title><link>https://wilhelm.codes/blog/liberating-custom-slack-emojis/</link><pubDate>Tue, 20 Sep 2022 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/liberating-custom-slack-emojis/</guid><category>emoji</category><category>api</category><category>bash</category><wc:kind>post</wc:kind><description>As is stupid tradition, whenever I start at a new company, one of the first things I like to do while getting settled in is upload my favourite emojis to whatever real-time messaging platform is in use. I know it&amp;rsquo;s childish, but silly memes and emojis are great ways for you to break the ice with your new coworkers.</description><content:encoded><![CDATA[<p>As is stupid tradition, whenever I start at a new company, one of the first things I like to do while getting settled in is upload my favourite emojis to whatever real-time messaging platform is in use. I know it&rsquo;s childish, but silly memes and emojis are great ways for you to break the ice with your new coworkers.</p>
<p>But, when it comes time to part ways it&rsquo;s only understandable to want to gather your stuff for the next place to start the process again. Over the years one can collect and curate quite a nice hoard of stupid images.</p>
<p>I&rsquo;ve been using Slack prolifically for the past few years. Unfortunately, there is no simple way of exporting your precious collection outside of the old “right-click and save” method. This is effective for smaller collections of a couple dozen or so, but becomes impractical when there are hundreds or even thousands.</p>
<p>And, of course, I&rsquo;m not alone. There have been a non-zero number of times when someone left the company only to hit me up later asking for an emoji archive.</p>
<p>Yes, this really happens and something <em>must</em> be done about it!</p>
<p>What follows is an unnecessarily long dive into a little bit of reverse engineering one of Slack&rsquo;s API endpoints, subverting it for our own use and writing a stupid little tool to help ourselves ( and others ) automate this process for the future.</p>
<h2 id="some-investigative-work">
  <a class="heading-link" href="#some-investigative-work">Some Investigative Work<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>There just has to be a straightforward way we can <em>mostly</em> automate this. So, I did some cursory digging around the Chrome Developer Tools console and found this endpoint in the “Network” tab:</p>
<div class="code-block">
  <pre tabindex="0"><code>https://edgeapi.slack.com/cache/T0XXXX/emojis/list?fp=97</code></pre>
</div>
<p>The <code>T0XXXX</code> will be your workspace, or team, id. Make note of it, you&rsquo;ll need it later.</p>
<p>If you intercept one of these requests and take a peak at the “Response” tab in the “Network” panel, you&rsquo;ll see a JSON response with a structure similar to:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">JSON</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;next_marker&#34;</span><span class="p">:</span> <span class="s2">&#34;a-custom-emoji&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;results&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;another-custom-emoji&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;https://emoji.slack-edge.com/T0XXXX/another-custom-emoji/xxxxxxx.png&#34;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="err">...</span> <span class="err">more</span> <span class="err">emojis</span> <span class="err">...</span>
</span></span><span class="line"><span class="cl">	<span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<ul>
<li><code>ok</code> tells you whether the request was successful or not. If it returns <code>false</code> there will be an additional field named <code>error</code> that should clue you into what went wrong.</li>
<li><code>next_marker</code> tells you where in the list of custom emoji the <em>next</em> page of results should start. We use this as the value of <code>marker</code> in subsequent request payloads ( see below ). This is effectively how you page through large lists of emoji.</li>
<li><code>results</code> should be obvious, but it contains an array of objects with <code>name</code> and <code>value</code> fields.
<ul>
<li><code>name</code> is the alias of the emoji.</li>
<li><code>value</code> is the direct CDN URL to the emoji.</li>
</ul>
</li>
</ul>
<p>You may see other fields, but <code>next_marker</code> and <code>value</code> are the only fields we really care about.</p>
<p>Next, If you take a look at the “Payload” tab in the “Network” panel for the same request, you&rsquo;ll see something similar to:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">JSON</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;token&#34;</span><span class="p">:</span> <span class="s2">&#34;xoxc-xxxx-xxxx-xxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;count&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;marker&#34;</span><span class="p">:</span> <span class="s2">&#34;some-other-emoji&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<ul>
<li><code>token</code> is the API request token the Slack client uses to make the emoji list request on your behalf.</li>
<li><code>count</code> is the amount of emojis to return as a page.</li>
<li><code>marker</code> tells the API where in the emoji list to start paging.</li>
</ul>
<p>The final piece of the puzzle is getting the value of your session cookie. You can grab this by clicking the “Request Headers” section under the “Headers” tab. You should see a header labeled <code>cookie</code>.</p>
<p>There&rsquo;s going to be a fairly large block of text to parse through, but you&rsquo;re looking for the cookie named <code>d</code>. Save everything between the <code>d=</code> and the closing <code>;</code>. You&rsquo;re going to need this to authenticate with the Slack API itself.</p>
<h2 id="testing-your-findings">
  <a class="heading-link" href="#testing-your-findings">Testing Your Findings<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>We now understand the structure of our payload. We also have the endpoint to hit as well as our tokens and session cookie to authenticate. Piecing it all together, you can now use cURL to hit the API directly from your terminal:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ curl --silent https://edgeapi.slack.com/cache/&lt;team&gt;/emojis/list?fp<span class="o">=</span><span class="m">97</span>
</span></span><span class="line"><span class="cl">  -H <span class="s1">&#39;cookie: d=&lt;cookie&gt;;&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --data-raw <span class="s1">&#39;{&#34;token&#34;:&#34;&lt;token&gt;&#34;,&#34;count&#34;:10}&#39;</span></span></span></code></pre></div>
</div>
<p>Replace the following:</p>
<ul>
<li><code>&lt;team&gt;</code> is your workspace, or team, id.</li>
<li><code>&lt;cookie&gt;</code> is your session cookie value.</li>
<li><code>&lt;token&gt;</code> is your request token.</li>
</ul>
<p>If everything worked out, you should have successful API response like the one above. If the response has root field named <code>marker_next</code>, make note of the associated value to help page through subsequent results. Keep doing this in a loop until your response no longer returns a <code>marker_next</code> field.</p>
<p>Once that happens, you&rsquo;re at the end of the list.</p>
<p>With a little bit of help from <code>jq</code> to parse and filter the JSON responses, we can grab a simple list of URLs.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ &lt;previous_curl_command&gt; <span class="p">|</span> jq -r <span class="s1">&#39;.results[].value&#39;</span>
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/no/060fca9aa2581a93.png
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/nods/c7fe54342ab5b8b6.gif
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/nods-back/fbd1b5384cdd0905.gif
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/nomnomnom/612cfc74c785010d.gif
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/octocat/627964d7c9.png
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/ohyou/6a352df984d9c076.gif
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/one-sec-cooking/e749627cb088859d.png
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/oof/db94710445cbb206.png
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/petrol/35ed3db238f795fc.png
</span></span><span class="line"><span class="cl">https://emoji.slack-edge.com/T0XXXX/piggy/b7762ee8cd.png</span></span></code></pre></div>
</div>
<p>We now understand how to collect everything we need, so let&rsquo;s automate this a bit more.</p>
<h2 id="the-implementation">
  <a class="heading-link" href="#the-implementation">The Implementation<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>We&rsquo;re going to write a little script that&rsquo;ll download all the custom emojis from the target Slack workspace. It&rsquo;s going to perform the following steps:</p>
<ol>
<li>Define a starting JSON payload.</li>
<li>Create a run loop.</li>
<li>With each loop we make a request using the API endpoint.</li>
<li>Do some error checking.</li>
<li>Iterate through the results and save them to disk.</li>
<li>If the results contain a <code>next_marker</code> update the JSON payload with a <code>marker</code> field and continue the run loop.</li>
<li>Keep going until the last request no longer provides a <code>next_marker</code>.</li>
</ol>
<p>First, we want to define some environmental variables so we can easily configure the script:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_WORKSPACE_ID</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="p">:=</span><span class="si">}</span></span></span></code></pre></div>
</div>
<p>Now we define our JSON payload object. We use <code>jq</code> here to keep things easy. Not only is it great for parsing and filtering JSON in Bash, it also makes building and manipulating objects simple:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">payload</span><span class="o">=</span><span class="k">$(</span>
</span></span><span class="line"><span class="cl">  jq -nc <span class="se">\
</span></span></span><span class="line"><span class="cl">    --arg     token <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    --argjson count <span class="s2">&#34;100&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">      token: $token, 
</span></span></span><span class="line"><span class="cl"><span class="s1">      count: $count
</span></span></span><span class="line"><span class="cl"><span class="s1">    }&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">)</span></span></span></code></pre></div>
</div>
<p>Next, is the run loop where we make our API requests. This passes through the payload object we just defined while also targeting our workspace:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">while</span> true<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nv">result</span><span class="o">=</span><span class="k">$(</span>
</span></span><span class="line"><span class="cl">    curl -s --compressed <span class="s2">&#34;https://edgeapi.slack.com/cache/</span><span class="si">${</span><span class="nv">SLACK_WORKSPACE_ID</span><span class="si">}</span><span class="s2">/emojis/list?fp=97&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -H <span class="s2">&#34;cookie: d=</span><span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="si">}</span><span class="s2">;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      --data-raw <span class="s2">&#34;</span><span class="si">${</span><span class="nv">payload</span><span class="si">}</span><span class="s2">&#34;</span> 
</span></span><span class="line"><span class="cl">  <span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>We obviously want to do some simple error checking so we know what we did wrong if the requests fail. Grab the error, spit it out and terminate the script.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.ok&#34;</span><span class="k">)</span> <span class="o">==</span> <span class="nb">false</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.error&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div>
</div>
<p>We now start iterating through any of the results we got from the initial request. We really only care about the <code>value</code> field as the URL gives us all the pieces we need to give our emojis a human-readable filename.</p>
<p>Here we treat the resulting URL as a standard file path and use <code>dirname</code> paired with <code>basename</code> to get the name of the parent directory of the file. The parent directory holds the actual name of the emoji, while the file itself is only a random hash. We use <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">Bash&rsquo;s parameter expansion</a> feature to construct our filename.</p>
<p>Finally, we download the remote file to the current directory:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span>  <span class="p">|</span> jq -r <span class="s1">&#39;.results[].value&#39;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r url<span class="p">;</span> <span class="k">do</span> 
</span></span><span class="line"><span class="cl"><span class="nv">name</span><span class="o">=</span><span class="k">$(</span>basename <span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">url</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">))</span>
</span></span><span class="line"><span class="cl">curl -s -o <span class="s2">&#34;</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">.</span><span class="si">${</span><span class="nv">url</span><span class="p">##*.</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">url</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>We need to check if there are more results to page through, so let&rsquo;s look for the <code>marker_next</code> field. If we can&rsquo;t find one, then mission accomplished; all done. However, if we do get a value, we take the previous payload we constructed and <em>add</em> the <code>marker</code> field.</p>
<p>The next loop will pick this change up and pass it on through to the API request telling Slack to give us the next page of precious emojis.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">marker</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.next_marker&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">marker</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;null&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">payload</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">payload</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq --arg marker <span class="s2">&#34;</span><span class="si">${</span><span class="nv">marker</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s1">&#39;. + { marker: $marker }&#39;</span><span class="k">)</span></span></span></code></pre></div>
</div>
<h2 id="the-finished-product">
  <a class="heading-link" href="#the-finished-product">The Finished Product<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -eo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_WORKSPACE_ID</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">: <span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="p">:=</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">payload</span><span class="o">=</span><span class="k">$(</span>
</span></span><span class="line"><span class="cl">  jq -nc <span class="se">\
</span></span></span><span class="line"><span class="cl">    --arg     token <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SLACK_TOKEN</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    --argjson count <span class="s2">&#34;100&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">      token: $token, 
</span></span></span><span class="line"><span class="cl"><span class="s1">      count: $count
</span></span></span><span class="line"><span class="cl"><span class="s1">    }&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> true<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nv">result</span><span class="o">=</span><span class="k">$(</span>
</span></span><span class="line"><span class="cl">    curl -s --compressed <span class="s2">&#34;https://edgeapi.slack.com/cache/</span><span class="si">${</span><span class="nv">SLACK_WORKSPACE_ID</span><span class="si">}</span><span class="s2">/emojis/list?fp=97&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      -H <span class="s2">&#34;cookie: d=</span><span class="si">${</span><span class="nv">SLACK_COOKIE</span><span class="si">}</span><span class="s2">;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      --data-raw <span class="s2">&#34;</span><span class="si">${</span><span class="nv">payload</span><span class="si">}</span><span class="s2">&#34;</span> 
</span></span><span class="line"><span class="cl">  <span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.ok&#34;</span><span class="k">)</span> <span class="o">==</span> <span class="nb">false</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.error&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span>  <span class="p">|</span> jq -r <span class="s1">&#39;.results[].value&#39;</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r url<span class="p">;</span> <span class="k">do</span> 
</span></span><span class="line"><span class="cl">    <span class="nv">name</span><span class="o">=</span><span class="k">$(</span>basename <span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">url</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">))</span>
</span></span><span class="line"><span class="cl">    curl -s -o <span class="s2">&#34;</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">.</span><span class="si">${</span><span class="nv">url</span><span class="p">##*.</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">url</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">done</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nv">marker</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">result</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s2">&#34;.next_marker&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">marker</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;null&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nv">payload</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">payload</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq --arg marker <span class="s2">&#34;</span><span class="si">${</span><span class="nv">marker</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s1">&#39;. + { marker: $marker }&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>This is pretty much it. A barebones script that downloads all findings to its present working directory. To test it out yourself, make the script executable, define the environmental variables and fire it off like so:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nv">SLACK_WORKSPACE_ID</span><span class="o">=</span>*** <span class="nv">SLACK_COOKIE</span><span class="o">=</span>*** <span class="nv">SLACK_TOKEN</span><span class="o">=</span>*** ./fetch.sh</span></span></code></pre></div>
</div>
<h2 id="an-even-better-version">
  <a class="heading-link" href="#an-even-better-version">An Even Better Version!<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>I know I just made you read through all this, but you also could&rsquo;ve just <a href="https://github.com/wilhelm-murdoch/slack-emoji-toolkit">downloaded the tool from here</a>. It&rsquo;s got heaps more bells and whistles ( if you&rsquo;re into that sort of thing):</p>
<h2 id="the-bitter-irony">
  <a class="heading-link" href="#the-bitter-irony">The Bitter Irony<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>In typical bored engineer fashion, I&rsquo;ve spent more time writing this article, documenting and publishing the code than I ever would have personally just “handraulically” doing this from time to time.</p>
<p>Hopefully, you&rsquo;ve learned something new! 🤞</p>
<h2 id="bonus-relevant-xkcd-image">
  <a class="heading-link" href="#bonus-relevant-xkcd-image">Bonus Relevant XKCD Image<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p><img
  src="https://wilhelm.codes/blog/liberating-custom-slack-emojis/image-1_hu_190150482d40845e.webp"
  srcset="/blog/liberating-custom-slack-emojis/image-1_hu_190150482d40845e.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="740"
  alt=""
  
  loading="lazy"
  decoding="async"
/>

source: <a href="https://xkcd.com/1319/">xkcd: automation</a></p>]]></content:encoded></item><item><title>Falsifying Github Participation Graphs for Fun &amp; Profit</title><link>https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/</link><pubDate>Thu, 04 Nov 2021 00:00:00 +0000</pubDate><author>0xdeadbeef@devilmayco.de (Wilhelm Murdoch)</author><guid>https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/</guid><category>bash</category><category>git</category><wc:kind>post</wc:kind><description>If you&amp;rsquo;ve spent any time job hunting in the tech industry, you&amp;rsquo;ve had to deal with recruiters. I like to joke around with my peers about how they&amp;rsquo;re a necessary evil, but in all seriousness, great recruiters are worth their weight in gold. However &amp;hellip;</description><content:encoded><![CDATA[<p>If you&rsquo;ve spent any time job hunting in the tech industry, you&rsquo;ve had to deal with recruiters. I like to joke around with my peers about how they&rsquo;re a necessary evil, but in all seriousness, great recruiters are worth their weight in gold. However &hellip;</p>
<p>But, good or bad, far too many of them will pass over your resume for the wildest reasons. We&rsquo;re not here to go over all of them. No, today we&rsquo;re going to address the somewhat recent trend of glancing at your public Github profile page and grading you based on how many green squares show up in your participation graph.</p>
<h2 id="whats-your-point">
  <a class="heading-link" href="#whats-your-point">What&rsquo;s your point?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Like basing your performance on LoC, using &ldquo;commits made&rdquo; as a recruiting metric is beyond useless. There can be any number of reasons why your graph isn&rsquo;t as populated as they&rsquo;d like:</p>
<ol>
<li>You work on private repositories and you haven&rsquo;t configured your profile to show anonymised private contributions.</li>
<li>You have an alternate account from your personal one to keep work and private life separate. Or, maybe it&rsquo;s just company policy.</li>
<li>You&rsquo;ve signed an NDA and can&rsquo;t publish your contributions publicly.</li>
<li>You set personal boundaries for your work-life balance and don&rsquo;t care to stare at glowing rectangles in your free time.</li>
<li>Or, maybe you&rsquo;ve just had a slow year? 🤷</li>
</ol>
<p>Personally, I have <em>thousands</em> of commits that will never see the light of day for some of the ☝️ points. If I&rsquo;m out job hunting, I would hate to be passed over because I don&rsquo;t tick some mundane box on an arbitrary checklist.</p>
<h2 id="what-to-do">
  <a class="heading-link" href="#what-to-do">What to do?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Well, we light up that graph like it&rsquo;s a switch board, of course. Make this information utterly useless. What do they care if your profile says you&rsquo;ve had <code>2,345</code> commits in the last week? Maybe you did. If it&rsquo;s 🟩 , they cross it off their checklist and move on to something else.</p>
<h2 id="ok-show-me">
  <a class="heading-link" href="#ok-show-me">Ok, show me.<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Crack your knuckles and get ready to do some coding. For this little project, we&rsquo;ll be doing some simple Bash scripting. I&rsquo;m currently working on MacOS, but this code should be fairly portable on other Linux-based distributions.</p>
<p>You will, naturally, need a Github account. Let&rsquo;s start there.</p>
<h3 id="configure-your-participation-graph">
  <a class="heading-link" href="#configure-your-participation-graph">Configure Your Participation Graph<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Did you know you can configure your participation graph to display anonymised private commit statistics? Ensure that setting is ✅ and move on to the next step.</p>
<p><img
  src="https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-1_hu_a64e6216dfe1bb5e.webp"
  srcset="/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-1_hu_a64e6216dfe1bb5e.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="198"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h3 id="new-private-repository">
  <a class="heading-link" href="#new-private-repository">New Private Repository<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Let&rsquo;s create a new <em>empty</em> repository entitled <code>graph-participation</code> and make sure it&rsquo;s private.</p>
<p><img
  src="https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-2_hu_b7f3ea1f8aabce13.webp"
  srcset="/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-2_hu_b7f3ea1f8aabce13.webp 736w, /blog/falsifying-github-participation-graphs-for-fun-and-profit/image-2_hu_415764d2f8820b54.webp 1104w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="78"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<h3 id="checkout-local-working-copy">
  <a class="heading-link" href="#checkout-local-working-copy">Checkout Local Working Copy<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Follow the post-setup instructions Github gives you to check out a local working copy of your new 🔒 repository!</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Let&#39;s goooooooo!&#34;</span> &gt;&gt; README.md
</span></span><span class="line"><span class="cl">git init
</span></span><span class="line"><span class="cl">git add README.md
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;The first and final legitimate commit in this repository ...&#34;</span>
</span></span><span class="line"><span class="cl">git branch -M main
</span></span><span class="line"><span class="cl">git remote add origin git@github.com:&lt;account&gt;/github-participation.git
</span></span><span class="line"><span class="cl">git push -u origin main</span></span></code></pre></div>
</div>
<p>Of course, replace <code>&lt;account&gt;</code> with the name of your Github account.</p>
<h3 id="writing-some-code">
  <a class="heading-link" href="#writing-some-code">Writing Some Code<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h3>
<p>Here comes the best part! What, you didn&rsquo;t think you&rsquo;d walk away from this article without writing a bit of code, did you?</p>
<p>This script is super-simple. There&rsquo;s no need for elegance here as we&rsquo;re probably only ever going to run this code once or twice. I&rsquo;ll show you the full snippet and then walk you through the details.</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">start</span><span class="o">=</span>2021-05-01
</span></span><span class="line"><span class="cl"><span class="nv">stop</span><span class="o">=</span>2021-05-09
</span></span><span class="line"><span class="cl"><span class="nv">max</span><span class="o">=</span><span class="m">20</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">start</span><span class="si">}</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">stop</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">do</span> 
</span></span><span class="line"><span class="cl">  <span class="nv">start</span><span class="o">=</span><span class="k">$(</span>gdate -I -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">start</span><span class="si">}</span><span class="s2"> + 1 day&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="o">((</span><span class="nv">run</span><span class="o">=</span>1<span class="p">;</span> run &lt;<span class="o">=</span> <span class="k">$((</span>RANDOM <span class="o">%</span> max <span class="o">+</span> <span class="m">1</span><span class="k">))</span><span class="p">;</span> run++<span class="o">))</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    openssl rand -hex <span class="m">32</span> &gt; touch.txt
</span></span><span class="line"><span class="cl">    git add -A
</span></span><span class="line"><span class="cl">    <span class="nb">export</span> <span class="nv">GIT_AUTHOR_DATE</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">start</span><span class="si">}</span><span class="s2">T00:01&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">export</span> <span class="nv">GIT_COMMITTER_DATE</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">GIT_AUTHOR_DATE</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    git commit -m <span class="s2">&#34;Touch #</span><span class="si">${</span><span class="nv">run</span><span class="si">}</span><span class="s2"> for </span><span class="si">${</span><span class="nv">GIT_AUTHOR_DATE</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
</div>
<p>First things first, we only need to know three things to get started:</p>
<ol>
<li><code>start</code>: The day of our first &ldquo;commit&rdquo; in the format of <code>YYYY-MM-DD</code>.</li>
<li><code>stop</code>: The day of our last &ldquo;commit&rdquo; in the format of <code>YYYY-MM-DD</code>.</li>
<li><code>max</code>: We don&rsquo;t want a single commit per day. Otherwise, the graph will be all one colour and not at all random enough to look legitimate. So, to account for this, we randomly create between <code>1</code> and <code>20</code> commits per day.</li>
</ol>
<p>We start a <code>while</code> loop that keeps going until our <code>start</code> and <code>stop</code> strings are no longer different. With each iteration, we add <code>1</code> day to our <code>start</code> variable. Eventually, this date will increment until both <code>start</code> and <code>stop</code> strings are equal, thus ending the loop.</p>
<p>For each day iteration, we create another loop that randomly-iterates anywhere between <code>1</code> and <code>max</code> times. Within each nested iteration we replace the content of the file named <code>touch.txt</code> with a random value making it a unique commit.</p>
<p>We then stage this change and assign a date value to both the <code>GIT_AUTHOR_DATE</code> and <code>GIT_COMMITTER_DATE</code> environmental variables. These are what allow you to easily back date a commit.</p>
<p>Finally, we commit this unique change locally. This keeps happening for every day until the script stops.</p>
<p>Write this to an executable file, or just paste it into your terminal of choice and hit <code>enter</code>. If you see the following kind of log output, you know you&rsquo;re doing the right thing:</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>main 2ebb5b9<span class="o">]</span> Touch <span class="c1">#1 2021-05-07T00:01</span>
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">1</span> insertion<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>main e104b0a<span class="o">]</span> Touch <span class="c1">#2 2021-05-07T00:01</span>
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">1</span> insertion<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>main 873fa39<span class="o">]</span> Touch <span class="c1">#3 2021-05-07T00:01</span>
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">1</span> insertion<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>main 9def564<span class="o">]</span> Touch <span class="c1">#4 2021-05-07T00:01</span>
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">1</span> insertion<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>main 1b83187<span class="o">]</span> Touch <span class="c1">#1 2021-05-08T00:01</span>
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">1</span> insertion<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>main b9f00db<span class="o">]</span> Touch <span class="c1">#2 2021-05-08T00:01</span>
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">1</span> insertion<span class="o">(</span>+<span class="o">)</span>, <span class="m">1</span> deletion<span class="o">(</span>-<span class="o">)</span></span></span></code></pre></div>
</div>
<p>Now, you simply have to push all your changes up to your repository on Github!</p>
<div class="code-block">
  <div class="code-head">
    <span class="code-file"></span>
    <span class="code-lang">Bash</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git push origin main</span></span></code></pre></div>
</div>
<p>All you now have to do is sit back and wait for everything to update.</p>
<p>We&rsquo;ve gone from barren landscape:</p>
<p><img
  src="https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-3_hu_57ee498a4822644c.webp"
  srcset="/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-3_hu_57ee498a4822644c.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="185"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>To a verdant garden paradise:</p>
<p><img
  src="https://wilhelm.codes/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-4_hu_86315f66da62beba.webp"
  srcset="/blog/falsifying-github-participation-graphs-for-fun-and-profit/image-4_hu_86315f66da62beba.webp 736w"
  sizes="(max-width: 48rem) 100vw, 736px"
  width="736"
  height="184"
  alt=""
  
  loading="lazy"
  decoding="async"
/>
</p>
<p>And that&rsquo;s it! 🎉</p>
<p>If you&rsquo;ve made a mistake, simply delete your private repository and try again with a fresh one.</p>
<h2 id="any-final-thoughts">
  <a class="heading-link" href="#any-final-thoughts">Any final thoughts?<span class="heading-anchor" aria-hidden="true">#</span>
  </a>
</h2>
<p>Job hunting is hard enough. Especially with more and more companies becoming increasingly risk-averse in their interviewing and recruiting processes. It&rsquo;s my hope that this little bit of harmless mischief will make things a bit easier for you!</p>]]></content:encoded></item></channel></rss>