├── README.md └── twitch.php /README.md: -------------------------------------------------------------------------------- 1 | # 🎥 Twitch to M3U 2 | 3 | A lightweight and developer-friendly PHP script to retrieve the **direct `.m3u8` HLS stream URL** for any live Twitch channel. It leverages Twitch's public GraphQL API to generate a valid token and signature for authenticated playback. 4 | 5 | --- 6 | 7 | ## ✨ Features 8 | 9 | - 🔐 Generates **secure** stream access tokens and signatures via Twitch's GQL API 10 | - 🖥️ Supports **browser** and **CLI** usage 11 | - 🌐 Returns result as: 12 | - HTTP redirect (for browsers) 13 | - JSON (if `format=json` is passed) 14 | - ✅ Input validation included 15 | - 📦 No dependencies beyond built-in PHP cURL 16 | 17 | --- 18 | 19 | ## ⚙️ Requirements 20 | 21 | - PHP 7.0+ 22 | - `php-curl` enabled 23 | 24 | --- 25 | 26 | ## 🚀 Usage 27 | 28 | ### 🌍 Web (GET) 29 | 30 | Upload `script.php` to your web server and access it like so: 31 | 32 | ```bash 33 | GET /script.php?channel=dorozeaxd 34 | ``` 35 | 36 | This will respond with a `Location` redirect to the `.m3u8` stream. 37 | 38 | Want JSON instead? 39 | 40 | ```bash 41 | GET /script.php?channel=dorozeaxd&format=json 42 | ``` 43 | 44 | #### Optional: Set `Content-Type: application/json` header for JSON output 45 | 46 | --- 47 | 48 | ### 💻 CLI 49 | 50 | Run from terminal: 51 | 52 | ```bash 53 | php script.php channel=dorozeaxd 54 | ``` 55 | 56 | To get JSON output: 57 | 58 | ```bash 59 | php script.php channel=dorozeaxd format=json 60 | ``` 61 | 62 | --- 63 | 64 | ## 📥 Parameters 65 | 66 | | Name | Required | Description | 67 | |------------|----------|--------------------------------------------------| 68 | | `channel` | ✅ Yes | Twitch channel name (e.g., `dorozeaxd`) | 69 | | `format` | ❌ No | Use `json` for JSON output, otherwise redirects | 70 | 71 | --- 72 | 73 | ## 🧪 Example JSON Output 74 | 75 | ```json 76 | { 77 | "success": true, 78 | "channel": "dorozeaxd", 79 | "url": "https://usher.ttvnw.net/api/channel/hls/dorozeaxd.m3u8?...&sig=...&token=..." 80 | } 81 | ``` 82 | 83 | On error: 84 | 85 | ```json 86 | { 87 | "success": false, 88 | "error": "Channel not found or offline" 89 | } 90 | ``` 91 | 92 | --- 93 | 94 | ## 🧼 Notes 95 | 96 | - Only works if the channel is **currently live** 97 | - Output URL is valid for direct streaming (e.g. with `ffmpeg`, `VLC`, or in web players) 98 | - Twitch may update their GQL schema or validation mechanisms at any time 99 | 100 | --- 101 | 102 | ## 📄 License 103 | 104 | MIT License 105 | 106 | --- 107 | 108 | ## 🙌 Credits 109 | 110 | - Originally authored by [toxiicdev.net](https://toxiicdev.net) 111 | - Maintained & cleaned up for open source by contributors 112 | -------------------------------------------------------------------------------- /twitch.php: -------------------------------------------------------------------------------- 1 | 'PlaybackAccessToken_Template', 24 | 'query' => 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!, $platform: String!) { 25 | streamPlaybackAccessToken(channelName: $login, params: {platform: $platform, playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { 26 | value 27 | signature 28 | authorization { isForbidden forbiddenReasonCode } 29 | __typename 30 | } 31 | videoPlaybackAccessToken(id: $vodID, params: {platform: $platform, playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { 32 | value 33 | signature 34 | __typename 35 | } 36 | }', 37 | 'variables' => [ 38 | 'isLive' => true, 39 | 'login' => $channel, 40 | 'isVod' => false, 41 | 'vodID' => '', 42 | 'playerType' => 'site', 43 | 'platform' => 'web' 44 | ] 45 | ]; 46 | 47 | $ch = curl_init('https://gql.twitch.tv/gql'); 48 | curl_setopt_array($ch, [ 49 | CURLOPT_RETURNTRANSFER => true, 50 | CURLOPT_TIMEOUT => 5, 51 | CURLOPT_POST => true, 52 | CURLOPT_HTTPHEADER => [ "Client-ID: $clientId", "User-Agent: $userAgent", "Content-Type: application/json" ], 53 | CURLOPT_POSTFIELDS => json_encode($query) 54 | ]); 55 | 56 | $response = curl_exec($ch); 57 | curl_close($ch); 58 | return json_decode($response, true); 59 | } 60 | 61 | if (!$isValidChannel) { 62 | $error = 'Invalid channel name. Channel name must be 4-25 characters long and can only contain letters, numbers, and underscores.'; 63 | if ($format === 'json') { 64 | header('Content-Type: application/json'); 65 | echo json_encode([ 'success' => false, 'channel' => $channel, 'error' => $error ]); 66 | } 67 | else echo "Error: " . $error; 68 | exit(); 69 | } 70 | 71 | $response = getTwitchToken($channel, $clientId, $userAgent); 72 | $data = $response['data']['streamPlaybackAccessToken'] ?? null; 73 | 74 | if ($data == null || $data['authorization']['isForbidden']) { 75 | $error = 'Channel does not exist or stream is not available/forbidden from your location..'; 76 | if ($format === 'json') { 77 | header('Content-Type: application/json'); 78 | echo json_encode([ 'success' => false, 'channel' => $channel, 'error' => $error ]); 79 | } 80 | else echo "Error: " . $error; 81 | exit(); 82 | } 83 | 84 | $sig = urlencode($data['signature']); 85 | $token = urlencode($data['value']); 86 | $sessionId = md5(time()); 87 | $url = "https://usher.ttvnw.net/api/channel/hls/$channel.m3u8?" 88 | . "acmb=$sessionId&allow_source=true&browser_family=chrome&browser_version=136.0" 89 | . "&cdm=wv&enable_score=true&fast_bread=true&os_name=macOS&os_version=10.15.7" 90 | . "&p=1337&platform=web&play_session_id=$sessionId&player_backend=mediaplayer" 91 | . "&player_version=1.41.0-rc.1&playlist_include_framerate=true" 92 | . "&reassignments_supported=true&sig=$sig&supported_codecs=av1,h265,h264" 93 | . "&token=$token&transcode_mode=cbr_v1"; 94 | 95 | if ($format === 'json') { 96 | header('Content-Type: application/json'); 97 | echo json_encode([ 'success' => true, 'channel' => $channel, 'url' => $url ]); 98 | } 99 | else header("Location: $url"); 100 | 101 | ?> 102 | --------------------------------------------------------------------------------