Files
obs-config/webapp/app/Services/TwitchHelix.php
2026-05-21 15:00:12 +02:00

94 lines
3.1 KiB
PHP

<?php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use RuntimeException;
/**
* Thin Twitch Helix client. Replaces twitch-bot/search-game.py + set-channel.py
* for use from `php artisan rig:loading`.
*
* Requires TWITCH_CLIENT_ID + TWITCH_ACCESS_TOKEN (user OAuth token with the
* channel:manage:broadcast scope) and TWITCH_BROADCASTER_ID in .env.
* Legacy TWITCH_TOKEN is still accepted as a fallback (see config/rig.php).
*/
class TwitchHelix
{
private Client $http;
private string $clientId;
private string $token;
private string $broadcasterId;
public function __construct()
{
$this->clientId = (string) config('rig.twitch.client_id');
$this->token = (string) config('rig.twitch.token');
$this->broadcasterId = (string) config('rig.twitch.broadcaster_id');
$this->http = new Client([
'base_uri' => 'https://api.twitch.tv/helix/',
'timeout' => 8.0,
]);
}
public function enabled(): bool
{
return $this->clientId !== '' && $this->token !== '';
}
/**
* Returns [['id' => '12345', 'name' => 'Half-Life 2'], …] for an exact-name match,
* or null if Twitch returned no matches.
*/
public function searchCategory(string $query): ?array
{
try {
$res = $this->http->get('search/categories', [
'headers' => $this->headers(),
'query' => ['query' => $query, 'first' => 20],
]);
} catch (GuzzleException $e) {
throw new RuntimeException('Twitch search failed: ' . $e->getMessage(), 0, $e);
}
$body = json_decode((string) $res->getBody(), true);
$hits = $body['data'] ?? [];
if (empty($hits)) return null;
// Prefer exact (case-insensitive) match; otherwise the first hit Twitch returned.
foreach ($hits as $row) {
if (strcasecmp((string) ($row['name'] ?? ''), $query) === 0) {
return ['id' => (string) $row['id'], 'name' => (string) $row['name']];
}
}
return ['id' => (string) $hits[0]['id'], 'name' => (string) $hits[0]['name']];
}
public function setChannel(string $title, ?string $gameId = null): void
{
if ($this->broadcasterId === '') {
throw new RuntimeException('TWITCH_BROADCASTER_ID not set in .env');
}
$body = ['title' => $title];
if ($gameId !== null && $gameId !== '') $body['game_id'] = $gameId;
try {
$this->http->patch('channels', [
'headers' => $this->headers() + ['Content-Type' => 'application/json'],
'query' => ['broadcaster_id' => $this->broadcasterId],
'json' => $body,
]);
} catch (GuzzleException $e) {
throw new RuntimeException('Twitch PATCH /helix/channels failed: ' . $e->getMessage(), 0, $e);
}
}
private function headers(): array
{
return [
'Authorization' => 'Bearer ' . $this->token,
'Client-Id' => $this->clientId,
];
}
}