From 9df1ac6b58cc707a977351dacba5fe2e4497dcea Mon Sep 17 00:00:00 2001
From: dnns01 <github@dnns01.de>
Date: Tue, 7 Dec 2021 12:39:09 +0100
Subject: [PATCH] Add spotify to web interface

---
 strolchibot/forms.py                    |  12 +--
 strolchibot/migrations/0009_spotify.py  |  25 ++++++
 strolchibot/models.py                   |  10 +++
 strolchibot/templates/navigation.html   |   2 +
 strolchibot/templates/spotify/edit.html |   9 ++
 strolchibot/templates/spotify/list.html |  28 ++++++
 strolchibot/urls.py                     |   4 +
 strolchibot/views.py                    |  84 +++++++++++++++++-
 twitchbot/spotify_cog.py                | 108 ++++++++++++++----------
 9 files changed, 231 insertions(+), 51 deletions(-)
 create mode 100644 strolchibot/migrations/0009_spotify.py
 create mode 100644 strolchibot/templates/spotify/edit.html
 create mode 100644 strolchibot/templates/spotify/list.html

diff --git a/strolchibot/forms.py b/strolchibot/forms.py
index 6e49630..51e24bb 100644
--- a/strolchibot/forms.py
+++ b/strolchibot/forms.py
@@ -1,6 +1,6 @@
 from django import forms
 
-from .models import Config, Command
+from .models import Config, Command, Spotify
 
 
 class BaseModelForm(forms.ModelForm):
@@ -52,8 +52,8 @@ class CommandForm(BaseModelForm):
         model = Command
         exclude = ['active']
 
-# class ClipEditForm(BaseModelForm):
-#     class Meta:
-#         model = Clip
-#         fields = ['custom_title', 'tags']
-#         widgets = {'tags': forms.widgets.CheckboxSelectMultiple()}
+
+class SpotifyForm(BaseModelForm):
+    class Meta:
+        model = Spotify
+        fields = ['streamer']
diff --git a/strolchibot/migrations/0009_spotify.py b/strolchibot/migrations/0009_spotify.py
new file mode 100644
index 0000000..6a8bca3
--- /dev/null
+++ b/strolchibot/migrations/0009_spotify.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.8 on 2021-12-04 23:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('strolchibot', '0008_counter'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Spotify',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('streamer', models.TextField(max_length=50, unique=True)),
+                ('access_token', models.TextField(max_length=200)),
+                ('token_type', models.TextField(max_length=20)),
+                ('expires_in', models.IntegerField()),
+                ('refresh_token', models.TextField(max_length=200)),
+                ('scope', models.TextField(max_length=100)),
+                ('user_id', models.TextField(max_length=50)),
+            ],
+        ),
+    ]
diff --git a/strolchibot/models.py b/strolchibot/models.py
index 4580920..61e3966 100644
--- a/strolchibot/models.py
+++ b/strolchibot/models.py
@@ -89,3 +89,13 @@ class TwitchUser(models.Model):
 class Counter(models.Model):
     name = models.CharField(max_length=50)
     count = models.IntegerField()
+
+
+class Spotify(models.Model):
+    streamer = models.TextField(max_length=50, unique=True)
+    access_token = models.TextField(max_length=200)
+    token_type = models.TextField(max_length=20)
+    expires_in = models.IntegerField()
+    refresh_token = models.TextField(max_length=200)
+    scope = models.TextField(max_length=100)
+    user_id = models.TextField(max_length=50)
diff --git a/strolchibot/templates/navigation.html b/strolchibot/templates/navigation.html
index 8dc5911..f0c9b49 100644
--- a/strolchibot/templates/navigation.html
+++ b/strolchibot/templates/navigation.html
@@ -12,6 +12,8 @@
            class="w3-bar-item w3-button {% if title == "Timers" %}w3-light-gray{% endif %}">Timers</a>
         <a href="{% url 'link_protection' %}"
            class="w3-bar-item w3-button {% if title == "Link Protection" %}w3-light-gray{% endif %}">Link Protection</a>
