<?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 · Tutorials</title><link>https://wilhelm.codes/tags/tutorials/</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>Thu, 09 Jan 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://wilhelm.codes/tags/tutorials/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>