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.
Client-Id und Token aus dem CPH-Objekt
Abschnitt betitelt „Client-Id und Token aus dem CPH-Objekt“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.TwitchClientIdgibt die Client-Id der verbundenen Twitch-Anwendung zurück.CPH.TwitchOAuthTokengibt 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.
Beispiel: GET-Request und ein Feld posten
Abschnitt betitelt „Beispiel: GET-Request und ein Feld posten“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:
- Header pro Request setzen. Statt
client.DefaultRequestHeadersglobal zu verändern, hängt das Beispiel die Header an dieHttpRequestMessage. So bleibt der geteilte statische Client sauber. - Synchron brücken.
Execute()istbool, nichtasync. DeshalbSendAsync(...).GetAwaiter().GetResult()stattawait. - Antwort parsen. Helix liefert die Nutzdaten unter dem Schlüssel
dataals Array.JObject.Parseplusjson["data"]reicht für einfache Fälle. Tippfeste Modelle gehen mitJsonConvert.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.
Pagination per Cursor
Abschnitt betitelt „Pagination per Cursor“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.
Häufige Fallen
Abschnitt betitelt „Häufige Fallen“- 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-Idlehnt 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.TwitchOAuthTokenzur Laufzeit. - Pagination ignoriert. Wer nur
datader 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 zu429. Wenig oft abrufen, Ergebnisse per Global cachen, und bei429kurz warten statt sofort erneut zu senden. - Versionsabhängiger Token-Weg.
CPH.TwitchOAuthTokenundCPH.TwitchClientIdadressieren 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.