+        <a href="{% url 'spotify' %}"
+           class="w3-bar-item w3-button {% if title == "Spotify" %}w3-light-gray{% endif %}">Spotify</a>
         {% if user.admin %}
             <a href="{% url 'config' %}"
                class="w3-bar-item w3-button {% if title == "Config" %}w3-light-gray{% endif %}">Config</a>
diff --git a/strolchibot/templates/spotify/edit.html b/strolchibot/templates/spotify/edit.html
new file mode 100644
index 0000000..7588a5c
--- /dev/null
+++ b/strolchibot/templates/spotify/edit.html
@@ -0,0 +1,9 @@
+{% extends 'layout.html' %}
+
+{% block content %}
+    <form method="post">
+        {% csrf_token %}
+        {% include 'card_form.html' %}
+        <input type="submit" value="Add/Save" class="w3-button w3-strolchpink w3-block">
+    </form>
+{% endblock %}
\ No newline at end of file
diff --git a/strolchibot/templates/spotify/list.html b/strolchibot/templates/spotify/list.html
new file mode 100644
index 0000000..deb2a12
--- /dev/null
+++ b/strolchibot/templates/spotify/list.html
@@ -0,0 +1,28 @@
+{% extends 'layout.html' %}
+
+{% block content %}
+    {% csrf_token %}
+    <div class="w3-card w3-white">
+        <div>
+            <a href="{% url "spotify_login" %}" class="w3-button w3-strolchpink w3-right w3-margin">
+                <em class="fas fa-plus"></em>Add/Update</a>
+        </div>
+        <div class="w3-padding">
+            <table class="w3-table w3-striped">
+                <tbody>
+                <tr>
+                    <th>Streamer</th>
+                    <th></th>
+                </tr>
+                {% for spotify_login in spotify_logins %}
+                    <tr>
+                        <td>{{ spotify_login.streamer }}</td>
+                        <td><a href="{% url "spotify_edit" spotify_login.streamer %}"><em
+                                class="fas fa-pencil-alt"></em></a></td>
+                    </tr>
+                {% endfor %}
+                </tbody>
+            </table>
+        </div>
+    </div>
+{% endblock %}
\ No newline at end of file
diff --git a/strolchibot/urls.py b/strolchibot/urls.py
index babb13d..be5a8d6 100644
--- a/strolchibot/urls.py
+++ b/strolchibot/urls.py
@@ -47,6 +47,10 @@ urlpatterns = [
          name="link_protection_blacklist_remove"),
     path('strolchguru/', include('strolchguru.urls')),
     path('twitter/', include('twitter.urls')),
+    path('spotify', views.spotify, name="spotify"),
+    path('spotify/edit/<str:streamer>', views.spotify_edit, name="spotify_edit"),
+    path('spotify/login', views.spotify_login, name="spotify_login"),
+    path('spotify/login/redirect', views.spotify_login_redirect, name="spotify_login_redirect"),
 ]
 
 if settings.DEBUG:
diff --git a/strolchibot/views.py b/strolchibot/views.py
index 86a7cfd..0688dde 100644
--- a/strolchibot/views.py
+++ b/strolchibot/views.py
@@ -8,8 +8,8 @@ from django.forms import modelformset_factory
 from django.http import Http404, JsonResponse, HttpResponse, HttpRequest
 from django.shortcuts import render, redirect, get_object_or_404
 
-from .forms import BaseModelForm, LinkProtectionConfigForm, CommandForm
-from .models import Command, Klassenbuch, Timer, Config, LinkPermit, LinkWhitelist, LinkBlacklist
+from .forms import BaseModelForm, LinkProtectionConfigForm, CommandForm, SpotifyForm
+from .models import Command, Klassenbuch, Timer, Config, LinkPermit, LinkWhitelist, LinkBlacklist, Spotify
 
 
 def home(request):
@@ -296,4 +296,84 @@ def commands_set_active(request: HttpRequest) -> JsonResponse:
             pass
 
     raise Http404
