Zum Inhalt springen
  • Dunkel
  • Hell
  • System

Twitch Helix Custom-Calls aus C#

Streamer.bot deckt einen großen Teil der Twitch-API mit nativen Actions ab. Für alles andere, etwa Schedule, VODs, Channel-Emotes oder Team-Endpoints, gibt es keine fertige Aktion. Dann sendest du den Helix-Request selbst per HttpClient. Das Gute: Du musst dich nicht um OAuth kümmern, denn Streamer.bot stellt dir die verbundenen Credentials als Properties am CPH-Objekt bereit.

Diese Seite baut auf dem HttpClient-Muster aus Externe APIs mit HttpClient auf. Wenn du den synchronen GetAwaiter().GetResult()-Trick und den statischen Client noch nicht kennst, lies das zuerst.

Jeder Helix-Request braucht zwei Header: Client-Id und Authorization mit einem Bearer-Token. Beides liefert Streamer.bot direkt, du musst nichts selbst anlegen:

  • CPH.TwitchClientId gibt die Client-Id der verbundenen Twitch-Anwendung zurück.
  • CPH.TwitchOAuthToken gibt das OAuth-Token des verbundenen Twitch-Accounts zurück.

Wichtig und ehrlich: Welcher Account dahinter steckt und ob es das Broadcaster- oder Bot-Token ist, hängt davon ab, wie du Twitch in Streamer.bot verbunden hast. Streamer.bot kennt zwei getrennte Verbindungen, Broadcaster und Bot, mit jeweils eigenen Scopes. CPH.TwitchOAuthToken liefert das Token der primären Verbindung. Für Endpoints, die ein bestimmtes Token oder einen bestimmten Scope erfordern, musst du sicherstellen, dass dieser Account die nötigen Berechtigungen hat. Welche Property genau welche Verbindung adressiert, ist versionsabhängig. Prüfe das im Zweifel gegen die offizielle Doku, statt es zu raten.

Doku: docs.streamer.bot · Twitch-Methoden

Helix-Endpoints verlangen oft einen bestimmten OAuth-Scope. Fehlt der Scope am Token, antwortet Twitch mit 401 oder 403, nicht mit einem leeren Ergebnis. Die nötigen Scopes stehen in der Twitch-Doku pro Endpoint. In Streamer.bot werden Scopes beim Verbinden des Accounts vergeben. Wie das mit EventSub zusammenhängt und wie du Scopes nachziehst, steht unter Twitch EventSub.

Das folgende Beispiel ruft den eigenen Stream-Status ab (/helix/streams) und schreibt den aktuellen Titel in den Chat. Es nutzt den statischen HttpClient und holt Header frisch pro Request, weil sich das Token zwischen Restarts ändern kann:

using System;
using System.Net.Http;
using Newtonsoft.Json.Linq;
public class CPHInline {
// Statisch, damit der Client wiederverwendet wird (kein Socket-Leak).
private static readonly HttpClient client = new HttpClient();
public bool Execute() {
string token = CPH.TwitchOAuthToken;
string clientId = CPH.TwitchClientId;
if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(clientId)) {
CPH.LogError("[Helix] Token oder Client-Id leer. Twitch verbunden?");
return false;
}
// Eigenen User-Login als Query-Parameter. Hier hardcoded zur Demo,
// in echt aus einem Argument oder Global lesen.
string login = "azacraft";
string url = "https://api.twitch.tv/helix/streams?user_login=" + login;
using (var request = new HttpRequestMessage(HttpMethod.Get, url)) {
request.Headers.Add("Client-Id", clientId);
request.Headers.Add("Authorization", "Bearer " + token);
HttpResponseMessage response =
client.SendAsync(request).GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode) {
// 401/403 deutet fast immer auf fehlenden Scope oder ungueltiges Token.
CPH.LogError("[Helix] HTTP " + (int)response.StatusCode);
return false;
}
string body = response.Content.ReadAsStringAsync()
.GetAwaiter().GetResult();
JObject json = JObject.Parse(body);
JArray data = (JArray)json["data"];
if (data == null || data.Count == 0) {
CPH.SendMessage("Stream ist gerade offline.");
return true;
}
// Ein einzelnes Feld aus dem ersten Eintrag herausziehen und posten.
string title = data[0]["title"].ToString();
CPH.SendMessage("Aktueller Titel: " + title);
}
return true;
}
}

