Huebi Charity Spendendashboard

Eine Geschichte von Hand-eingetragen zu automatisiert. Oder auch: Die Zerstörung von Tipeee :D

Huebi Charity Spendendashboard

Huebi Charity ist ein jährlicher 24-Stunden Spendenstream, bei welchem jegliche Einnahmen an wohltätige Zwecke gespendet werden. Huebi - der Veranstalter des Streams - ist ein bekannter deutscher YouTuber und Twitch-Streamer.

Huebi Charity 2021

Die Idee

Alle guten Dinge sind... Excel?!

Ungefähr zum Anfang des Huebi Charity 2021 Streams hatte ich die Idee, dass man doch ein Diagramm erstellen könnte, wie sich der Spendenbetrag über die 24 Stunden hinweg verändert. Dafür habe ich in Excel eine einfache Tabelle erstellt, bestehend aus den Stunden (0 bis 24) und den Streams (2017 bis 2021). Die alten Daten musste ich aus den Videos der alten Streams finden. Da habe ich ungefähr jede Stunde mal geschaut, auf welchem Stand die Spendensumme war. Gleichzeitig habe ich zur vollen Stunde immer im aktuellen Stream der Huebi Charity 2021 geschaut, wie viel es da ist, und habe das dann in Excel eingetragen.

Screenshot der Exceltabelle: Man sieht Huebi Charity 2017 bis 2020 komplett, und Huebi Charity 2021, wie es erst 3h lief.
Frühester Screenshot der Exceltabelle (Stand: 17:30 Uhr)

Die Excel Tabelle wurde noch ziemlich lange gepflegt. Dabei habe ich zwischenzeitlich immer mehr Leuten das Diagramm gezeigt. Unter anderem auch einem Mod von Huebi - Meister Keks - der nicht nur den Screenshot an Huebi weitergeleitet, sondern auch nachts die Daten für die Zeiten 4 bis 7 Uhr aufgeschrieben hat. Ja, ich habe die Nacht nicht durchgemacht. I'm soooo sorry.

Eine Webseite?

Gegen 11 Uhr des zweiten Tages hatte ich die Idee, dass ich doch daraus eine Webseite machen könnte. So könnte jeder sich das Diagramm anschauen und ich könnte sogar Huebi einfach im Stream den Link schicken, sodass er sich das anschaut. Außerdem hätte ich dann eine kleine Challenge zum Programmieren.

Die Entwicklung der Webseite

Die Webseite musste schnell entstehen, da es nur noch wenige Stunden waren, bis Huebi seinen Stream beendet. Daher habe ich einfach die Basis meiner privaten Webseite kopiert, um schnell ein laufendes PHP Projekt mit einer Verbindung zu einer MySQL Datenbank zu haben. Das Design ähnelte damals daher auch stark meiner Webseite. Aber das Diagramm war für mich neu. Damals hatte ich bislang nicht so viel ChartJS Kenntnisse, weshalb das Diagramm auch so simpel aussieht.

Ja, die Webseite war dreckig geschrieben. War es sonderlich effizient? Sicherlich nicht. Aber es funktionierte, und darauf kam es an. Hier mal ein Auszug aus dem Code:

<?php
$data = [];
$core = Core::getInstance();
$getData_statement = $core->pdo->query("SELECT * FROM huebicharitydashboard_data");
while ($year = $getData_statement->fetch()) {
    $data[] = [
        'year' => $year['year'],
        'hour_1' => $year['hour_1'],
        'hour_2' => $year['hour_2'],
        'hour_3' => $year['hour_3'],
        'hour_4' => $year['hour_4'],
        'hour_5' => $year['hour_5'],
        'hour_6' => $year['hour_6'],
        'hour_7' => $year['hour_7'],
        'hour_8' => $year['hour_8'],
        'hour_9' => $year['hour_9'],
        'hour_10' => $year['hour_10'],
        'hour_11' => $year['hour_11'],
        'hour_12' => $year['hour_12'],
        'hour_13' => $year['hour_13'],
        'hour_14' => $year['hour_14'],
        'hour_15' => $year['hour_15'],
        'hour_16' => $year['hour_16'],
        'hour_17' => $year['hour_17'],
        'hour_18' => $year['hour_18'],
        'hour_19' => $year['hour_19'],
        'hour_20' => $year['hour_20'],
        'hour_21' => $year['hour_21'],
        'hour_22' => $year['hour_22'],
        'hour_23' => $year['hour_23'],
        'hour_24' => $year['hour_24'],
        "color" => $year['color']
    ];
}
?>