+
+
+# </editor-fold>
+
+
+# <editor-fold desc="Spotify">
+@login_required(login_url="/login")
+def spotify(request: HttpRequest) -> HttpResponse:
+    spotify_logins = Spotify.objects.all()
+
+    return render(request, "spotify/list.html",
+                  {"title": "Spotify", "spotify_logins": spotify_logins})
+
+
+def spotify_edit(request: HttpRequest, streamer: str) -> HttpResponse:
+    user = get_object_or_404(Spotify, streamer=streamer)
+
+    if request.method == "POST":
+        form = SpotifyForm(request.POST, instance=user)
+
+        if form.is_valid():
+            form.save()
+            return redirect("/spotify")
+
+    form = SpotifyForm(instance=user)
+
+    return render(request, "spotify/edit.html",
+                  {"title": "Spotify", "form": {"form": form}})
+
+
+@login_required(login_url="/login")
+def spotify_login(request: HttpRequest) -> HttpResponse:
+    url = os.getenv("SPOTIFY_AUTH_URL")
+    return redirect(url)
+
+
+@login_required(login_url="/login")
+def spotify_login_redirect(request: HttpRequest) -> JsonResponse:
+    code = request.GET.get('code')
+    data = {
+        "client_id": os.getenv("SPOTIFY_CLIENT_ID"),
+        "client_secret": os.getenv("SPOTIFY_CLIENT_SECRET"),
+        "grant_type": "authorization_code",
+        "code": code,
+        "redirect_uri": os.getenv("SPOTIFY_REDIRECT_URI"),
+    }
+    headers = {
+        'Content-Type': 'application/x-www-form-urlencoded'
+    }
+    response = requests.post("https://accounts.spotify.com/api/token", data=data, headers=headers)
+    credentials = response.json()
+    access_token = credentials['access_token']
+    token_type = credentials['token_type']
+    expires_in = credentials['expires_in']
+    refresh_token = credentials['refresh_token']
+    scope = credentials['scope']
+
+    response = requests.get("https://api.spotify.com/v1/me", headers={
+        'Authorization': f'Bearer {access_token}'
+    })
+
+    user_id = response.json()['id']
+    streamer = response.json()['display_name'].replace(' ', '').lower()
+    users = Spotify.objects.filter(user_id=user_id)
+
+    if len(list(users)) == 1:
+        for user in users:
+            user.access_token = access_token
+            user.token_type = token_type
+            user.expires_in = expires_in
+            user.refresh_token = refresh_token
+            user.scope = scope
+            user.save()
+            return redirect(f"/spotify")
+    else:
+        user = Spotify(streamer=streamer, access_token=access_token, token_type=token_type,
+                       expires_in=expires_in, refresh_token=refresh_token, scope=scope, user_id=user_id)
+        user.save()
+        return redirect(f"/spotify/edit/{user_id}")
+
 # </editor-fold>
diff --git a/twitchbot/spotify_cog.py b/twitchbot/spotify_cog.py
index 9388cbf..b1bd517 100644
--- a/twitchbot/spotify_cog.py
+++ b/twitchbot/spotify_cog.py
@@ -1,5 +1,5 @@
-import json
 import os
+import sqlite3
 
 import requests
 from twitchio.ext import commands
@@ -8,19 +8,37 @@ from twitchio.ext import commands
 class SpotifyCog(commands.Cog):
     def __init__(self, bot):
         self.bot = bot
-        self.spotify_file = "spotify.json"
-        self.basic_auth = os.getenv("BASIC_AUTH")
-        self.spotify = {}
-        self.load_spotify()
+        self.basic_auth = os.getenv("SPOTIFY_BASIC_AUTH")
         self.streamer = None
 