Der Ablauf in Stichworten:

  1. Header pro Request setzen. Statt client.DefaultRequestHeaders global zu verändern, hängt das Beispiel die Header an die HttpRequestMessage. So bleibt der geteilte statische Client sauber.
  2. Synchron brücken. Execute() ist bool, nicht async. Deshalb SendAsync(...).GetAwaiter().GetResult() statt await.
  3. Antwort parsen. Helix liefert die Nutzdaten unter dem Schlüssel data als Array. JObject.Parse plus json["data"] reicht für einfache Fälle. Tippfeste Modelle gehen mit JsonConvert.DeserializeObject<T>, Details unter JSON mit Newtonsoft.

Die offizielle Doku zeigt eine sehr ähnliche Variante mit DefaultRequestHeaders.Clear() und GetAsync: docs.streamer.bot · Advanced Fetch URL.

Listen-Endpoints geben nur eine Seite zurück (meist 20 bis 100 Einträge). Die nächste Seite holst du über den Cursor aus pagination.cursor. Ist data voll und ein Cursor vorhanden, hängst du &after=<cursor> an und rufst erneut auf, bis kein Cursor mehr kommt:

string cursor = null;
do {
string pageUrl = "https://api.twitch.tv/helix/videos?user_id=123456"
+ (cursor != null ? "&after=" + cursor : "");
using (var request = new HttpRequestMessage(HttpMethod.Get, pageUrl)) {
request.Headers.Add("Client-Id", clientId);
request.Headers.Add("Authorization", "Bearer " + token);
var resp = client.SendAsync(request).GetAwaiter().GetResult();
string body = resp.Content.ReadAsStringAsync()
.GetAwaiter().GetResult();
JObject json = JObject.Parse(body);
foreach (var item in (JArray)json["data"]) {
CPH.LogInfo("[Helix] " + item["title"]);
}
// Cursor fuer die naechste Runde, oder null wenn fertig.
cursor = json["pagination"]?["cursor"]?.ToString();
}
} while (!string.IsNullOrEmpty(cursor));

Setze ein sinnvolles first=-Limit pro Seite und brich nach einer Maximalzahl an Runden ab, damit eine Schleife bei sehr großen Datenmengen nicht den Worker blockiert.

  • Token-Scope fehlt (401/403). Der häufigste Fehler. Der Endpoint braucht einen Scope, den der verbundene Account nicht hat. Prüfe die nötigen Scopes in der Twitch-Doku und ziehe sie über die Account-Verbindung in Streamer.bot nach. Siehe EventSub.
  • Client-Id-Header vergessen. Ohne Client-Id lehnt Twitch den Request ab, selbst mit gültigem Token. Beide Header sind Pflicht.
  • Token im Code statt aus dem CPH-Objekt. Hardcode niemals ein Token in die Action. Es läuft ab und ist im Klartext im Export sichtbar. Hole es immer über CPH.TwitchOAuthToken zur Laufzeit.
  • Pagination ignoriert. Wer nur data der ersten Seite liest, verpasst bei Listen-Endpoints alles dahinter. Den Cursor immer abarbeiten, wenn vollständige Daten gebraucht werden.
  • Rate-Limit. Helix begrenzt Requests pro Minute (Header Ratelimit-Remaining). In Schleifen oder bei vielen parallelen Actions zu schnell zu feuern führt zu 429. Wenig oft abrufen, Ergebnisse per Global cachen, und bei 429 kurz warten statt sofort erneut zu senden.
  • Versionsabhängiger Token-Weg. CPH.TwitchOAuthToken und CPH.TwitchClientId adressieren die verbundene Twitch-Anwendung, aber welche Verbindung (Broadcaster oder Bot) genau, kann sich zwischen Versionen ändern. Verlass dich nicht auf Annahmen, sondern auf die offizielle Doku.