<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Simon Metson</title>
    <description>The latest articles on DEV Community by Simon Metson (@drsm79).</description>
    <link>https://dev.to/drsm79</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F570815%2F716a127c-9045-4d56-acb4-195a587ea49d.png</url>
      <title>DEV Community: Simon Metson</title>
      <link>https://dev.to/drsm79</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/drsm79"/>
    <language>en</language>
    <item>
      <title>Payment verification with Vanilla &amp; Flask</title>
      <dc:creator>Simon Metson</dc:creator>
      <pubDate>Sun, 31 Jan 2021 13:29:51 +0000</pubDate>
      <link>https://dev.to/drsm79/payment-verification-with-vanilla-flask-4hmd</link>
      <guid>https://dev.to/drsm79/payment-verification-with-vanilla-flask-4hmd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Grab that cash with both hands and make a stash&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;This was originally published on the &lt;a href="https://blog.audiotarky.com/2020/12/14/payment-verification/index.html"&gt;Audiotarky blog&lt;/a&gt; in December 2020, there's an update due...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Audiotarky wants to get musicians paid fairly for the music they produce.&lt;br&gt;
We're using &lt;a href="https://webmonetization.org/"&gt;Web Monetisation&lt;/a&gt; to power that, but there's a gap that needs&lt;br&gt;
to be closed. The &lt;a href="https://webmonetization.org/docs/exclusive-content"&gt;exclusive content&lt;/a&gt; example is doing its magic client side&lt;br&gt;
(in the browser) this makes it easily circumvented. Possibly not a big deal in&lt;br&gt;
many cases, but for a music platform not really sufficient. Imagine 2 years&lt;br&gt;
worth of work trivially downloaded without payment, you'd not be a happy bunny.&lt;/p&gt;

&lt;p&gt;Fortunately the bright folk from &lt;a href="https://cinnamon.video/"&gt;Cinnamon&lt;/a&gt; have already hit this problem,&lt;br&gt;
and solved it via a service called &lt;a href="https://vanilla.so/"&gt;Vanilla&lt;/a&gt;. This gives you a proxy payment&lt;br&gt;
pointer which the service can then verify payment on. You can then call their&lt;br&gt;
API to confirm that the client has streamed payment to the site.&lt;/p&gt;

&lt;p&gt;For Audiotarky we're using this to make a session, which our &lt;a href="https://flask.palletsprojects.com/en/1.1.x/"&gt;Flask&lt;/a&gt; server&lt;br&gt;
can then "see" and return the exclusive content to those who are streaming&lt;br&gt;
payment to the artist.&lt;/p&gt;

