|
4 | 4 |
|
5 | 5 | use Illuminate\Http\Request;
|
6 | 6 | use Illuminate\Support\Facades\Http;
|
| 7 | +use Illuminate\Support\Facades\Cache; |
7 | 8 | use Illuminate\Http\JsonResponse;
|
8 | 9 |
|
9 | 10 | class Controller extends \App\Http\Controllers\Controller
|
10 | 11 | {
|
| 12 | + private const TWITCH_GQL_URL = 'https://gql.twitch.tv/gql'; |
| 13 | + private const TWITCH_CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; |
| 14 | + |
11 | 15 | public function __invoke(Request $request, string $username): JsonResponse
|
12 | 16 | {
|
13 | 17 | try {
|
14 |
| - $tokenResponse = Http::asForm()->post('https://id.twitch.tv/oauth2/token', [ |
15 |
| - 'client_id' => config('services.twitch.client_id'), |
16 |
| - 'client_secret' => config('services.twitch.client_secret'), |
17 |
| - 'grant_type' => 'client_credentials', |
| 18 | + // Clean the username - remove trailing slashes and spaces |
| 19 | + $username = trim($username, "/ \t\n\r\0\x0B"); |
| 20 | + |
| 21 | + // Query for full user data following the documented structure |
| 22 | + $query = <<<GQL |
| 23 | + query { |
| 24 | + user(login: "{$username}") { |
| 25 | + id |
| 26 | + login |
| 27 | + displayName |
| 28 | + description |
| 29 | + createdAt |
| 30 | + roles { |
| 31 | + isPartner |
| 32 | + isAffiliate |
| 33 | + } |
| 34 | + profileImageURL(width: 300) |
| 35 | + offlineImageURL |
| 36 | + lastBroadcast { |
| 37 | + title |
| 38 | + game { |
| 39 | + name |
| 40 | + } |
| 41 | + } |
| 42 | + stream { |
| 43 | + id |
| 44 | + title |
| 45 | + type |
| 46 | + viewersCount |
| 47 | + createdAt |
| 48 | + game { |
| 49 | + name |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | + } |
| 54 | + GQL; |
| 55 | + |
| 56 | + // Make GraphQL request |
| 57 | + $response = Http::withHeaders([ |
| 58 | + 'Client-ID' => self::TWITCH_CLIENT_ID, |
| 59 | + 'Content-Type' => 'application/json', |
| 60 | + ])->post(self::TWITCH_GQL_URL, [ |
| 61 | + 'query' => $query, |
| 62 | + 'variables' => (object)[] |
18 | 63 | ]);
|
19 | 64 |
|
20 |
| - if (!$tokenResponse->successful()) { |
| 65 | + if (!$response->successful()) { |
21 | 66 | return response()->json([
|
22 |
| - 'error' => 'Failed to authenticate with Twitch', |
23 |
| - 'details' => $tokenResponse->json(), |
| 67 | + 'error' => 'Failed to fetch Twitch data', |
| 68 | + 'details' => $response->json() |
24 | 69 | ], 500);
|
25 | 70 | }
|
26 | 71 |
|
27 |
| - $accessToken = $tokenResponse->json()['access_token']; |
28 |
| - $headers = [ |
29 |
| - 'Client-ID' => config('services.twitch.client_id'), |
30 |
| - 'Authorization' => 'Bearer ' . $accessToken, |
31 |
| - ]; |
32 |
| - |
33 |
| - // Get user data first |
34 |
| - $userResponse = Http::withHeaders($headers) |
35 |
| - ->get("https://api.twitch.tv/helix/users", [ |
36 |
| - 'login' => $username, |
37 |
| - ]); |
| 72 | + $data = $response->json(); |
38 | 73 |
|
39 |
| - if (!$userResponse->successful()) { |
| 74 | + // Validate response structure |
| 75 | + if (!isset($data['data'])) { |
40 | 76 | return response()->json([
|
41 |
| - 'error' => 'Failed to fetch user data', |
42 |
| - 'details' => $userResponse->json(), |
| 77 | + 'error' => 'Invalid response format', |
| 78 | + 'details' => $data |
43 | 79 | ], 500);
|
44 | 80 | }
|
45 | 81 |
|
46 |
| - $userData = $userResponse->json(); |
47 |
| - |
48 |
| - if (empty($userData['data'])) { |
| 82 | + // Check if user exists |
| 83 | + if (empty($data['data']['user'])) { |
49 | 84 | return response()->json([
|
50 |
| - 'error' => 'User not found', |
| 85 | + 'error' => 'User not found' |
51 | 86 | ], 404);
|
52 | 87 | }
|
53 | 88 |
|
54 |
| - $user = $userData['data'][0]; |
55 |
| - |
56 |
| - // Get stream data |
57 |
| - $streamResponse = Http::withHeaders($headers) |
58 |
| - ->get("https://api.twitch.tv/helix/streams", [ |
59 |
| - 'user_id' => $user['id'], |
60 |
| - ]); |
| 89 | + $user = $data['data']['user']; |
61 | 90 |
|
62 |
| - if (!$streamResponse->successful()) { |
| 91 | + // Fall back to basic stream status if we hit any issues |
| 92 | + try { |
63 | 93 | return response()->json([
|
64 |
| - 'error' => 'Failed to fetch stream data', |
65 |
| - 'details' => $streamResponse->json(), |
66 |
| - ], 500); |
67 |
| - } |
68 |
| - |
69 |
| - $streamData = $streamResponse->json(); |
70 |
| - |
71 |
| - // Get channel information |
72 |
| - $channelResponse = Http::withHeaders($headers) |
73 |
| - ->get("https://api.twitch.tv/helix/channels", [ |
74 |
| - 'broadcaster_id' => $user['id'], |
| 94 | + 'user' => [ |
| 95 | + 'id' => $user['id'], |
| 96 | + 'login' => $user['login'], |
| 97 | + 'display_name' => $user['displayName'], |
| 98 | + 'type' => $user['roles']['isPartner'] ? 'partner' : |
| 99 | + ($user['roles']['isAffiliate'] ? 'affiliate' : ''), |
| 100 | + 'description' => $user['description'], |
| 101 | + 'profile_image_url' => $user['profileImageURL'], |
| 102 | + 'offline_image_url' => $user['offlineImageURL'], |
| 103 | + 'created_at' => $user['createdAt'] |
| 104 | + ], |
| 105 | + 'channel' => [ |
| 106 | + 'broadcaster_language' => 'en', |
| 107 | + 'game_name' => $user['lastBroadcast']['game']['name'] ?? null, |
| 108 | + 'title' => $user['lastBroadcast']['title'] ?? null |
| 109 | + ], |
| 110 | + 'stream' => [ |
| 111 | + 'is_live' => !empty($user['stream']), |
| 112 | + 'data' => $user['stream'] ? [ |
| 113 | + 'id' => $user['stream']['id'], |
| 114 | + 'game_name' => $user['stream']['game']['name'], |
| 115 | + 'title' => $user['stream']['title'], |
| 116 | + 'viewer_count' => $user['stream']['viewersCount'], |
| 117 | + 'started_at' => $user['stream']['createdAt'], |
| 118 | + 'thumbnail_url' => sprintf( |
| 119 | + 'https://static-cdn.jtvnw.net/previews-ttv/live_user_%s-{width}x{height}.jpg', |
| 120 | + strtolower($user['login']) |
| 121 | + ) |
| 122 | + ] : null |
| 123 | + ] |
75 | 124 | ]);
|
76 |
| - |
77 |
| - if (!$channelResponse->successful()) { |
| 125 | + } catch (\Exception $e) { |
| 126 | + // If we hit any issues parsing the full response, just return stream status |
78 | 127 | return response()->json([
|
79 |
| - 'error' => 'Failed to fetch channel data', |
80 |
| - 'details' => $channelResponse->json(), |
81 |
| - ], 500); |
| 128 | + 'is_live' => !empty($user['stream']), |
| 129 | + 'stream_id' => $user['stream']['id'] ?? null |
| 130 | + ]); |
82 | 131 | }
|
83 | 132 |
|
84 |
| - $channelData = $channelResponse->json(); |
85 |
| - |
86 |
| - return response()->json([ |
87 |
| - 'user' => [ |
88 |
| - 'id' => $user['id'], |
89 |
| - 'login' => $user['login'], |
90 |
| - 'display_name' => $user['display_name'], |
91 |
| - 'type' => $user['type'], |
92 |
| - 'broadcaster_type' => $user['broadcaster_type'], |
93 |
| - 'description' => $user['description'], |
94 |
| - 'profile_image_url' => $user['profile_image_url'], |
95 |
| - 'offline_image_url' => $user['offline_image_url'], |
96 |
| - 'created_at' => $user['created_at'], |
97 |
| - ], |
98 |
| - 'channel' => $channelData['data'][0] ?? null, |
99 |
| - 'stream' => [ |
100 |
| - 'is_live' => !empty($streamData['data']), |
101 |
| - 'data' => $streamData['data'][0] ?? null, |
102 |
| - ], |
103 |
| - ]); |
104 | 133 | } catch (\Exception $e) {
|
| 134 | + logger()->error('Twitch GraphQL API Error', [ |
| 135 | + 'message' => $e->getMessage(), |
| 136 | + 'trace' => $e->getTraceAsString() |
| 137 | + ]); |
| 138 | + |
105 | 139 | return response()->json([
|
106 | 140 | 'error' => 'An error occurred',
|
107 | 141 | 'message' => $e->getMessage(),
|
| 142 | + 'trace' => $e->getTraceAsString() |
108 | 143 | ], 500);
|
109 | 144 | }
|
110 | 145 | }
|
|
0 commit comments