first commit
This commit is contained in:
225
api/live.php
Normal file
225
api/live.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// api/live.php
|
||||
//
|
||||
// GET /api/live — public, returns current DB status
|
||||
// GET /api/live?refresh=1 — admin only, fetches from Twitch/Kick API
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
cors();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
json_error('Method not allowed', 405);
|
||||
}
|
||||
|
||||
// Public request — just return current status from DB
|
||||
if (!isset($_GET['refresh'])) {
|
||||
$rows = db()->query("
|
||||
SELECT id, name, platform, kick_name, status, game, title
|
||||
FROM streamers ORDER BY name
|
||||
")->fetchAll();
|
||||
json_out($rows);
|
||||
}
|
||||
|
||||
// Refresh request — admin only
|
||||
require_admin();
|
||||
|
||||
$cache_file = '/tmp/snb_live_cache.json';
|
||||
$now = time();
|
||||
|
||||
// Return cached result if still fresh
|
||||
if (file_exists($cache_file)) {
|
||||
$cache = json_decode(file_get_contents($cache_file), true);
|
||||
if ($cache && ($now - $cache['ts']) < LIVE_CACHE_TTL) {
|
||||
json_out(['cached' => true, 'age' => $now - $cache['ts'], 'results' => $cache['results']]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all streamers from DB
|
||||
$rows = db()->query("
|
||||
SELECT id, name, platform, kick_name FROM streamers ORDER BY name
|
||||
")->fetchAll();
|
||||
|
||||
$twitch_streamers = [];
|
||||
$kick_streamers = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if ($row['platform'] === 'kick' && !empty($row['kick_name'])) {
|
||||
$kick_streamers[] = $row;
|
||||
} else {
|
||||
$twitch_streamers[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Twitch — Helix API
|
||||
// ------------------------------------------------------------------
|
||||
if ($twitch_streamers) {
|
||||
$token = get_twitch_token();
|
||||
|
||||
if ($token) {
|
||||
foreach (array_chunk($twitch_streamers, 100) as $batch) {
|
||||
// Build URL with multiple login= params
|
||||
$params = array_map(
|
||||
fn($s) => 'user_login=' . urlencode(strtolower($s['name'])),
|
||||
$batch
|
||||
);
|
||||
$url = 'https://api.twitch.tv/helix/streams?' . implode('&', $params) . '&first=100';
|
||||
|
||||
$response = twitch_get($url, $token);
|
||||
|
||||
// Build lookup by login name
|
||||
$live = [];
|
||||
if ($response && isset($response['data'])) {
|
||||
foreach ($response['data'] as $stream) {
|
||||
$live[strtolower($stream['user_login'])] = $stream;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($batch as $s) {
|
||||
$login = strtolower($s['name']);
|
||||
$stream = $live[$login] ?? null;
|
||||
$results[$s['id']] = [
|
||||
'id' => $s['id'],
|
||||
'name' => $s['name'],
|
||||
'status' => $stream ? 'live' : 'offline',
|
||||
'game' => $stream['game_name'] ?? '',
|
||||
'title' => $stream['title'] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($twitch_streamers as $s) {
|
||||
$results[$s['id']] = [
|
||||
'id' => $s['id'], 'name' => $s['name'],
|
||||
'status' => 'unknown', 'game' => '', 'title' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Kick — public API
|
||||
// ------------------------------------------------------------------
|
||||
foreach ($kick_streamers as $s) {
|
||||
$kick_name = $s['kick_name'] ?: $s['name'];
|
||||
// Kick public channel API — no auth required
|
||||
$url = 'https://kick.com/api/v2/channels/' . urlencode(strtolower($kick_name));
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'Accept-Language: en-US,en;q=0.9',
|
||||
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
],
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$data = ($status === 200 && $body) ? json_decode($body, true) : null;
|
||||
// livestream key is present and non-null when live
|
||||
$is_live = !empty($data['livestream']);
|
||||
|
||||
$results[$s['id']] = [
|
||||
'id' => $s['id'],
|
||||
'name' => $s['name'],
|
||||
'status' => $is_live ? 'live' : 'offline',
|
||||
'game' => $data['livestream']['categories'][0]['name'] ?? '',
|
||||
'title' => $data['livestream']['session_title'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Write results back to DB
|
||||
// ------------------------------------------------------------------
|
||||
if ($results) {
|
||||
$db = db();
|
||||
$stmt = $db->prepare("
|
||||
UPDATE streamers
|
||||
SET status = :status, game = :game, title = :title
|
||||
WHERE id = :id
|
||||
");
|
||||
foreach ($results as $r) {
|
||||
if ($r['status'] === 'unknown') continue;
|
||||
$stmt->execute([
|
||||
':status' => $r['status'],
|
||||
':game' => $r['game'],
|
||||
':title' => $r['title'],
|
||||
':id' => $r['id'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache
|
||||
file_put_contents($cache_file, json_encode([
|
||||
'ts' => $now,
|
||||
'results' => array_values($results),
|
||||
]));
|
||||
|
||||
json_out(['cached' => false, 'results' => array_values($results)]);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
function get_twitch_token(): ?string {
|
||||
$cache_file = '/tmp/snb_twitch_token.json';
|
||||
|
||||
if (file_exists($cache_file)) {
|
||||
$cached = json_decode(file_get_contents($cache_file), true);
|
||||
if ($cached && $cached['expires_at'] > time() + 3600) {
|
||||
return $cached['access_token'];
|
||||
}
|
||||
}
|
||||
|
||||
$ch = curl_init('https://id.twitch.tv/oauth2/token');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query([
|
||||
'client_id' => TWITCH_CLIENT_ID,
|
||||
'client_secret' => TWITCH_CLIENT_SECRET,
|
||||
'grant_type' => 'client_credentials',
|
||||
]),
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($status !== 200) return null;
|
||||
|
||||
$data = json_decode($body, true);
|
||||
if (empty($data['access_token'])) return null;
|
||||
|
||||
file_put_contents($cache_file, json_encode([
|
||||
'access_token' => $data['access_token'],
|
||||
'expires_at' => time() + ($data['expires_in'] ?? 3600),
|
||||
]));
|
||||
|
||||
return $data['access_token'];
|
||||
}
|
||||
|
||||
function twitch_get(string $url, string $token): ?array {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Client-ID: ' . TWITCH_CLIENT_ID,
|
||||
'Authorization: Bearer ' . $token,
|
||||
],
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
return ($status === 200) ? json_decode($body, true) : null;
|
||||
}
|
||||
Reference in New Issue
Block a user