Auszug aus dem Code, der die Datenbank abfragt

WTF, was habe ich da gemacht?!

Die Datenbank

Die Datenbank bestand aus einer einzigen Tabelle. Diese Tabelle hatte eine ID, das Jahr (z.B. 2021), eine Farbe (für das Diagramm) und jeweils eine Spalte pro Stunde. Ziemlich rudimentär, da ich einfach keine Zeit für irgendwas Kompliziertes mit Referenzen hatte. Ich wollte einfach nur eine MySQL Abfrage haben, die beim Aufruf der Webseite alle Streams mit den jeweiligen Summen anzeigt.

Die Datenbank musste allerdings auch irgendwie gefüllt werden. Da ich die Daten nur als Excel Tabelle hatte, habe ich mit einem guten Freund - Herr Knuddel - SQL Statements geschrieben, die die Daten in die Tabelle einfügen. Die meisten dieser Statements hat er geschrieben, da ich währenddessen noch an der Webseite gearbeitet habe.

Fertig!

Gegen 12:30 Uhr war die Webseite dann fertig und online verfügbar. Wirklich sehr kurz vor knapp - Huebi würde nur noch ungefähr 1h 30min streamen. Daher: Unbedingt jetzt die Spendennachricht senden! Und das tat ich auch. Er hat die Spendennachricht gesehen und hat sich dann kurz danach die Webseite auch angeschaut. Hier ein Clip davon:

0:00
/0:19

Reaktion von Huebi auf das Huebi Charity Spendendashboard

Hier sieht man noch mal den Stand der Webseite, wie sie direkt nach der Huebi Charity 2021 aussah:

Aber das war's mit der Webseite für Huebi Charity. Sie ist fertig. Ich würde doch niemals noch weiter dran arbei-

Huebi Charity 7

Der Beginn der Automatisierung

Eine eventuelle Kooperation?

Wir schreiben den 27. August 2022. Ich bin auf der Gamescom mit ein paar Freunden und bin kurz davor, mit Huebi ein Foto zu machen. Außerdem wollte ich ihn dabei fragen, ob es ggf. möglich wäre, das Huebi Charity Spendendashboard irgendwie offiziell zu machen. Also mit offizieller Unterstützung von Huebi. Und... er meinte, dass man das machen könnte! 🥳 Ich sollte ihm auf Discord schreiben - was ich natürlich auch am selben Abend noch getan habe. Leider hatte er gar nicht geantwortet. Gut, kann passieren. Er ist eben ein viel beschäftigter Mensch.

Neujahr kam und es gab immer noch keine Antwort von Huebi. Allerdings stand Huebi Charity 7 bald an... Also dachte ich mir, dass ich ihm noch mal eine Nachricht schreibe. Und er hat direkt am nächsten Tag geantwortet! Er hat vorgeschlagen, dass er mir ja einen Tipeee API Token geben kann und ich mir dann die API von Tipeee selbst anschauen müsste, um an die Daten zu kommen. Okay.

TipeeeStream's API

Ein Glück hat TipeeeStream eine öffentliche API, inklusive einer Dokumentation dieser. So konnte ich sehen, dass ich (Spenden-) Events in einem bestimmten Zeitraum abfragen kann. Das hilft mir hier schon, da ich dann einfach alle Spenden im Zeitraum des Streams abfragen, und dann eben alle Spenden zusammenrechnen könnte.