-    def load_spotify(self):
-        spotify_file = open(self.spotify_file, mode='r')
-        self.spotify = json.load(spotify_file)
+    def get_streamer(self, name):
+        streamer = None
+        conn = sqlite3.connect("db.sqlite3")
+        c = conn.cursor()
+        c.execute(
+            "SELECT access_token, token_type, expires_in, refresh_token, scope, user_id from strolchibot_spotify where streamer = ?",
+            (name,))
+        if s := c.fetchone():
+            streamer = {
+                "access_token": s[0],
+                "token_type": s[1],
+                "expires_in": s[2],
+                "refresh_token": s[3],
+                "scope": s[4],
+                "user_id": s[5],
+            }
 
-    def save_spotify(self):
-        spotify_file = open(self.spotify_file, mode='w')
-        json.dump(self.spotify, spotify_file)
+        conn.close()
+        return streamer
+
+    def update_streamer(self, streamer, name):
+        conn = sqlite3.connect("db.sqlite3")
+        c = conn.cursor()
+        c.execute(
+            "UPDATE strolchibot_spotify set access_token = ? where streamer = ?",
+            (streamer["access_token"], name,))
+        conn.commit()
+        conn.close()
 
     @commands.command(name="song")
     async def cmd_song(self, ctx):
@@ -39,44 +57,48 @@ class SpotifyCog(commands.Cog):
                 "Ich würde ja gerne sagen, welcher kultige Song gerade läuft. Leider hat mir noch keiner gesagt, wer eigentlich gerade streamt strolchWut")
 
     def get_song(self):
-        response = requests.get("https://api.spotify.com/v1/me/player", headers={
-            'Authorization': f'Bearer {self.spotify[self.streamer]["access_token"]}'
-        })
-
-        if response.status_code == 204:
-            return None
+        streamer = self.get_streamer(self.streamer)
+        if streamer:
+            response = requests.get("https://api.spotify.com/v1/me/player", headers={
+                'Authorization': f'Bearer {streamer["access_token"]}'
+            })
 
-        player = response.json()
+            if response.status_code == 204:
+                return None
 
-        if response.status_code == 401 and player.get("error") and player.get("error").get(
-                "message") == "The access token expired":
-            self.refresh_token()
-            return self.get_song()
-        elif response.status_code == 200 and player.get("is_playing"):
             player = response.json()
-            item = player['item']
-            artists = ", ".join([artist['name'] for artist in item['artists']])
-            name = item['name']
-            album = item['album']['name']
-            url = item['external_urls']['spotify']
 
-            return {"name": name, "artists": artists, "album": album, "url": url}
-        else:
-            return None
+            if response.status_code == 401:
+                self.refresh_token()
+                return self.get_song()
+            elif response.status_code == 200 and player.get("is_playing"):
+                player = response.json()
+                item = player['item']
+                artists = ", ".join([artist['name'] for artist in item['artists']])
+                name = item['name']
+                album = item['album']['name']
+                url = item['external_urls']['spotify']
+
+                return {"name": name, "artists": artists, "album": album, "url": url}
+
+        return None
 
     def refresh_token(self):
-        data = {
-            "grant_type": "refresh_token",
-            "refresh_token": f'{self.spotify[self.streamer]["refresh_token"]}'
-        }
-        headers = {
-            'Content-Type': 'application/x-www-form-urlencoded',
-            'Authorization': f'Basic {self.basic_auth}'
-        }
-        response = requests.post("https://accounts.spotify.com/api/token", data=data, headers=headers)
-        credentials = response.json()
-        self.spotify[self.streamer]["access_token"] = credentials["access_token"]
-        self.save_spotify()
+        streamer = self.get_streamer(self.streamer)
+
+        if streamer:
+            data = {
+                "grant_type": "refresh_token",
+                "refresh_token": f'{streamer["refresh_token"]}'
+            }
+            headers = {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'Authorization': f'Basic {self.basic_auth}'
+            }
+            response = requests.post("https://accounts.spotify.com/api/token", data=data, headers=headers)
+            credentials = response.json()
+            streamer["access_token"] = credentials["access_token"]
+            self.update_streamer(streamer, self.streamer)
 
     @commands.command(name="streamer")
     async def cmd_streamer(self, ctx, streamer):
-- 
GitLab