diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e23123ff03b99d3ff4b87af4ce25c4a763111409..0000000000000000000000000000000000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="Encoding"> - <file url="file://$PROJECT_DIR$/info.json" charset="UTF-8" /> - </component> -</project> \ No newline at end of file diff --git a/.idea/haugebot.iml b/.idea/haugebot.iml new file mode 100644 index 0000000000000000000000000000000000000000..619069087d7ed1d3faa2c1c17570711f3bfdfc28 --- /dev/null +++ b/.idea/haugebot.iml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="PYTHON_MODULE" version="4"> + <component name="FacetManager"> + <facet type="django" name="Django"> + <configuration> + <option name="rootFolder" value="$MODULE_DIR$" /> + <option name="settingsModule" value="haugebot/settings.py" /> + <option name="manageScript" value="$MODULE_DIR$/manage.py" /> + <option name="environment" value="<map/>" /> + <option name="doNotUseTestRunner" value="false" /> + <option name="trackFilePattern" value="migrations" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager"> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/venv" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> + <component name="TemplatesService"> + <option name="TEMPLATE_CONFIGURATION" value="Django" /> + <option name="TEMPLATE_FOLDERS"> + <list> + <option value="$MODULE_DIR$/../haugebot\templates" /> + </list> + </option> + </component> +</module> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index da49fd62fb2eb6682bd89e556dcbe272aba33ac8..e32c50e4fee440929478026307cc206c2ec003d3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ <component name="JavaScriptSettings"> <option name="languageLevel" value="ES6" /> </component> - <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (TwitchHausGeist)" project-jdk-type="Python SDK" /> + <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (haugebot)" project-jdk-type="Python SDK" /> </project> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index f7b4bc476940d994e4f3193b52ba16c8b9772ca0..e9d8211fa6e18662bc4351428adf8f7de119fbdd 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ <project version="4"> <component name="ProjectModuleManager"> <modules> - <module fileurl="file://$PROJECT_DIR$/.idea/pipimeter.iml" filepath="$PROJECT_DIR$/.idea/pipimeter.iml" /> + <module fileurl="file://$PROJECT_DIR$/.idea/haugebot.iml" filepath="$PROJECT_DIR$/.idea/haugebot.iml" /> </modules> </component> </project> \ No newline at end of file diff --git a/.idea/pipimeter.iml b/.idea/pipimeter.iml deleted file mode 100644 index 6b70e7785d0412ae5c6560289a9f569d4ef474f7..0000000000000000000000000000000000000000 --- a/.idea/pipimeter.iml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<module type="PYTHON_MODULE" version="4"> - <component name="NewModuleRootManager"> - <content url="file://$MODULE_DIR$"> - <excludeFolder url="file://$MODULE_DIR$/venv" /> - </content> - <orderEntry type="jdk" jdkName="Python 3.8 (TwitchHausGeist)" jdkType="Python SDK" /> - <orderEntry type="sourceFolder" forTests="false" /> - </component> -</module> \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000000000000000000000000000000000000..18091cbba127c265e42212cee8a5f534582fb74c --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="SqlDialectMappings"> + <file url="file://$PROJECT_DIR$/haugebot_web/migrations/0001_initial.py" dialect="GenericSQL" /> + <file url="PROJECT" dialect="SQLite" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/vagrant.xml b/.idea/vagrant.xml deleted file mode 100644 index a5aa78680308cff5d34e132d6cabd2ab43745203..0000000000000000000000000000000000000000 --- a/.idea/vagrant.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="VagrantProjectSettings"> - <option name="instanceFolder" value="" /> - <option name="provider" value="" /> - </component> -</project> \ No newline at end of file diff --git a/haugebot/__init__.py b/haugebot/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/haugebot/asgi.py b/haugebot/asgi.py new file mode 100644 index 0000000000000000000000000000000000000000..abb3541ea3f4de2dd0b3ebbca312a74d75ad794f --- /dev/null +++ b/haugebot/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for haugebot project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'haugebot.settings') + +application = get_asgi_application() diff --git a/haugebot/settings.py b/haugebot/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..04587d7da5d18210bb91183c2460178ec8f3ad44 --- /dev/null +++ b/haugebot/settings.py @@ -0,0 +1,125 @@ +""" +Django settings for haugebot project. + +Generated by 'django-admin startproject' using Django 3.1.5. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.1/ref/settings/ +""" + +import os +from pathlib import Path + +from dotenv import load_dotenv + +load_dotenv() + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.getenv("DJANGO_SECRET") + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +AUTHENTICATION_BACKENDS = [ + 'haugebot_web.auth.TwitchAuthenticationBackend', +] + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'haugebot_web', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'haugebot.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')] + , + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'haugebot.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/3.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +# Password validation +# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.1/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/haugebot/urls.py b/haugebot/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..971b78fc9843ad7affc843155c3125f26fe7ac66 --- /dev/null +++ b/haugebot/urls.py @@ -0,0 +1,29 @@ +"""haugebot URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +from haugebot_web import views + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', views.home, name="home"), + path('login/', views.login, name="login"), + path('logout/', views.logout, name="logout"), + path('login/redirect/', views.login_redirect, name="login_redirect"), + path('wusstest_du_schon/', views.wusstest_du_schon, name="wusstest_du_schon"), + path('wusstest_du_schon/remove/<int:id>', views.wusstest_du_schon_remove, name="wusstest_du_schon_remove"), +] diff --git a/haugebot/wsgi.py b/haugebot/wsgi.py new file mode 100644 index 0000000000000000000000000000000000000000..cb12bbff3f4342700a9236bb4f233e61b2724fad --- /dev/null +++ b/haugebot/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for haugebot project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'haugebot.settings') + +application = get_wsgi_application() diff --git a/giveaway_cog.py b/haugebot_twitch/giveaway_cog.py similarity index 99% rename from giveaway_cog.py rename to haugebot_twitch/giveaway_cog.py index 3c0d09f3fb1af59f557cc1362458d97029c697dd..31941bb8422a9f1e6a19a05fe7a98bb0acfed699 100644 --- a/giveaway_cog.py +++ b/haugebot_twitch/giveaway_cog.py @@ -5,7 +5,7 @@ from twitchio.ext import commands @commands.core.cog(name="GiveawayCog") -class GiveawayGog: +class GiveawayCog: def __init__(self, bot): self.bot = bot self.giveaway_enabled = False diff --git a/hausgeist.py b/haugebot_twitch/haugebot.py similarity index 81% rename from hausgeist.py rename to haugebot_twitch/haugebot.py index 16d6d01fdd4c912cae68aba9b3c20047b3b9fcd0..0feb0e3dc1f10154e393f283001d8ffd54cd05ce 100644 --- a/hausgeist.py +++ b/haugebot_twitch/haugebot.py @@ -1,18 +1,21 @@ import asyncio -import logging import os +import sqlite3 from abc import ABC from dotenv import load_dotenv -from twitchio.dataclasses import Context, Message, Channel -from twitchio.ext import commands - -from giveaway_cog import GiveawayGog +from giveaway_cog import GiveawayCog +# from giveaway_cog import GiveawayGog from info_cog import InfoCog from pipi_cog import PipiCog +from twitchio.dataclasses import Context, Message, Channel +from twitchio.ext import commands from vote_cog import VoteCog -logging.basicConfig(level=logging.INFO, filename='hausgeist.log') +# from pipi_cog import PipiCog +# from vote_cog import VoteCog + +# logging.basicConfig(level=logging.INFO, filename='hausgeist.log') load_dotenv() IRC_TOKEN = os.getenv("IRC_TOKEN") @@ -33,14 +36,12 @@ class HaugeBot(commands.Bot, ABC): self.PREFIX = os.getenv("PREFIX") super().__init__(irc_token=IRC_TOKEN, prefix=PREFIX, nick=NICK, initial_channels=[CHANNEL], client_id=CLIENT_ID, client_secret=CLIENT_SECRET) - self.pipi_cog = PipiCog(self) - self.giveaway_cog = GiveawayGog(self) - self.vote_cog = VoteCog(self) self.info_cog = InfoCog(self) - self.add_cog(self.pipi_cog) - self.add_cog(self.giveaway_cog) - self.add_cog(self.vote_cog) + self.pipi_cog = PipiCog(self) + self.add_cog(GiveawayCog(self)) + self.add_cog(VoteCog(self)) self.add_cog(self.info_cog) + self.add_cog(self.pipi_cog) @staticmethod async def send_me(ctx, content, color): @@ -55,6 +56,7 @@ class HaugeBot(commands.Bot, ABC): async def event_ready(self): print('Logged in') + asyncio.create_task(self.info_cog.info_loop()) asyncio.create_task(self.pipi_cog.pipimeter_loop()) @@ -75,6 +77,16 @@ class HaugeBot(commands.Bot, ABC): async def stream(self): return await self.get_stream(self.CHANNEL) + @staticmethod + def get_setting(key): + conn = sqlite3.connect("db.sqlite3") + + c = conn.cursor() + c.execute('SELECT value from haugebot_web_setting where key = ?', (key,)) + value = c.fetchone()[0] + conn.close() + return value + bot = HaugeBot() diff --git a/haugebot_twitch/info_cog.py b/haugebot_twitch/info_cog.py new file mode 100644 index 0000000000000000000000000000000000000000..2e6dc98e23ca67feb809e6014524ed4cd02fe69e --- /dev/null +++ b/haugebot_twitch/info_cog.py @@ -0,0 +1,37 @@ +import asyncio +import random +import sqlite3 + +from twitchio.ext import commands + + +@commands.core.cog() +class InfoCog: + def __init__(self, bot): + self.bot = bot + + async def info_loop(self): + while True: + sleep_duration = int(self.bot.get_setting("WusstestDuSchonLoop")) + await asyncio.sleep(sleep_duration * 60) + + if await self.bot.stream(): + channel = self.bot.channel() + color = self.bot.get_setting("WusstestDuSchonColor") + prefix = self.bot.get_setting("WusstestDuSchonPrefix") + message = self.get_random_message(prefix) + await self.bot.send_me(channel, message, color) + + @staticmethod + def get_random_message(prefix): + conn = sqlite3.connect("db.sqlite3") + + c = conn.cursor() + c.execute('SELECT text, use_prefix from haugebot_web_wusstestduschon where active is true') + wusstestduschon = random.choice(c.fetchall()) + conn.close() + + if wusstestduschon[1] == 1: + return prefix.strip() + " " + wusstestduschon[0].strip() + else: + return wusstestduschon[0] diff --git a/pipi_cog.py b/haugebot_twitch/pipi_cog.py similarity index 100% rename from pipi_cog.py rename to haugebot_twitch/pipi_cog.py diff --git a/vote_cog.py b/haugebot_twitch/vote_cog.py similarity index 99% rename from vote_cog.py rename to haugebot_twitch/vote_cog.py index 3640aa69dec34648df8343dc7080b3dc3bd96906..21b45f23895efc812a6766952ea39f42c7d6f8cc 100644 --- a/vote_cog.py +++ b/haugebot_twitch/vote_cog.py @@ -2,9 +2,8 @@ import asyncio import os import time -from twitchio.ext import commands - import vote_redis +from twitchio.ext import commands @commands.core.cog(name="VoteCog") diff --git a/vote_redis.py b/haugebot_twitch/vote_redis.py similarity index 100% rename from vote_redis.py rename to haugebot_twitch/vote_redis.py diff --git a/haugebot_web/__init__.py b/haugebot_web/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/haugebot_web/admin.py b/haugebot_web/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..846f6b4061a68eda58bc9c76c36603d1e7721ee8 --- /dev/null +++ b/haugebot_web/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/haugebot_web/apps.py b/haugebot_web/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..39b50682c8fe4085d451bcab17b4962904a1260c --- /dev/null +++ b/haugebot_web/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class HaugebotWebConfig(AppConfig): + name = 'haugebot_web' diff --git a/haugebot_web/auth.py b/haugebot_web/auth.py new file mode 100644 index 0000000000000000000000000000000000000000..28b3e4b1dea70935802ebadaca93072e01a704d3 --- /dev/null +++ b/haugebot_web/auth.py @@ -0,0 +1,18 @@ +from django.contrib.auth.backends import BaseBackend + +from .models import TwitchUser + + +class TwitchAuthenticationBackend(BaseBackend): + def authenticated(self, request, user) -> TwitchUser: + find_user = TwitchUser.objects.filter(id=user['id']) + if len(find_user) == 0: + TwitchUser.objects.create_new_twitch_user(user) + return self.authenticate(request, user) + return find_user + + def get_user(self, user_id): + try: + return TwitchUser.objects.get(pk=user_id) + except TwitchUser.DoesNotExist: + return None diff --git a/haugebot_web/forms.py b/haugebot_web/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..b34be0de434c738607a9c61beb43a250d9b72bb3 --- /dev/null +++ b/haugebot_web/forms.py @@ -0,0 +1,53 @@ +from django import forms + +from .models import Setting, TwitchColor + + +class BaseForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super(BaseForm, self).__init__(*args, **kwargs) + for field_name, field in self.fields.items(): + if type(field) is forms.fields.BooleanField: + field.widget.attrs['class'] = ' w3-check ' + field.label_suffix = "" + else: + field.widget.attrs['class'] = ' w3-input ' + field.widget.attrs['placeholder'] = field.label + + +class WusstestDuSchonSettingsForm(forms.Form): + prefix_field = forms.CharField(max_length=50, initial=Setting.objects.get(key="WusstestDuSchonPrefix").value, + label="Präfix") + loop_field = forms.IntegerField(initial=Setting.objects.get(key="WusstestDuSchonLoop").value, + label="Pause (in Minuten)") + color_field = forms.ChoiceField(choices=[(color.color, color.display_name) for color in TwitchColor.objects.all()], + label="Text Farbe") + + def __init__(self, *args, **kwargs): + super(WusstestDuSchonSettingsForm, self).__init__(*args, **kwargs) + + self.fields["prefix_field"].initial = Setting.objects.get(key="WusstestDuSchonPrefix").value + self.fields["loop_field"].initial = Setting.objects.get(key="WusstestDuSchonLoop").value + self.fields["color_field"].initial = TwitchColor.objects.get( + twitch_name=Setting.objects.get(key="WusstestDuSchonColor").value).color + + for field_name, field in self.fields.items(): + if type(field) is forms.fields.BooleanField: + field.widget.attrs['class'] = ' w3-check ' + field.label_suffix = "" + else: + field.widget.attrs['class'] = ' w3-input ' + field.widget.attrs['placeholder'] = field.label + + def save(self): + prefix = Setting.objects.get(key="WusstestDuSchonPrefix") + prefix.value = self.cleaned_data["prefix_field"] + prefix.save() + + loop = Setting.objects.get(key="WusstestDuSchonLoop") + loop.value = self.cleaned_data["loop_field"] + loop.save() + + color = Setting.objects.get(key="WusstestDuSchonColor") + color.value = TwitchColor.objects.get(color=self.cleaned_data["color_field"]).twitch_name + color.save() diff --git a/haugebot_web/managers.py b/haugebot_web/managers.py new file mode 100644 index 0000000000000000000000000000000000000000..d082c721ca00a86b8d1dde11164fd522b09f7661 --- /dev/null +++ b/haugebot_web/managers.py @@ -0,0 +1,12 @@ +from django.contrib.auth import models + + +class TwitchUserManager(models.UserManager): + def create_new_twitch_user(self, user): + new_user = self.create( + id=user['id'], + login=user['login'], + access_token=user['access_token'], + refresh_token=user['refresh_token'] + ) + return new_user diff --git a/haugebot_web/migrations/0001_initial.py b/haugebot_web/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..0dead9154e877883b93ff12d39bafc4bb4226ae0 --- /dev/null +++ b/haugebot_web/migrations/0001_initial.py @@ -0,0 +1,109 @@ +# Generated by Django 3.1.5 on 2021-01-05 12:27 + +from django.db import migrations, models + +import haugebot_web.managers + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Setting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=50)), + ('value', models.CharField(max_length=50)), + ], + ), + migrations.CreateModel( + name='TwitchColor', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('twitch_name', models.CharField(max_length=20)), + ('display_name', models.CharField(max_length=20)), + ('color', models.CharField(max_length=7)), + ], + ), + migrations.CreateModel( + name='TwitchUser', + fields=[ + ('id', models.BigIntegerField(primary_key=True, serialize=False)), + ('login', models.CharField(max_length=50)), + ('access_token', models.CharField(max_length=50)), + ('refresh_token', models.CharField(max_length=50)), + ('last_login', models.DateTimeField(null=True)), + ], + managers=[ + ('objects', haugebot_web.managers.TwitchUserManager()), + ], + ), + migrations.CreateModel( + name='WusstestDuSchon', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('advertised_command', models.CharField(max_length=20)), + ('text', models.TextField(max_length=450)), + ('use_prefix', models.BooleanField(default=True, verbose_name='Präfix verwenden')), + ('active', models.BooleanField(default=True, verbose_name='Aktiv')), + ], + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_setting (key, value) VALUES('WusstestDuSchonPrefix', 'Psssst... wusstest du eigentlich schon,')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_setting (key, value) VALUES('WusstestDuSchonLoop', '10')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_setting (key, value) VALUES('WusstestDuSchonColor', 'HotPink')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (1, 'Red', 'Rot', '#ff0000')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (2, 'Blue', 'Blau', '#0000ff')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (3, 'Green', 'Grün', '#008000')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (4, 'FireBrick', 'Backstein', '#b22222')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (5, 'Coral', 'Korallenrot', '#ff7f50')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (6, 'YellowGreen', 'Gelbliches Grün', '#9acd32')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (7, 'OrangeRed', 'Orangenrot', '#ff4500')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (8, 'SeaGreen', 'Meeresgrün', '#2e8b57')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (9, 'GoldenRod', 'Goldrute', '#daa520')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (10, 'Chocolate', 'Schokoladenbraun', '#d2691e')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (11, 'CadetBlue', 'Marineblau', '#5f9ea0')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (12, 'DodgerBlue', 'Dodgerblau', '#1e90ff')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (13, 'HotPink', 'Leuchtendes Rosa', '#ff69b4')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (14, 'BlueViolet', 'Blauviolett', '#8a2be2')" + ), + migrations.RunSQL( + "INSERT INTO haugebot_web_twitchcolor (id, twitch_name, display_name, color) VALUES (15, 'SpringGreen', 'Frühlingsgrün', '#00ff7f')" + ), + ] diff --git a/haugebot_web/migrations/__init__.py b/haugebot_web/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/haugebot_web/models.py b/haugebot_web/models.py new file mode 100644 index 0000000000000000000000000000000000000000..a65a8578087bc5fee7463cc31f81245536826459 --- /dev/null +++ b/haugebot_web/models.py @@ -0,0 +1,47 @@ +import os + +from django.db import models + +from haugebot_web import twitch_api +from .managers import TwitchUserManager + + +class Setting(models.Model): + key = models.CharField(max_length=50) + value = models.CharField(max_length=50) + + +class TwitchColor(models.Model): + twitch_name = models.CharField(max_length=20) + display_name = models.CharField(max_length=20) + color = models.CharField(max_length=7) + + +class WusstestDuSchon(models.Model): + advertised_command = models.CharField(max_length=20) + text = models.TextField(max_length=450) + use_prefix = models.BooleanField(default=True, verbose_name="Präfix verwenden") + active = models.BooleanField(default=True, verbose_name="Aktiv") + + +class TwitchUser(models.Model): + objects = TwitchUserManager() + + id = models.BigIntegerField(primary_key=True) + login = models.CharField(max_length=50) + access_token = models.CharField(max_length=50) + refresh_token = models.CharField(max_length=50) + last_login = models.DateTimeField(null=True) + + def update_tokens(self, access_token, refresh_token): + self.access_token = access_token + self.refresh_token = refresh_token + self.save() + + def is_authenticated(self): + broadcaster_id = os.getenv("BROADCASTER_ID") + try: + broadcaster = TwitchUser.objects.get(pk=broadcaster_id) + return twitch_api.is_mod(self, broadcaster) + except TwitchUser.DoesNotExist: + return False diff --git a/haugebot_web/static/css/styles.css b/haugebot_web/static/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..ba369ffb6d860e6aea62d577d15adb70a47877f2 --- /dev/null +++ b/haugebot_web/static/css/styles.css @@ -0,0 +1,47 @@ +#content { + margin-left: 200px; +} + +.w3-haugedark { + background: #222222; + color: #fca4ba; +} + +.w3-haugepink { + background: #fca4ba; + color: #000e47; +} + +.w3-haugepinkdark { + background: #f65656; +} + +.w3-haugeblue { + background: #000e47; +} + +.w3-haugepink:hover { + background: #f65656 !important; + color: #000e47; +} + +#logo { + max-width: 100%; +} + +.w3-form-container { + margin-right: 50px; +} + +.w3-display-topright .w3-button { + margin-right: 1em; +} + +.w3-card { + margin-bottom: 20px; +} + +textarea { + max-height: 62px; + resize: none; +} \ No newline at end of file diff --git a/haugebot_web/static/css/w3.css b/haugebot_web/static/css/w3.css new file mode 100644 index 0000000000000000000000000000000000000000..5bc550226da13d9e732632e20c995c7fd9458e5f --- /dev/null +++ b/haugebot_web/static/css/w3.css @@ -0,0 +1,1749 @@ +/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */ +html { + box-sizing: border-box +} + +*, *:before, *:after { + box-sizing: inherit +} + +/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ +html { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +article, aside, details, figcaption, figure, footer, header, main, menu, nav, section { + display: block +} + +summary { + display: list-item +} + +audio, canvas, progress, video { + display: inline-block +} + +progress { + vertical-align: baseline +} + +audio:not([controls]) { + display: none; + height: 0 +} + +[hidden], template { + display: none +} + +a { + background-color: transparent +} + +a:active, a:hover { + outline-width: 0 +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} + +b, strong { + font-weight: bolder +} + +dfn { + font-style: italic +} + +mark { + background: #ff0; + color: #000 +} + +small { + font-size: 80% +} + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sub { + bottom: -0.25em +} + +sup { + top: -0.5em +} + +figure { + margin: 1em 40px +} + +img { + border-style: none +} + +code, kbd, pre, samp { + font-family: monospace, monospace; + font-size: 1em +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +button, input, select, textarea, optgroup { + font: inherit; + margin: 0 +} + +optgroup { + font-weight: bold +} + +button, input { + overflow: visible +} + +button, select { + text-transform: none +} + +button, [type=button], [type=reset], [type=submit] { + -webkit-appearance: button +} + +button::-moz-focus-inner, [type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner { + border-style: none; + padding: 0 +} + +button:-moz-focusring, [type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring { + outline: 1px dotted ButtonText +} + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: .35em .625em .75em +} + +legend { + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} + +textarea { + overflow: auto +} + +[type=checkbox], [type=radio] { + padding: 0 +} + +[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button { + height: auto +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} + +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} + +/* End extract */ +html, body { + font-family: Verdana, sans-serif; + font-size: 15px; + line-height: 1.5 +} + +html { + overflow-x: hidden +} + +h1 { + font-size: 36px +} + +h2 { + font-size: 30px +} + +h3 { + font-size: 24px +} + +h4 { + font-size: 20px +} + +h5 { + font-size: 18px +} + +h6 { + font-size: 16px +} + +.w3-serif { + font-family: serif +} + +.w3-sans-serif { + font-family: sans-serif +} + +.w3-cursive { + font-family: cursive +} + +.w3-monospace { + font-family: monospace +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Segoe UI", Arial, sans-serif; + font-weight: 400; + margin: 10px 0 +} + +.w3-wide { + letter-spacing: 4px +} + +hr { + border: 0; + border-top: 1px solid #eee; + margin: 20px 0 +} + +.w3-image { + max-width: 100%; + height: auto +} + +img { + vertical-align: middle +} + +a { + color: inherit +} + +.w3-table, .w3-table-all { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + display: table +} + +.w3-table-all { + border: 1px solid #ccc +} + +.w3-bordered tr, .w3-table-all tr { + border-bottom: 1px solid #ddd +} + +.w3-striped tbody tr:nth-child(even) { + background-color: #f1f1f1 +} + +.w3-table-all tr:nth-child(odd) { + background-color: #fff +} + +.w3-table-all tr:nth-child(even) { + background-color: #f1f1f1 +} + +.w3-hoverable tbody tr:hover, .w3-ul.w3-hoverable li:hover { + background-color: #ccc +} + +.w3-centered tr th, .w3-centered tr td { + text-align: center +} + +.w3-table td, .w3-table th, .w3-table-all td, .w3-table-all th { + padding: 8px 8px; + display: table-cell; + text-align: left; + vertical-align: top +} + +.w3-table th:first-child, .w3-table td:first-child, .w3-table-all th:first-child, .w3-table-all td:first-child { + padding-left: 16px +} + +.w3-btn, .w3-button { + border: none; + display: inline-block; + padding: 8px 16px; + vertical-align: middle; + overflow: hidden; + text-decoration: none; + color: inherit; + background-color: inherit; + text-align: center; + cursor: pointer; + white-space: nowrap +} + +.w3-btn:hover { + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19) +} + +.w3-btn, .w3-button { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +.w3-disabled, .w3-btn:disabled, .w3-button:disabled { + cursor: not-allowed; + opacity: 0.3 +} + +.w3-disabled *, :disabled * { + pointer-events: none +} + +.w3-btn.w3-disabled:hover, .w3-btn:disabled:hover { + box-shadow: none +} + +.w3-badge, .w3-tag { + background-color: #000; + color: #fff; + display: inline-block; + padding-left: 8px; + padding-right: 8px; + text-align: center +} + +.w3-badge { + border-radius: 50% +} + +.w3-ul { + list-style-type: none; + padding: 0; + margin: 0 +} + +.w3-ul li { + padding: 8px 16px; + border-bottom: 1px solid #ddd +} + +.w3-ul li:last-child { + border-bottom: none +} + +.w3-tooltip, .w3-display-container { + position: relative +} + +.w3-tooltip .w3-text { + display: none +} + +.w3-tooltip:hover .w3-text { + display: inline-block +} + +.w3-ripple:active { + opacity: 0.5 +} + +.w3-ripple { + transition: opacity 0s +} + +.w3-input { + padding: 8px; + display: block; + border: none; + border-bottom: 1px solid #ccc; + width: 100% +} + +.w3-select { + padding: 9px 0; + width: 100%; + border: none; + border-bottom: 1px solid #ccc +} + +.w3-dropdown-click, .w3-dropdown-hover { + position: relative; + display: inline-block; + cursor: pointer +} + +.w3-dropdown-hover:hover .w3-dropdown-content { + display: block +} + +.w3-dropdown-hover:first-child, .w3-dropdown-click:hover { + background-color: #ccc; + color: #000 +} + +.w3-dropdown-hover:hover > .w3-button:first-child, .w3-dropdown-click:hover > .w3-button:first-child { + background-color: #ccc; + color: #000 +} + +.w3-dropdown-content { + cursor: auto; + color: #000; + background-color: #fff; + display: none; + position: absolute; + min-width: 160px; + margin: 0; + padding: 0; + z-index: 1 +} + +.w3-check, .w3-radio { + width: 24px; + height: 24px; + position: relative; + top: 6px +} + +.w3-sidebar { + height: 100%; + width: 200px; + background-color: #fff; + position: fixed !important; + z-index: 1; + overflow: auto +} + +.w3-bar-block .w3-dropdown-hover, .w3-bar-block .w3-dropdown-click { + width: 100% +} + +.w3-bar-block .w3-dropdown-hover .w3-dropdown-content, .w3-bar-block .w3-dropdown-click .w3-dropdown-content { + min-width: 100% +} + +.w3-bar-block .w3-dropdown-hover .w3-button, .w3-bar-block .w3-dropdown-click .w3-button { + width: 100%; + text-align: left; + padding: 8px 16px +} + +.w3-main, #main { + transition: margin-left .4s +} + +.w3-modal { + z-index: 3; + display: none; + padding-top: 100px; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.4) +} + +.w3-modal-content { + margin: auto; + background-color: #fff; + position: relative; + padding: 0; + outline: 0; + width: 600px +} + +.w3-bar { + width: 100%; + overflow: hidden +} + +.w3-center .w3-bar { + display: inline-block; + width: auto +} + +.w3-bar .w3-bar-item { + padding: 8px 16px; + float: left; + width: auto; + border: none; + display: block; + outline: 0 +} + +.w3-bar .w3-dropdown-hover, .w3-bar .w3-dropdown-click { + position: static; + float: left +} + +.w3-bar .w3-button { + white-space: normal +} + +.w3-bar-block .w3-bar-item { + width: 100%; + display: block; + padding: 8px 16px; + text-align: left; + border: none; + white-space: normal; + float: none; + outline: 0 +} + +.w3-bar-block.w3-center .w3-bar-item { + text-align: center +} + +.w3-block { + display: block; + width: 100% +} + +.w3-responsive { + display: block; + overflow-x: auto +} + +.w3-container:after, .w3-container:before, .w3-panel:after, .w3-panel:before, .w3-row:after, .w3-row:before, .w3-row-padding:after, .w3-row-padding:before, +.w3-cell-row:before, .w3-cell-row:after, .w3-clear:after, .w3-clear:before, .w3-bar:before, .w3-bar:after { + content: ""; + display: table; + clear: both +} + +.w3-col, .w3-half, .w3-third, .w3-twothird, .w3-threequarter, .w3-quarter { + float: left; + width: 100% +} + +.w3-col.s1 { + width: 8.33333% +} + +.w3-col.s2 { + width: 16.66666% +} + +.w3-col.s3 { + width: 24.99999% +} + +.w3-col.s4 { + width: 33.33333% +} + +.w3-col.s5 { + width: 41.66666% +} + +.w3-col.s6 { + width: 49.99999% +} + +.w3-col.s7 { + width: 58.33333% +} + +.w3-col.s8 { + width: 66.66666% +} + +.w3-col.s9 { + width: 74.99999% +} + +.w3-col.s10 { + width: 83.33333% +} + +.w3-col.s11 { + width: 91.66666% +} + +.w3-col.s12 { + width: 99.99999% +} + +@media (min-width: 601px) { + .w3-col.m1 { + width: 8.33333% + } + + .w3-col.m2 { + width: 16.66666% + } + + .w3-col.m3, .w3-quarter { + width: 24.99999% + } + + .w3-col.m4, .w3-third { + width: 33.33333% + } + + .w3-col.m5 { + width: 41.66666% + } + + .w3-col.m6, .w3-half { + width: 49.99999% + } + + .w3-col.m7 { + width: 58.33333% + } + + .w3-col.m8, .w3-twothird { + width: 66.66666% + } + + .w3-col.m9, .w3-threequarter { + width: 74.99999% + } + + .w3-col.m10 { + width: 83.33333% + } + + .w3-col.m11 { + width: 91.66666% + } + + .w3-col.m12 { + width: 99.99999% + } +} + +@media (min-width: 993px) { + .w3-col.l1 { + width: 8.33333% + } + + .w3-col.l2 { + width: 16.66666% + } + + .w3-col.l3 { + width: 24.99999% + } + + .w3-col.l4 { + width: 33.33333% + } + + .w3-col.l5 { + width: 41.66666% + } + + .w3-col.l6 { + width: 49.99999% + } + + .w3-col.l7 { + width: 58.33333% + } + + .w3-col.l8 { + width: 66.66666% + } + + .w3-col.l9 { + width: 74.99999% + } + + .w3-col.l10 { + width: 83.33333% + } + + .w3-col.l11 { + width: 91.66666% + } + + .w3-col.l12 { + width: 99.99999% + } +} + +.w3-rest { + overflow: hidden +} + +.w3-stretch { + margin-left: -16px; + margin-right: -16px +} + +.w3-content, .w3-auto { + margin-left: auto; + margin-right: auto +} + +.w3-content { + max-width: 980px +} + +.w3-auto { + max-width: 1140px +} + +.w3-cell-row { + display: table; + width: 100% +} + +.w3-cell { + display: table-cell +} + +.w3-cell-top { + vertical-align: top +} + +.w3-cell-middle { + vertical-align: middle +} + +.w3-cell-bottom { + vertical-align: bottom +} + +.w3-hide { + display: none !important +} + +.w3-show-block, .w3-show { + display: block !important +} + +.w3-show-inline-block { + display: inline-block !important +} + +@media (max-width: 1205px) { + .w3-auto { + max-width: 95% + } +} + +@media (max-width: 600px) { + .w3-modal-content { + margin: 0 10px; + width: auto !important + } + + .w3-modal { + padding-top: 30px + } + + .w3-dropdown-hover.w3-mobile .w3-dropdown-content, .w3-dropdown-click.w3-mobile .w3-dropdown-content { + position: relative + } + + .w3-hide-small { + display: none !important + } + + .w3-mobile { + display: block; + width: 100% !important + } + + .w3-bar-item.w3-mobile, .w3-dropdown-hover.w3-mobile, .w3-dropdown-click.w3-mobile { + text-align: center + } + + .w3-dropdown-hover.w3-mobile, .w3-dropdown-hover.w3-mobile .w3-btn, .w3-dropdown-hover.w3-mobile .w3-button, .w3-dropdown-click.w3-mobile, .w3-dropdown-click.w3-mobile .w3-btn, .w3-dropdown-click.w3-mobile .w3-button { + width: 100% + } +} + +@media (max-width: 768px) { + .w3-modal-content { + width: 500px + } + + .w3-modal { + padding-top: 50px + } +} + +@media (min-width: 993px) { + .w3-modal-content { + width: 900px + } + + .w3-hide-large { + display: none !important + } + + .w3-sidebar.w3-collapse { + display: block !important + } +} + +@media (max-width: 992px) and (min-width: 601px) { + .w3-hide-medium { + display: none !important + } +} + +@media (max-width: 992px) { + .w3-sidebar.w3-collapse { + display: none + } + + .w3-main { + margin-left: 0 !important; + margin-right: 0 !important + } + + .w3-auto { + max-width: 100% + } +} + +.w3-top, .w3-bottom { + position: fixed; + width: 100%; + z-index: 1 +} + +.w3-top { + top: 0 +} + +.w3-bottom { + bottom: 0 +} + +.w3-overlay { + position: fixed; + display: none; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 2 +} + +.w3-display-topleft { + position: absolute; + left: 0; + top: 0 +} + +.w3-display-topright { + position: absolute; + right: 0; + top: 0 +} + +.w3-display-bottomleft { + position: absolute; + left: 0; + bottom: 0 +} + +.w3-display-bottomright { + position: absolute; + right: 0; + bottom: 0 +} + +.w3-display-middle { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%) +} + +.w3-display-left { + position: absolute; + top: 50%; + left: 0%; + transform: translate(0%, -50%); + -ms-transform: translate(-0%, -50%) +} + +.w3-display-right { + position: absolute; + top: 50%; + right: 0%; + transform: translate(0%, -50%); + -ms-transform: translate(0%, -50%) +} + +.w3-display-topmiddle { + position: absolute; + left: 50%; + top: 0; + transform: translate(-50%, 0%); + -ms-transform: translate(-50%, 0%) +} + +.w3-display-bottommiddle { + position: absolute; + left: 50%; + bottom: 0; + transform: translate(-50%, 0%); + -ms-transform: translate(-50%, 0%) +} + +.w3-display-container:hover .w3-display-hover { + display: block +} + +.w3-display-container:hover span.w3-display-hover { + display: inline-block +} + +.w3-display-hover { + display: none +} + +.w3-display-position { + position: absolute +} + +.w3-circle { + border-radius: 50% +} + +.w3-round-small { + border-radius: 2px +} + +.w3-round, .w3-round-medium { + border-radius: 4px +} + +.w3-round-large { + border-radius: 8px +} + +.w3-round-xlarge { + border-radius: 16px +} + +.w3-round-xxlarge { + border-radius: 32px +} + +.w3-row-padding, .w3-row-padding > .w3-half, .w3-row-padding > .w3-third, .w3-row-padding > .w3-twothird, .w3-row-padding > .w3-threequarter, .w3-row-padding > .w3-quarter, .w3-row-padding > .w3-col { + padding: 0 8px +} + +.w3-container, .w3-panel { + padding: 0.01em 16px +} + +.w3-panel { + margin-top: 16px; + margin-bottom: 16px +} + +.w3-code, .w3-codespan { + font-family: Consolas, "courier new"; + font-size: 16px +} + +.w3-code { + width: auto; + background-color: #fff; + padding: 8px 12px; + border-left: 4px solid #4CAF50; + word-wrap: break-word +} + +.w3-codespan { + color: crimson; + background-color: #f1f1f1; + padding-left: 4px; + padding-right: 4px; + font-size: 110% +} + +.w3-card, .w3-card-2 { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12) +} + +.w3-card-4, .w3-hover-shadow:hover { + box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19) +} + +.w3-spin { + animation: w3-spin 2s infinite linear +} + +@keyframes w3-spin { + 0% { + transform: rotate(0deg) + } + 100% { + transform: rotate(359deg) + } +} + +.w3-animate-fading { + animation: fading 10s infinite +} + +@keyframes fading { + 0% { + opacity: 0 + } + 50% { + opacity: 1 + } + 100% { + opacity: 0 + } +} + +.w3-animate-opacity { + animation: opac 0.8s +} + +@keyframes opac { + from { + opacity: 0 + } + to { + opacity: 1 + } +} + +.w3-animate-top { + position: relative; + animation: animatetop 0.4s +} + +@keyframes animatetop { + from { + top: -300px; + opacity: 0 + } + to { + top: 0; + opacity: 1 + } +} + +.w3-animate-left { + position: relative; + animation: animateleft 0.4s +} + +@keyframes animateleft { + from { + left: -300px; + opacity: 0 + } + to { + left: 0; + opacity: 1 + } +} + +.w3-animate-right { + position: relative; + animation: animateright 0.4s +} + +@keyframes animateright { + from { + right: -300px; + opacity: 0 + } + to { + right: 0; + opacity: 1 + } +} + +.w3-animate-bottom { + position: relative; + animation: animatebottom 0.4s +} + +@keyframes animatebottom { + from { + bottom: -300px; + opacity: 0 + } + to { + bottom: 0; + opacity: 1 + } +} + +.w3-animate-zoom { + animation: animatezoom 0.6s +} + +@keyframes animatezoom { + from { + transform: scale(0) + } + to { + transform: scale(1) + } +} + +.w3-animate-input { + transition: width 0.4s ease-in-out +} + +.w3-animate-input:focus { + width: 100% !important +} + +.w3-opacity, .w3-hover-opacity:hover { + opacity: 0.60 +} + +.w3-opacity-off, .w3-hover-opacity-off:hover { + opacity: 1 +} + +.w3-opacity-max { + opacity: 0.25 +} + +.w3-opacity-min { + opacity: 0.75 +} + +.w3-greyscale-max, .w3-grayscale-max, .w3-hover-greyscale:hover, .w3-hover-grayscale:hover { + filter: grayscale(100%) +} + +.w3-greyscale, .w3-grayscale { + filter: grayscale(75%) +} + +.w3-greyscale-min, .w3-grayscale-min { + filter: grayscale(50%) +} + +.w3-sepia { + filter: sepia(75%) +} + +.w3-sepia-max, .w3-hover-sepia:hover { + filter: sepia(100%) +} + +.w3-sepia-min { + filter: sepia(50%) +} + +.w3-tiny { + font-size: 10px !important +} + +.w3-small { + font-size: 12px !important +} + +.w3-medium { + font-size: 15px !important +} + +.w3-large { + font-size: 18px !important +} + +.w3-xlarge { + font-size: 24px !important +} + +.w3-xxlarge { + font-size: 36px !important +} + +.w3-xxxlarge { + font-size: 48px !important +} + +.w3-jumbo { + font-size: 64px !important +} + +.w3-left-align { + text-align: left !important +} + +.w3-right-align { + text-align: right !important +} + +.w3-justify { + text-align: justify !important +} + +.w3-center { + text-align: center !important +} + +.w3-border-0 { + border: 0 !important +} + +.w3-border { + border: 1px solid #ccc !important +} + +.w3-border-top { + border-top: 1px solid #ccc !important +} + +.w3-border-bottom { + border-bottom: 1px solid #ccc !important +} + +.w3-border-left { + border-left: 1px solid #ccc !important +} + +.w3-border-right { + border-right: 1px solid #ccc !important +} + +.w3-topbar { + border-top: 6px solid #ccc !important +} + +.w3-bottombar { + border-bottom: 6px solid #ccc !important +} + +.w3-leftbar { + border-left: 6px solid #ccc !important +} + +.w3-rightbar { + border-right: 6px solid #ccc !important +} + +.w3-section, .w3-code { + margin-top: 16px !important; + margin-bottom: 16px !important +} + +.w3-margin { + margin: 16px !important +} + +.w3-margin-top { + margin-top: 16px !important +} + +.w3-margin-bottom { + margin-bottom: 16px !important +} + +.w3-margin-left { + margin-left: 16px !important +} + +.w3-margin-right { + margin-right: 16px !important +} + +.w3-padding-small { + padding: 4px 8px !important +} + +.w3-padding { + padding: 8px 16px !important +} + +.w3-padding-large { + padding: 12px 24px !important +} + +.w3-padding-16 { + padding-top: 16px !important; + padding-bottom: 16px !important +} + +.w3-padding-24 { + padding-top: 24px !important; + padding-bottom: 24px !important +} + +.w3-padding-32 { + padding-top: 32px !important; + padding-bottom: 32px !important +} + +.w3-padding-48 { + padding-top: 48px !important; + padding-bottom: 48px !important +} + +.w3-padding-64 { + padding-top: 64px !important; + padding-bottom: 64px !important +} + +.w3-padding-top-64 { + padding-top: 64px !important +} + +.w3-padding-top-48 { + padding-top: 48px !important +} + +.w3-padding-top-32 { + padding-top: 32px !important +} + +.w3-padding-top-24 { + padding-top: 24px !important +} + +.w3-left { + float: left !important +} + +.w3-right { + float: right !important +} + +.w3-button:hover { + color: #000 !important; + background-color: #ccc !important +} + +.w3-transparent, .w3-hover-none:hover { + background-color: transparent !important +} + +.w3-hover-none:hover { + box-shadow: none !important +} + +/* Colors */ +.w3-amber, .w3-hover-amber:hover { + color: #000 !important; + background-color: #ffc107 !important +} + +.w3-aqua, .w3-hover-aqua:hover { + color: #000 !important; + background-color: #00ffff !important +} + +.w3-blue, .w3-hover-blue:hover { + color: #fff !important; + background-color: #2196F3 !important +} + +.w3-light-blue, .w3-hover-light-blue:hover { + color: #000 !important; + background-color: #87CEEB !important +} + +.w3-brown, .w3-hover-brown:hover { + color: #fff !important; + background-color: #795548 !important +} + +.w3-cyan, .w3-hover-cyan:hover { + color: #000 !important; + background-color: #00bcd4 !important +} + +.w3-blue-grey, .w3-hover-blue-grey:hover, .w3-blue-gray, .w3-hover-blue-gray:hover { + color: #fff !important; + background-color: #607d8b !important +} + +.w3-green, .w3-hover-green:hover { + color: #fff !important; + background-color: #4CAF50 !important +} + +.w3-light-green, .w3-hover-light-green:hover { + color: #000 !important; + background-color: #8bc34a !important +} + +.w3-indigo, .w3-hover-indigo:hover { + color: #fff !important; + background-color: #3f51b5 !important +} + +.w3-khaki, .w3-hover-khaki:hover { + color: #000 !important; + background-color: #f0e68c !important +} + +.w3-lime, .w3-hover-lime:hover { + color: #000 !important; + background-color: #cddc39 !important +} + +.w3-orange, .w3-hover-orange:hover { + color: #000 !important; + background-color: #ff9800 !important +} + +.w3-deep-orange, .w3-hover-deep-orange:hover { + color: #fff !important; + background-color: #ff5722 !important +} + +.w3-pink, .w3-hover-pink:hover { + color: #fff !important; + background-color: #e91e63 !important +} + +.w3-purple, .w3-hover-purple:hover { + color: #fff !important; + background-color: #9c27b0 !important +} + +.w3-deep-purple, .w3-hover-deep-purple:hover { + color: #fff !important; + background-color: #673ab7 !important +} + +.w3-red, .w3-hover-red:hover { + color: #fff !important; + background-color: #f44336 !important +} + +.w3-sand, .w3-hover-sand:hover { + color: #000 !important; + background-color: #fdf5e6 !important +} + +.w3-teal, .w3-hover-teal:hover { + color: #fff !important; + background-color: #009688 !important +} + +.w3-yellow, .w3-hover-yellow:hover { + color: #000 !important; + background-color: #ffeb3b !important +} + +.w3-white, .w3-hover-white:hover { + color: #000 !important; + background-color: #fff !important +} + +.w3-black, .w3-hover-black:hover { + color: #fff !important; + background-color: #000 !important +} + +.w3-grey, .w3-hover-grey:hover, .w3-gray, .w3-hover-gray:hover { + color: #000 !important; + background-color: #9e9e9e !important +} + +.w3-light-grey, .w3-hover-light-grey:hover, .w3-light-gray, .w3-hover-light-gray:hover { + color: #000 !important; + background-color: #f1f1f1 !important +} + +.w3-dark-grey, .w3-hover-dark-grey:hover, .w3-dark-gray, .w3-hover-dark-gray:hover { + color: #fff !important; + background-color: #616161 !important +} + +.w3-pale-red, .w3-hover-pale-red:hover { + color: #000 !important; + background-color: #ffdddd !important +} + +.w3-pale-green, .w3-hover-pale-green:hover { + color: #000 !important; + background-color: #ddffdd !important +} + +.w3-pale-yellow, .w3-hover-pale-yellow:hover { + color: #000 !important; + background-color: #ffffcc !important +} + +.w3-pale-blue, .w3-hover-pale-blue:hover { + color: #000 !important; + background-color: #ddffff !important +} + +.w3-text-amber, .w3-hover-text-amber:hover { + color: #ffc107 !important +} + +.w3-text-aqua, .w3-hover-text-aqua:hover { + color: #00ffff !important +} + +.w3-text-blue, .w3-hover-text-blue:hover { + color: #2196F3 !important +} + +.w3-text-light-blue, .w3-hover-text-light-blue:hover { + color: #87CEEB !important +} + +.w3-text-brown, .w3-hover-text-brown:hover { + color: #795548 !important +} + +.w3-text-cyan, .w3-hover-text-cyan:hover { + color: #00bcd4 !important +} + +.w3-text-blue-grey, .w3-hover-text-blue-grey:hover, .w3-text-blue-gray, .w3-hover-text-blue-gray:hover { + color: #607d8b !important +} + +.w3-text-green, .w3-hover-text-green:hover { + color: #4CAF50 !important +} + +.w3-text-light-green, .w3-hover-text-light-green:hover { + color: #8bc34a !important +} + +.w3-text-indigo, .w3-hover-text-indigo:hover { + color: #3f51b5 !important +} + +.w3-text-khaki, .w3-hover-text-khaki:hover { + color: #b4aa50 !important +} + +.w3-text-lime, .w3-hover-text-lime:hover { + color: #cddc39 !important +} + +.w3-text-orange, .w3-hover-text-orange:hover { + color: #ff9800 !important +} + +.w3-text-deep-orange, .w3-hover-text-deep-orange:hover { + color: #ff5722 !important +} + +.w3-text-pink, .w3-hover-text-pink:hover { + color: #e91e63 !important +} + +.w3-text-purple, .w3-hover-text-purple:hover { + color: #9c27b0 !important +} + +.w3-text-deep-purple, .w3-hover-text-deep-purple:hover { + color: #673ab7 !important +} + +.w3-text-red, .w3-hover-text-red:hover { + color: #f44336 !important +} + +.w3-text-sand, .w3-hover-text-sand:hover { + color: #fdf5e6 !important +} + +.w3-text-teal, .w3-hover-text-teal:hover { + color: #009688 !important +} + +.w3-text-yellow, .w3-hover-text-yellow:hover { + color: #d2be0e !important +} + +.w3-text-white, .w3-hover-text-white:hover { + color: #fff !important +} + +.w3-text-black, .w3-hover-text-black:hover { + color: #000 !important +} + +.w3-text-grey, .w3-hover-text-grey:hover, .w3-text-gray, .w3-hover-text-gray:hover { + color: #757575 !important +} + +.w3-text-light-grey, .w3-hover-text-light-grey:hover, .w3-text-light-gray, .w3-hover-text-light-gray:hover { + color: #f1f1f1 !important +} + +.w3-text-dark-grey, .w3-hover-text-dark-grey:hover, .w3-text-dark-gray, .w3-hover-text-dark-gray:hover { + color: #3a3a3a !important +} + +.w3-border-amber, .w3-hover-border-amber:hover { + border-color: #ffc107 !important +} + +.w3-border-aqua, .w3-hover-border-aqua:hover { + border-color: #00ffff !important +} + +.w3-border-blue, .w3-hover-border-blue:hover { + border-color: #2196F3 !important +} + +.w3-border-light-blue, .w3-hover-border-light-blue:hover { + border-color: #87CEEB !important +} + +.w3-border-brown, .w3-hover-border-brown:hover { + border-color: #795548 !important +} + +.w3-border-cyan, .w3-hover-border-cyan:hover { + border-color: #00bcd4 !important +} + +.w3-border-blue-grey, .w3-hover-border-blue-grey:hover, .w3-border-blue-gray, .w3-hover-border-blue-gray:hover { + border-color: #607d8b !important +} + +.w3-border-green, .w3-hover-border-green:hover { + border-color: #4CAF50 !important +} + +.w3-border-light-green, .w3-hover-border-light-green:hover { + border-color: #8bc34a !important +} + +.w3-border-indigo, .w3-hover-border-indigo:hover { + border-color: #3f51b5 !important +} + +.w3-border-khaki, .w3-hover-border-khaki:hover { + border-color: #f0e68c !important +} + +.w3-border-lime, .w3-hover-border-lime:hover { + border-color: #cddc39 !important +} + +.w3-border-orange, .w3-hover-border-orange:hover { + border-color: #ff9800 !important +} + +.w3-border-deep-orange, .w3-hover-border-deep-orange:hover { + border-color: #ff5722 !important +} + +.w3-border-pink, .w3-hover-border-pink:hover { + border-color: #e91e63 !important +} + +.w3-border-purple, .w3-hover-border-purple:hover { + border-color: #9c27b0 !important +} + +.w3-border-deep-purple, .w3-hover-border-deep-purple:hover { + border-color: #673ab7 !important +} + +.w3-border-red, .w3-hover-border-red:hover { + border-color: #f44336 !important +} + +.w3-border-sand, .w3-hover-border-sand:hover { + border-color: #fdf5e6 !important +} + +.w3-border-teal, .w3-hover-border-teal:hover { + border-color: #009688 !important +} + +.w3-border-yellow, .w3-hover-border-yellow:hover { + border-color: #ffeb3b !important +} + +.w3-border-white, .w3-hover-border-white:hover { + border-color: #fff !important +} + +.w3-border-black, .w3-hover-border-black:hover { + border-color: #000 !important +} + +.w3-border-grey, .w3-hover-border-grey:hover, .w3-border-gray, .w3-hover-border-gray:hover { + border-color: #9e9e9e !important +} + +.w3-border-light-grey, .w3-hover-border-light-grey:hover, .w3-border-light-gray, .w3-hover-border-light-gray:hover { + border-color: #f1f1f1 !important +} + +.w3-border-dark-grey, .w3-hover-border-dark-grey:hover, .w3-border-dark-gray, .w3-hover-border-dark-gray:hover { + border-color: #616161 !important +} + +.w3-border-pale-red, .w3-hover-border-pale-red:hover { + border-color: #ffe7e7 !important +} + +.w3-border-pale-green, .w3-hover-border-pale-green:hover { + border-color: #e7ffe7 !important +} + +.w3-border-pale-yellow, .w3-hover-border-pale-yellow:hover { + border-color: #ffffcc !important +} + +.w3-border-pale-blue, .w3-hover-border-pale-blue:hover { + border-color: #e7ffff !important +} \ No newline at end of file diff --git a/haugebot_web/static/images/logo.png b/haugebot_web/static/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..00a09cfe416e2047ca4cb5616e1930870c1e5ff5 Binary files /dev/null and b/haugebot_web/static/images/logo.png differ diff --git a/haugebot_web/templates/color_select_option.html b/haugebot_web/templates/color_select_option.html new file mode 100644 index 0000000000000000000000000000000000000000..8ad45cab5210de8a5e28655b69d5711e2b8ac03f --- /dev/null +++ b/haugebot_web/templates/color_select_option.html @@ -0,0 +1,2 @@ +<option value="{{ widget.value|stringformat:'s' }}"{% include "django/forms/widgets/attrs.html" %} + style="{{ widget.value|stringformat:'s' }}">{{ widget.label }}</option> diff --git a/haugebot_web/templates/form.html b/haugebot_web/templates/form.html new file mode 100644 index 0000000000000000000000000000000000000000..5b2a1bda585e2eb4874944d2d781a439af2d1c3d --- /dev/null +++ b/haugebot_web/templates/form.html @@ -0,0 +1,52 @@ +{% extends 'layout.html' %} + +{% block content %} + <form method="post"> + {% csrf_token %} + {% if form %} + <div class="w3-card w3-white w3-display-container"> + <div class="w3-container w3-form-container"> + {{ form.non_field_errors }} + {% for field in form %} + <p> + {{ field.errors }} + {{ field.label_tag }} + {{ field }} + </p> + {% endfor %} + </div> + </div> + {% endif %} + {{ formset.management_form }} + {% for form in formset %} + <div class="w3-card w3-white w3-display-container"> + <div class="w3-container w3-form-container"> + {{ form.non_field_errors }} + {% for field in form %} + <p> + {{ field.errors }} + {{ field }} + {% if field.widget_type == "checkbox" %} + {{ field.label_tag }} + {% endif %} + </p> + {% endfor %} + </div> + {% if form.fields.id.initial %} + <div class="w3-display-topright"> + <p><a href="{% url remove_url form.fields.id.initial %}" class="w3-button w3-haugepink"><i + class="fas fa-trash-alt"></i></a></p> + </div> + {% endif %} + </div> + {% endfor %} + <input type="submit" value="Add/Save" class="w3-button w3-haugepink w3-block"> + </form> + + <script> + color_field_options = document.querySelectorAll("#id_color_field option"); + color_field_options.forEach(value => { + value.style = "background: " + value.getAttribute("value") + ";" + }) + </script> +{% endblock %} \ No newline at end of file diff --git a/haugebot_web/templates/home.html b/haugebot_web/templates/home.html new file mode 100644 index 0000000000000000000000000000000000000000..e1011a604437f651e978b36ac072706f94fa0021 --- /dev/null +++ b/haugebot_web/templates/home.html @@ -0,0 +1,5 @@ +{% extends 'layout.html' %} + +{% block content %} + {{ status }} +{% endblock %} \ No newline at end of file diff --git a/haugebot_web/templates/layout.html b/haugebot_web/templates/layout.html new file mode 100644 index 0000000000000000000000000000000000000000..d5162dfa834b79a4f0fa20cc4c803eee569170b7 --- /dev/null +++ b/haugebot_web/templates/layout.html @@ -0,0 +1,36 @@ +{% load static %} + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>{{ title }}</title> + <link rel="shortcut icon" type="image/png" href="{% static 'images/logo.png' %}"> + <script src="https://kit.fontawesome.com/cdec119da7.js" crossorigin="anonymous"></script> + <link rel="stylesheet" href="{% static 'css/w3.css' %}" type="text/css"> + <link rel="stylesheet" href="{% static 'css/styles.css' %}" type="text/css"> +</head> +<body class="w3-light-gray"> +<nav> + <div class="w3-sidebar w3-bar-block w3-haugedark "> + <a href="{% url 'home' %}" class="logo"><img src="{% static 'images/logo.png' %}" id="logo"/></a> + {% if user.is_authenticated %} + <a href="{% url 'wusstest_du_schon' %}" + class="w3-bar-item w3-button {% if title == "Wusstest du Schon?" %}w3-light-gray{% endif %}">Wusstest du + Schon?</a> + <a href="{% url 'logout' %}" class="w3-bar-item w3-button">Logout</a> + {% else %} + <a href="{% url 'login' %}" class="w3-bar-item w3-button">Login</a> + {% endif %} + </div> +</nav> +<div id="content"> + <div class="w3-container"> + <h1>{{ title }}</h1> + </div> + <div class="w3-container"> + {% block content %}{% endblock %} + </div> +</div> +</body> +</html> \ No newline at end of file diff --git a/haugebot_web/tests.py b/haugebot_web/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..a39b155ac3ee946fb97efafe6ecbb42f571cd7ad --- /dev/null +++ b/haugebot_web/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/haugebot_web/twitch_api.py b/haugebot_web/twitch_api.py new file mode 100644 index 0000000000000000000000000000000000000000..c544f5466f17587e519ef08e0daffc5a2ab5453b --- /dev/null +++ b/haugebot_web/twitch_api.py @@ -0,0 +1,109 @@ +import os +from datetime import datetime + +import requests +from django.utils.http import urlencode + +token_url = "https://id.twitch.tv/oauth2/token?client_id={0}&client_secret={1}&grant_type=client_credentials" +clips_url = "https://api.twitch.tv/helix/clips?broadcaster_id={0}&first=100" +broadcaster_id = os.getenv("BROADCASTER_ID") +client_id = os.getenv("CLIENT_ID") +client_secret = os.getenv("CLIENT_SECRET") +access_token = None + + +def get_access_token(): + global access_token + + response = requests.post(token_url.format(client_id, client_secret)) + credentials = response.json() + access_token = credentials["access_token"] + + +def call(url): + if access_token: + response = requests.get(url, headers={ + 'Authorization': 'Bearer {}'.format(access_token), + 'Client-Id': client_id + }) + if response.status_code == 200: + return response.json() + else: + get_access_token() + response = requests.get(url, headers={ + 'Authorization': 'Bearer {}'.format(access_token), + 'Client-Id': client_id + }) + if response.status_code == 200: + return response.json() + else: + get_access_token() + response = requests.get(url, headers={ + 'Authorization': 'Bearer {}'.format(access_token), + 'Client-Id': client_id + }) + if response.status_code == 200: + return response.json() + + +def get_clips(cursor=None, all_clips=False, today=False): + clips = [] + url = clips_url.format(broadcaster_id) + if cursor: + url += "&after=" + cursor + if today: + url += "&started_at=" + get_date() + + clips_json = call(url) + + if data := clips_json.get("data"): + for clip in data: + if thumbnail_url := clip.get("thumbnail_url"): + clip_url = thumbnail_url.split("-preview-")[0] + ".mp4" + clips.append(clip_url) + + if all_clips: + if pagination := clips_json.get("pagination"): + if new_cursor := pagination.get("cursor"): + clips.extend(get_clips(cursor=new_cursor, all_clips=all_clips, today=today)) + return clips + + +def get_date(): + now = datetime.now() + dt = datetime(day=now.day, month=now.month, year=now.year) + return dt.isoformat() + "Z" + + +def is_mod(user, broadcaster): + if user.id == broadcaster.id: + return True + + response = requests.get( + f"https://api.twitch.tv/helix/moderation/moderators?broadcaster_id={broadcaster.id}&user_id={user.id}", + headers={ + 'Authorization': f'Bearer {broadcaster.access_token}', + 'Client-Id': client_id + }) + + if response.status_code == 401 and response.json().get("message") == "Invalid OAuth token": + if not refresh_access_token(broadcaster): + return False + return is_mod(user, broadcaster) + + return response.json().get("data") is not None + + +def refresh_access_token(broadcaster): + url = "https://id.twitch.tv/oauth2/token?" + urlencode( + {"grant_type": "refresh_token", "refresh_token": broadcaster.refresh_token, "client_id": client_id, + "client_secret": client_secret}) + response = requests.post(url) + + if response.status_code == 400 and response.json().get("message") == "Invalid refresh token": + return None + + json = response.json() + access_token = json["access_token"] + refresh_token = json["refresh_token"] + broadcaster.update_tokens(access_token, refresh_token) diff --git a/haugebot_web/views.py b/haugebot_web/views.py new file mode 100644 index 0000000000000000000000000000000000000000..688e965c9e568a4bd5709c69869bab2c9b85cfaf --- /dev/null +++ b/haugebot_web/views.py @@ -0,0 +1,88 @@ +import os + +import requests +from django.contrib.auth import authenticate, login as django_login, logout as django_logout +from django.contrib.auth.decorators import login_required +from django.forms import modelformset_factory +from django.shortcuts import render, redirect + +from .forms import BaseForm, WusstestDuSchonSettingsForm +from .models import WusstestDuSchon + + +# Create your views here. +def home(request): + return render(request, "home.html", {'title': 'HaugeBot'}) + + +@login_required(login_url="/login") +def wusstest_du_schon(request): + WusstestDuSchonFormSet = modelformset_factory(WusstestDuSchon, form=BaseForm, + fields=('advertised_command', 'text', 'use_prefix', 'active'), + field_classes=['']) + if request.method == "POST": + settings_form = WusstestDuSchonSettingsForm(request.POST) + formset = WusstestDuSchonFormSet(request.POST, request.FILES) + if formset.is_valid(): + formset.save() + if settings_form.is_valid(): + settings_form.save() + + formset = WusstestDuSchonFormSet() + settings_form = WusstestDuSchonSettingsForm() + + return render(request, "form.html", + {'title': 'Wusstest du Schon?', 'formset': formset, 'remove_url': 'wusstest_du_schon_remove', + 'form': settings_form}) + + +@login_required(login_url="/login") +def wusstest_du_schon_remove(request, id): + WusstestDuSchon.objects.filter(pk=id).delete() + + return redirect("/wusstest_du_schon") + + +def login(request): + client_id = os.getenv("CLIENT_ID") + redirect_uri = os.getenv("REDIRECT_URI") + url = f"https://id.twitch.tv/oauth2/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=moderation:read" + return redirect(url) + + +def logout(request): + django_logout(request) + return redirect("/") + + +def login_redirect(request): + code = request.GET.get('code') + user = exchange_code(code) + if user: + twitch_user = authenticate(request, user=user) + twitch_user = list(twitch_user).pop() + django_login(request, twitch_user) + + return redirect("/") + + +def exchange_code(code): + client_id = os.getenv("CLIENT_ID") + client_secret = os.getenv("CLIENT_SECRET") + redirect_uri = os.getenv("REDIRECT_URI") + url = f"https://id.twitch.tv/oauth2/token?client_id={client_id}&client_secret={client_secret}&code={code}&grant_type=authorization_code&redirect_uri={redirect_uri}" + response = requests.post(url) + if response.status_code == 200: + credentials = response.json() + + response = requests.get("https://api.twitch.tv/helix/users", headers={ + 'Authorization': f'Bearer {credentials["access_token"]}', + 'Client-Id': client_id + }) + + user = response.json()["data"][0] + + return {'id': user['id'], 'login': user['login'], 'access_token': credentials['access_token'], + 'refresh_token': credentials['refresh_token']} + + return None diff --git a/info.json b/info.json deleted file mode 100644 index 7cc9087cca5f3c7589c5a941fcc52a98f7a4d5c8..0000000000000000000000000000000000000000 --- a/info.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - "mit !subs kannst du dir anzeigen lassen, wie viele haugeSun -e Menschen diesen Kanal derzeit mit einem Sub unterstützen.", - "mit !time kannst du dir die aktuelle Uhrzeit bei Hauke in Tokyo anzeigen lassen.", - "mit !pipi kannst du Hauke einen Hinweis hinterlassen, dass du demnächst mal eine Pause bräuchtest, um zum Beispiel Pipi zu machen. Außerdem kannst du mit !warpipi mitteilen, dass sich dein Wunsch nach einer Pause inzwischen erledigt hat.", - "mit !wishlist erhälst du den Link zu Haukes Amazon Wishlist.", - "mit !zeit kannst du dir die aktuelle Uhrzeit bei Hauke in Tokyo anzeigen lassen.", - "mit !amazon erhälst du Haukes Amazon Affiliate Link.", - "mit !art erhälst du die Information, wer für das wunderbare Design dieses tollen Twitch-Kanals verantwortlich ist.", - "mit !artists erhälst du die Links zu allen Musik Artists, deren Musik hier während des Streams immer im Hintergrund läuft.", - "mit !campfire bekommst du weitere Informationen zur interaktiven Hörspielapp Campfire.", - "mit !discord erfährst du den Link zum Discord Server dieses Kanals.", - "mit !donate erhälst du weitere Informationen, wie du Hauke durch eine Spende unterstützen kannst.", - "mit !game bekommst du weitere Informationen zum Morriton Manor Spiel.", - "wenn du Hauke auf Instagram folgen möchtest, dann schreib einfach !instagram in den Chat und du bekommst den Link zu Haukes Profil.", - "wenn dir die Playlist, die gerade im Hintergrund läuft gefällt, dann schreib doch mal !playlist in den Chat und du erhälst den Link zur Spotify Playlist.", - "falls du nicht genug von Hauke und Japan bekommen kannst, einfach !podcast in den Chat und du bekommst den Link zu seinem FYEO Podcast.", - "als Besitzer von Amazon Prime kannst du jeden Monat einen Twitch Kanal deiner Wahl kostenlos abonnieren. Falls du wissen möchtest, wie das geht, !prime in den Chat und du bekommst alle notwendigen Informationen.", - "wenn du studierst und noch kein Amazon Prime hast, dann kannst du dies zu bevorzugten Konditionen abschließen. Falls dich das interessiert, gib einfach mal !primestudent in den Chat und du erhälst einen Link mit allen notwendigen Informationen.", - "als alte(r) Reisliebhaber(in) kannst du von Haukes Partnerschaft mit Reishunger profitieren. Alle Informationen hierzu: !reishunger", - "mit !song kannst du dir Informationen zum aktuell laufenden Song anzeigen lassen.", - "du kannst sogar subben, wenn du den Stream am Handy schaust. Gib mal !sub in den Chat ein, wenn dich interessiert, wie das geht.", - "falls du Hauke auch auf Twitter folgen möchtest, !twitter in den Chat, und du bekommst sofort einen Link dafür.", - "mit !wort kannst du dir das Wort des Tages anzeigen lassen.", - "Hauke hat nicht nur einen Twitch Kanal, auf dem er regelmäßig streamt. Regelmäßig erscheinen auf YouTube auch Videos mit Ausschnitten des Streams, oder extra dafür produzierten Videos. Mit !youtube erhälst du den Link zum YouTube Kanal." -] \ No newline at end of file diff --git a/info_cog.py b/info_cog.py deleted file mode 100644 index 51e6d892c63948f2a481e0a8eaf2a7ab4718bf3d..0000000000000000000000000000000000000000 --- a/info_cog.py +++ /dev/null @@ -1,46 +0,0 @@ -import asyncio -import json -import logging -import os -import random -from datetime import datetime - -from twitchio.ext import commands - - -@commands.core.cog(name="InfoCog") -class InfoCog: - def __init__(self, bot): - self.bot = bot - self.info_file = os.getenv("INFO_JSON") - self.INFO_LOOP = float(os.getenv("INFO_LOOP")) - self.INFO_COLOR = os.getenv("INFO_COLOR") - self.info = [] - self.load_info() - - def load_info(self): - """ Loads all info that should be sent to the chat regularly from INFO_JSON """ - - info_file = open(self.info_file, mode='r') - self.info = json.load(info_file) - - async def info_loop(self): - """ Send !pipimeter into the chat every x Minutes. Also check, whether the stream was offline for x Minutes. - If this is true, reset the pipi counter, as you can assume, that the stream recently started.""" - - logging.log(logging.INFO, f"Info loop started. {datetime.now()} {self}") - - while True: - logging.log(logging.INFO, f"Inside Info loop. Sleep for {self.INFO_LOOP} minutes. {datetime.now()} {self}") - await asyncio.sleep(self.INFO_LOOP * 60) - logging.log(logging.INFO, f"Inside Info loop finished sleeping now. {datetime.now()} {self}") - - if await self.bot.stream(): - logging.log(logging.INFO, - f"Inside Info loop. Stream is online, so send a message now!!! {datetime.now()} {self}") - channel = self.bot.channel() - message = f"Psssst... wusstest du eigentlich schon, {random.choice(self.info)}" - await self.bot.send_me(channel, message, self.INFO_COLOR) - - logging.log(logging.INFO, - f"Inside Info loop. Ooooookay, Loop ended, let's continue with the next round!!! {datetime.now()} {self}") diff --git a/manage.py b/manage.py new file mode 100644 index 0000000000000000000000000000000000000000..fb5aa8f8fe32ff5e1f636ee0f997ce8eda0dad56 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'haugebot.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt index 57f9216388c60b5b71c9c362dd61e2b592a09ed4..c794e3df838bdceeb8d6337d0587c70838f92e62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,19 @@ -aiohttp==3.6.2 +aiohttp==3.7.3 +asgiref==3.3.1 async-timeout==3.0.1 -attrs==19.3.0 +attrs==20.3.0 +certifi==2020.12.5 chardet==3.0.4 +Django==3.1.5 idna==2.10 -multidict==4.7.6 -python-dotenv==0.14.0 +multidict==5.1.0 +python-dotenv==0.15.0 +pytz==2020.5 +redis==3.5.3 +requests==2.25.1 +sqlparse==0.4.1 twitchio==1.1.0 +typing-extensions==3.7.4.3 +urllib3==1.26.2 websockets==8.1 -yarl==1.4.2 -redis==3.5.3 +yarl==1.6.3