blog/public/handy-script-for-a-more-zen-twitch-experience/index.html
2025-08-20 15:19:24 +01:00

143 lines
12 KiB
HTML

<!doctype html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="https://nonsense.dymc.win/favicon.ico">
<link id="stylesheet" rel="stylesheet" href="/css/light.css">
<link rel="canonical" href="https://nonsense.dymc.win/handy-script-for-a-more-zen-twitch-experience/" />
<title>Handy script for a more zen twitch experience</title>
</head>
<body><header id="banner">
<nav class="navbar">
<a href="https://nonsense.dymc.win/" class="home">🏠</a>
<a
href="/info/"
title="👋"
>👋</a
><a
href="/search/"
title="🔎"
>🔎</a
>
<button id="toggle-button" class="toggle-button" onclick="toggleTheme()">🌚</button>
</nav>
</header>
<main id="content">
<article>
<header id="post-header">
<h2>Handy script for a more zen twitch experience</h2>
<div>
<p>August 13, 2025</p>
</div>
</header><p>I like to watch twitch streams.
Watching them in the browser at <code>twitch.tv</code> is generally not an experience which sparks joy though.
I&rsquo;m buffering.
I&rsquo;m declining cookies.
I&rsquo;m getting spammed with notifications to claim a sick new overwatch skin.
There&rsquo;s a little channel point button twerking for me to click it.
You get the idea; it&rsquo;s a heavy noisy experience.</p>
<p>As a result I set out to devise a solution which does spark joy.
I came up with a script which leans on DIY desktop staples (dunst, tofi, mpv) and the very nice cli utility <a href="https://github.com/streamlink/streamlink">streamlink</a>.
Here it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">PLAYER</span><span class="o">=</span><span class="s2">&#34;mpv&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">LAUNCHER</span><span class="o">=</span><span class="s2">&#34;tofi&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NOTIFY</span><span class="o">=</span><span class="s2">&#34;dunstify&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># you need an api key, get them here</span>
</span></span><span class="line"><span class="cl"><span class="c1"># https://dev.twitch.tv/docs/api/get-started/</span>
</span></span><span class="line"><span class="cl"><span class="nv">CLIENT_ID</span><span class="o">=</span><span class="s2">&#34;id-here&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">CLIENT_SECRET</span><span class="o">=</span><span class="s2">&#34;secret-here-please-sssshh&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># list of streams to check</span>
</span></span><span class="line"><span class="cl"><span class="nv">STREAMS</span><span class="o">=(</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;limmy&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;fl0m&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;northernlion&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;caedrel&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># reassure user that something is happening</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;</span><span class="si">${</span><span class="nv">NOTIFY</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;twitch -- checking who&#39;s live beep boop be patient&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># get auth token from twitch</span>
</span></span><span class="line"><span class="cl"><span class="nv">ACCESS_TOKEN</span><span class="o">=</span><span class="k">$(</span>curl -s -X POST <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="s2">&#34;https://id.twitch.tv/oauth2/token&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -d <span class="s2">&#34;client_id=</span><span class="nv">$CLIENT_ID</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -d <span class="s2">&#34;client_secret=</span><span class="nv">$CLIENT_SECRET</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -d <span class="s2">&#34;grant_type=client_credentials&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="p">|</span> jq -r <span class="s1">&#39;.access_token&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># define empty list</span>
</span></span><span class="line"><span class="cl"><span class="nv">LIVE</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># loop through streams, check if live and append info to list</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> STREAM in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">STREAMS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> <span class="nv">RESPONSE</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;Client-ID: </span><span class="nv">$CLIENT_ID</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -H <span class="s2">&#34;Authorization: Bearer </span><span class="nv">$ACCESS_TOKEN</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="s2">&#34;https://api.twitch.tv/helix/streams?user_login=</span><span class="nv">$STREAM</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">LIVE_STATUS</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$RESPONSE</span><span class="s2">&#34;</span> <span class="p">|</span> jq <span class="s1">&#39;.data | length&#39;</span><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="s2">&#34;</span><span class="nv">$LIVE_STATUS</span><span class="s2">&#34;</span> -gt <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="nv">TITLE</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$RESPONSE</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s1">&#39;.data[0].title&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"> <span class="nv">GAME</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$RESPONSE</span><span class="s2">&#34;</span> <span class="p">|</span> jq -r <span class="s1">&#39;.data[0].game_name&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"> <span class="nv">LIVE</span><span class="o">+=(</span><span class="s2">&#34;</span><span class="nv">$STREAM</span><span class="s2"> | </span><span class="nv">$GAME</span><span class="s2"> | </span><span class="nv">$TITLE</span><span class="s2">&#34;</span><span class="o">)</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"><span class="c1"># pipe list items into tofi with new lines at the end</span>
</span></span><span class="line"><span class="cl"><span class="nv">choice</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span> <span class="nb">printf</span> <span class="s2">&#34;%s\n&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">LIVE</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">LAUNCHER</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</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> -n <span class="s2">&#34;</span><span class="nv">$choice</span><span class="s2">&#34;</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"># get first column from selection aka stream name</span>
</span></span><span class="line"><span class="cl"> <span class="nv">meat</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$choice</span><span class="s2">&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $1}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">NOTIFY</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;twitch -- launching twitch.tv/</span><span class="nv">$meat</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"> streamlink twitch.tv/<span class="s2">&#34;</span><span class="nv">$meat</span><span class="s2">&#34;</span> 1080p60 --player <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLAYER</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><h3 id="what-it-does">What it does:</h3>
<ul>
<li>talk to twitch api to get auth token</li>
<li>loop trough list of streams to check if they&rsquo;re live (using auth token)</li>
<li>grab some info about streams that are live and append it to a list</li>
<li>pipe said list into tofi</li>
<li>capture user&rsquo;s choice</li>
<li>open choice in mpv using streamlink</li>
</ul>
<h3 id="dependencies">Dependencies</h3>
<ul>
<li>curl</li>
<li>jq</li>
<li>tofi (would work with other launchers dmenu etc.)</li>
<li>mpv (would work with other media players vlc etc.)</li>
<li>dunst (would work with other notification daemons mako etc.)</li>
</ul>
<h3 id="positive-">Positive :)</h3>
<ul>
<li>no chat (you don&rsquo;t have to read the degenerates spamming LUL)</li>
<li>you can customise the script to use your favourite desktop tools</li>
<li>can rewind!</li>
</ul>
<h3 id="negative-">Negative :(</h3>
<ul>
<li>no chat (you might want to spam LUL)</li>
<li>takes a second to talk to the internet so things don&rsquo;t launch immediately</li>
<li>have to faff with getting api key - <a href="https://dev.twitch.tv/docs/api/get-started/">how to get one btw</a></li>
</ul>
<p>Hope you enjoyed bye</p></article>
</main>
<footer id="footer">
<small>
made with <a href="https://gohugo.io">hugo</a>
</small>
<script src="/js/search.js"></script>
<script src="/js/toggle.js"></script>
</footer>
</body>
</html>