{
  "id": 123456789,
  "type": "donation",
  "user": { ... },
  "created_at": "2023-01-01T01:01:00+0100",
  "inserted_at": "2023-01-01T01:01:00+0100",
  "display": true,
  "parameters": {
    "amount": 10,
    "currency": "EUR",
    "formattedMessage": "...",
    "message": "...",
    "username": "SteffoSpieler"
  },
  "formattedAmount": "€10.00",
  "parameters.amount": 10
}

Beispiel eines Spendenevents von TipeeeStream

Also habe ich eine einfache TipeeeAPI Klasse geschrieben, in der ich einen Zeitraum angeben kann, und diese dann an TipeeeStream eine Abfrage schickt.

class TipeeeAPI {
    static function requestDataBetween($start, $end) {
        return json_decode(self::callAPI([
            'type[]' => 'donation',
            'limit' => '0',
            'apiKey' => Config::read('tipeee.token'),
            'start' => $start,
            'end' => $end
        ]), true);
    }

    static function getSumOfDonations($start, $end): float {
        $datas = self::requestDataBetween($start, $end)['datas']['items'];
        $money = 0;

        foreach ($datas as $item) {
            if (is_numeric($item['parameters']['amount'])) {
                $money = $money + $item['parameters']['amount'];
            }
        }

        return $money;
    }
}

Auszug der TipeeeAPI Klasse

Das hat in meinen Tests auch super funktioniert! Also, ran an die automatisierte Task!

Die Entwicklung der Task

Plesk, das System, das netcup im Webhosting verwendet, unterstützt die Möglichkeit, "geplante Aufgaben" (... aka. Cron Tasks) zu erstellen. Da kann man einfach gesagt eine (PHP-) Datei angeben, und die wird dann alle X Zeit gestartet. Man kann da eine Syntax wie bei Cron Tasks nutzen.

Simplifiziert muss ich ja nur schauen, wie viele Stunden seit dem Streamstart vergangen sind, und dann die Summe aller Spenden-Events in die Datenbank schreiben. Also habe ich das getan.

function getTime(DateTime $start, DateTime $end): float|int {
    $interval = $start->diff($end);
    $hours = (int)$interval->format('%h') + ($interval->days * 24);
    $minutes = (int)$interval->format('%i');
    $decimal_minutes = $minutes / 60;
    return $hours + (floor($decimal_minutes * 2) / 2);
}

$start = (new DateTime)::createFromFormat('Y-m-d H:i', '2023-01-07 13:00');
$end = new DateTime();

if ($start > $end) {
    echo "start is in future\n";
    return;
}

$time = getTime($start, $end);

if ($time > 24) {
    echo "huebi charity is done\n";
    return;
}

$value = TipeeeAPI::getSumOfDonations("2023-01-07T13:00:00+0100", "2023-01-08T13:00:00+0100");

$stream = StreamManager::getStream("6");
$stream->addSnapshot($time, $value);

echo "Added Snapshot for time " . $time . " with value " . $value . "\n";

Die createSnapshot Task

Ja, das ist hart gecodet. Ich weiß auch nicht, warum. Aber hey, es ist ein Anfang. Und somit bin ich ungefähr um Mitternacht desselben Tages fertig geworden und... der Charity Stream kann kommen!

💡
Kurze Transparenz: Ich hatte bis zum Stream noch einiges am Code überarbeitet und der Code war zum Start des Streams deutlich sauberer. Das Prinzip blieb aber gleich, daher hier zur Veranschaulichung.

Huebi Charity 7 startet

Der Huebi Charity 7 Stream startet und alles scheint zu funktionieren. Nach den ersten 15 Minuten wird der aktuelle Stand auf der Webseite angezeigt. Super! Funktioniert!

Der Stream lief ohne Probleme, die Webseite hatte keine Performanceprobleme und es gab keine Probleme. Ich hatte nichts mehr zu tu-