&lt;p&gt;Lets look at the code for our proving function first. This is what calls the&lt;br&gt;
Vanilla API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""Prove that a WM payment has occurred."""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'''{{
        proof(requestId: "&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;") {{
            total
            rate
            metadata{{
            requestId
            clientId
            contentId
            }}
        }}
    }}'''&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'server'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'APIURL'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTTPBasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'server'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'server'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'SECRET'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'proof'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'metadata'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'proof'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing fantastically interesting in there, and it's essentially a&lt;br&gt;
port of one of the Vanilla examples from Javasript to Python.&lt;/p&gt;

&lt;p&gt;We now need some frontend code to create the session. Normally this would be&lt;br&gt;
done when someone logs on to the site, but we're deliberately trying to avoid&lt;br&gt;
such things; we're trying to be privacy focussed, after all. As the session is&lt;br&gt;
only needed for visitors who are streaming payment, we can do something like&lt;br&gt;
the following to create it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;monetization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;monetization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;monetizationprogress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;authz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;looks for whether the webmonetisation plug in is available (&lt;code&gt;if (document.monetization)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;if it is, sets up a listener for when payment is streamed&lt;/li&gt;
&lt;li&gt;once a payment is streamed, send the &lt;code&gt;requestId&lt;/code&gt; of that payment to the &lt;code&gt;/session&lt;/code&gt;
endpoint for validation with Vanilla&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;loaded&lt;/code&gt; is used to track whether this has been done already, really the even&lt;br&gt;
listener should be removed or similar.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/session&lt;/code&gt; endpoint is pretty minimal, using the &lt;code&gt;prove&lt;/code&gt; function from&lt;br&gt;
earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/session'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_session&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;"""Create a session if the client iw WM enabled."""&lt;/span&gt;
    &lt;span class="n"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'clientId'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'metadata'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'clientId'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'session created'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;PaymentRequired&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK, so we now have code that creates a session when the user is streaming&lt;br&gt;
payment, great!&lt;/p&gt;

&lt;p&gt;But... now what?&lt;/p&gt;

&lt;p&gt;Well, now we can check that session (the session token will be sent by the&lt;br&gt;
browser on each request) and decide whether "exclusive content" should be&lt;br&gt;
served or not. This can be checked in the Flask app by calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'clientId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the session exists, and is valid, the application can assume (and it is an&lt;br&gt;
assumption - more later) that payment is streaming.&lt;/p&gt;

&lt;p&gt;In the case of Audiotarky, we want musicians to be able to decide if folk can&lt;br&gt;
listen to their music regardless of whether they're being paid, or that they&lt;br&gt;
want to lock it down to only those who are streaming payment. At the same time,&lt;br&gt;
Audiotarky itself only generates revenue if people are browsing for music &amp;amp;&lt;br&gt;
streaming payment as they do so, so we want to encourage people to sign up to&lt;br&gt;
&lt;a href="https://coil.com/"&gt;Coil&lt;/a&gt;. At the moment this is done visually, and also by inserting a robotic&lt;br&gt;
voice (thanks to &lt;code&gt;say&lt;/code&gt;) mentioning that a Coil subscription would mean people&lt;br&gt;
got paid.&lt;/p&gt;

&lt;p&gt;The plan is to bundle some of this up into &lt;a href="https://github.com/audiotarky/wm-flask"&gt;a library&lt;/a&gt; for people to use in&lt;br&gt;
their own applications, but that will have to wait a little while as we build&lt;br&gt;
out the site, prove the idea some more, and, most importantly, get musicians&lt;br&gt;
on boarded &amp;amp; getting paid!&lt;/p&gt;

</description>
      <category>webmonetization</category>
      <category>flask</category>
      <category>python</category>
    </item>
    <item>
      <title>Audiotarky Tech stack</title>
      <dc:creator>Simon Metson</dc:creator>
      <pubDate>Sun, 31 Jan 2021 13:25:36 +0000</pubDate>
      <link>https://dev.to/drsm79/audiotarky-tech-stack-1iok</link>
      <guid>https://dev.to/drsm79/audiotarky-tech-stack-1iok</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Buy it, use it, break it, fix it, trash it, change it, mail, upgrade it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;This was originally published on the &lt;a href="https://blog.audiotarky.com/2020/11/15/tech-stack/index.html"&gt;Audiotarky blog&lt;/a&gt; in November 2020, there's an update due...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While we're still very much "in development" I thought it might be interesting to write up the tech stack that's in use today, if only as a historical record to compare back to in the future.&lt;/p&gt;

&lt;p&gt;The site uses &lt;a href="https://flask.palletsprojects.com/en/1.1.x/"&gt;Flask&lt;/a&gt; in the backend, and &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; to render static content. For the initial phase of development, we're avoiding databases in the backend - the content is not that dynamic, in fact its almost write once. Jekyll means we can pre-render the pages, apply changes to templates etc. easily. There are a few "interesting" templates that are being built, and that probably points to Jekyll not being a long term solution (&lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; looks interesting).&lt;/p&gt;

&lt;p&gt;We've so far avoided a front end framework, though I expect that will be changing soon. That said, I'd like to keep any UI framework minimal, &amp;amp; standards based.&lt;/p&gt;

&lt;p&gt;We're trying to be privacy concious, and the architecture &amp;amp; use of &lt;a href="https://coil.com"&gt;Coil&lt;/a&gt; means we &lt;em&gt;need&lt;/em&gt; to know very little about our audience, but also do want to understand &lt;em&gt;some&lt;/em&gt; of what people are doing. To that end we've got &lt;a href="https://usefathom.com/ref/KP7MDR"&gt;Fathom&lt;/a&gt;* providing some "non-creepy" analytics.&lt;/p&gt;

&lt;p&gt;The flask app is hosted on &lt;a href="https://www.pythonanywhere.com/?affiliate_id=0086ceb1"&gt;PythonAnywhere&lt;/a&gt;* which I stumbled upon, and have so far been very impressed with. There's some work to do to better use their service (static assets, some scheduled tasks, maybe some further deployment automation) but for now it's doing whats needed. It's simple, it does what you need &amp;amp; gets out the way - winner!&lt;/p&gt;

&lt;p&gt;The flask app is used to change how audio is served based on whether the listener has &lt;a href="https://coil.com"&gt;Coil&lt;/a&gt; enabled. To do this we're using &lt;a href="https://admin.vanilla.so/"&gt;Vanilla&lt;/a&gt; to verify a payment stream, and some code that will be open sourced in the future.&lt;/p&gt;

&lt;p&gt;Last there are some google forms/sheets &amp;amp; associated python scripts to pull data. These are used for &lt;a href="https://forms.gle/7irCZKrSUaZjRnZk7"&gt;expression of interest&lt;/a&gt; &amp;amp; artist sign up&lt;/p&gt;

&lt;p class="small"&gt;* referal link&lt;/p&gt;

</description>
      <category>webmonetization</category>
    </item>
  </channel>
</rss>