ES GIBT JA AUCH ANDERE WÄHRUNGEN! 🤦

Servus. Ich bin's, der Steffo, und ich habe natürlich nicht vergessen, dass es auch was anderes als nur Euro gibt. Ups.

Das ist mir zum Glück ziemlich schnell eingefallen, noch bevor es irgendwelche Spenden in anderen Währungen gab. Also, hoffentlich. Ich habe so schnell, wie ich nur konnte, irgendeine exchange-rates Webseite gefunden, mir die ganzen Raten von Währung → Euro exportiert, und dann im Code noch schnell die Umrechnung hinzugefügt. Nein, ich habe davon keine Ahnung. Aber wird sicherlich schon stimmen oder so.

Ein Glück hatte ich netcup bzw. Plesk so eingestellt, dass, wenn ich etwas aufs Repository pushe, es dann sofort online geht. So konnte ich generell immer on-the-fly noch schnell Dinge fixen und abändern.

TipeeeStream's kleines... "Problemchen"

Ich habe natürlich am Anfang des Streams genauer auf die Cron Tasks geschaut, dass sie richtig laufen und alles im grünen Bereich ist. Da ist mir aufgefallen, dass die Zeit, wie lange es gedauert hat, bis so eine Task fertig war, immer länger wurde. Allein die erste Anfrage, die nur 15 Minuten als Zeitintervall hatte, dauerte schon 15 Sekunden - und hatte "nur" 90 Einträge.

Da mir das aber erst nach ein paar Stunden aufgefallen ist, waren die Zeiten schon deutlich länger. Aber wer hätte schon ahnen können, dass so viele Spenden die API etwas verlangsamen können? Na ja, zum Glück war es ja nur die API... richtig?

0:00
/0:09

Huebi, wie er On-Stream versucht, die Alerts wieder zum Laufen zu bringen

Fuck.

💡
Natürlich ist das hier etwas überspitzt dargestellt - und es ist möglich, dass mein Code und der Ausfall von TipeeeStream nichts miteinander zu tun haben. Ich habe nachgeschaut und gesehen, dass ich meinen Fix erst eine Stunde, nachdem es schon im Stream behoben war, gepusht habe. 🤔

Zu diesem Zeitpunkt musste ein Fix kommen - und zwar schnell! Also kam ich auf folgende Idee: Ich lade mir nur die Spendendaten zwischen dem letzten Snapshot und dem neuen Snapshot von TipeeeStream's API, und addiere diese Summe zum vorherigen Wert. Somit lade ich von TipeeeStream's API Daten in einem Zeitraum von in der Regel 15 Minuten - das dauert "nur" ca. 15 Sekunden und sollte daher leichter für TipeeeStream sein. Außerdem habe ich zu Debugzwecken mehr Logging hinzugefügt, sodass ich genau sehen kann, was die Aufgabe macht.

Nach dem Fix hat sich TipeeeStream auch wieder beruhigen können - alles hat wie normal geklappt. Die Datei der Aufgabe, im Stand des Fixes, findet man hier: createSnapshot.php

Ende gut, alles gut

Im restlichen Lauf des Streams habe ich natürlich weiterhin sichergestellt, dass die Aufgabe alles richtig macht, und ich kann sagen: Es hat alles geklappt. Selbst, als ich geschlafen hatte, hatte alles ganz normal funktioniert und die Spendensumme auf der Webseite hatte, als ich aufgewacht bin, mit dem im Stream übereingestimmt.

Natürlich habe ich aber auch etwas weiter dran entwickelt, und unter anderem ein neues Diagramm hinzugefügt: "Spenden pro 30 Minuten". Das zeigt an, wie viel Geld alle 30 Minuten gespendet wurde.
Dazu habe ich aber auch noch ein Panel hinzugefügt, das die fehlende Summe für einen Monopoly-Stream angezeigt hat. Hierzu die Information: Huebi hatte damals gesagt, dass er einen allerletzten Monopoly-Stream machen wird, wenn bei Huebi Charity die 100k € erreicht sind. Als, ich glaube, nur noch 20k € gefehlt hatten, habe ich den Counter hinzugefügt. Überraschung: Wir haben die 100k € erreicht! 🎉

Hier jetzt der Stand des Spendendashboards, wie es direkt nach Huebi Charity 7 aussah:

Ein Screenshot des Huebi Charity Spendendashboards, wie sie zum Stand nach dem Huebi Charity 7 Stream aussah.

Huebi Charity 8

Code fixes, Optimierungen und Umzug - und jetzt Open Source!

Die Vorbereitung zum Stream

Zu Huebi Charity 8 hat sich nicht mehr viel getan. Die Seite war dieses Mal wirklich fertig, das Snapshot-System funktioniert und die Seite läuft - bis auf Kleinigkeiten - autonom.

Also, was musste jetzt noch gemacht werden? Zuerst die von mir genannten "Kleinigkeiten": Das Promo-Video mit dem für Huebi Charity 8 austauschen - und natürlich den neuen Stream mit den Zeiten in die Datenbank eintragen.

Spezielle Verhalten der createSnapshot Aufgabe

Ich habe 2 spezielle Verhalten zur createSnapshot Aufgabe hinzugefügt.

  1. Beim ersten Snapshot (also zum Zeitpunkt 0 - Streamstart) ist der Wert immer 0. So sieht's im Diagramm etwas sauberer aus.
  2. Der darauffolgende Snapshot (in der Regel 15 Minuten nach Streamstart) fragt bei TipeeeStream ab eine Stunde vor Streamstart nach Spenden. Falls jemand beim Counter am Anfang spendet, wird das somit trotzdem mitgezählt.

Andere Domain

Ich habe die Domain, auf der das Projekt läuft, geändert. Anfangs lief das Dashboard auf https://huebi-charity.steffospieler.de, da ich damals nur diese besaß. Jetzt läuft es auf https://huebi-charity.steffo.dev, da ich meine Projekte auf steffo.dev migrieren wollte, da steffospieler.de eher meine Domain für mein YouTube und Twitch Zeugs sein sollte.

Open Source

Das Dashboard zum Huebi Charity 8 Stream ist jetzt Open Source geworden! Das heißt jeder kann jetzt sehen, was ich da für eine Scheiße gebaut habe. 😅

huebi-charity-dashboard
Ein Dashboard für die Spendenbeträge von Huebi Charity in einer viertelstündlichen Übersicht.

Der Stream

Während des Streams hat alles - wie erwartet - geklappt. Ich habe natürlich zwischendrin ein paar passende Updates gepusht, wie zum Beispiel einen Commit, der kurzzeitig den "100k € Counter" wieder angezeigt hat. Ansonsten lief alles, wie es sollte.


Und jetzt?

Jetzt warte ich auf den nächsten Huebi Charity Stream, sodass ich diesen dann zur Webseite hinzufügen kann und das System dann sein Ding machen lasse.

Eine neue Archiv-Ansicht

In der Zwischenzeit habe ich noch eine Archiv-Funktion hinzugefügt. Man kann jetzt auswählen, welchen Stream man im Fokus sehen will. Die Webseite war zum Glück schon so entwickelt, dass ich da nicht viel verändern musste, damit das funktioniert. Allerdings bin ich bislang nicht so 100 % zufrieden, wie das Dropdown mit den Streams aussieht. Wenn da jemand einen Vorschlag hat, gerne her damit! (Ich nehme auch gerne funktionierendes HTML & CSS. 😉)

Außerdem sind noch ein paar Features geplant, wie man in der Issue-Übersicht sehen kann.

Und um ein allerletztes Mal den Stand der Webseite zu zeigen... So sieht sie zum Zeitpunkt des Blog-Posts aus:

Ein Screenshot des Huebi Charity Spendendashboards, wie sie zum Stand des Blog-Posts aussieht.