<?php
// m3u8_crawler.php
// 안정성 개선: CURL 오류 처리, IP별 결과 수집 후 출력, 500 에러 원인 방지

function fetch_url_with_curl($url, $host_override = '', $ip_override = '') {
    if(!function_exists('curl_init')) {
        return ['content' => false, 'http_code' => 0, 'error' => 'curl 확장 없음'];
    }

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

    $headers = [];
    if($host_override) {
        $headers[] = "Host: $host_override";
    }
    if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    if($ip_override) {
        $scheme = parse_url($url, PHP_URL_SCHEME) ?: 'http';
        $port = ($scheme === 'https') ? 443 : 80;
        // CURLOPT_RESOLVE expects array like 'domain:port:ip'
        curl_setopt($ch, CURLOPT_RESOLVE, ["$host_override:$port:$ip_override"]);
    }

    $content = curl_exec($ch);
    $err = curl_error($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);

    return ['content' => $content, 'http_code' => $info['http_code'] ?? 0, 'error' => $err ?: null];
}

function fetch_url_fallback($url) {
    // simple file_get_contents fallback (no host/ip override)
    $ctx = stream_context_create(['http'=>['timeout'=>10]]);
    $content = @file_get_contents($url, false, $ctx);
    $http_code = 0;
    if($content !== false) $http_code = 200;
    return ['content' => $content, 'http_code' => $http_code, 'error' => null];
}

$results = []; // array of per-IP results

if(isset($_POST['playlist_url'])){
    $playlist_url = trim($_POST['playlist_url']);
    $ip_text = isset($_POST['ip_override']) ? trim($_POST['ip_override']) : '';

    $ip_list = [];
    if($ip_text === '') {
        $ip_list[] = ''; // empty means use system DNS (no override)
    } else {
        // split by newline or comma or space
        $lines = preg_split('/[\r\n,;]+/', $ip_text);
        foreach($lines as $l) {
            $v = trim($l);
            if($v !== '') $ip_list[] = $v;
        }
    }

    // ensure at least one item
    if(empty($ip_list)) $ip_list[] = '';

    $parsed_playlist = @parse_url($playlist_url);
    if(!$parsed_playlist || !isset($parsed_playlist['host'])) {
        $results[] = ['ip' => '(invalid)', 'error' => 'playlist URL이 올바르지 않습니다.'];
    } else {
        $host = $parsed_playlist['host'];
        $scheme = $parsed_playlist['scheme'] ?? 'https';
        $path = $parsed_playlist['path'] ?? '/';
        $base_path = rtrim(substr($path, 0, strrpos($path, '/') ?: 0), '/') ?: '';

        foreach($ip_list as $ip_override) {
            $res = ['ip' => $ip_override ?: '(default DNS)', 'playlist' => null, 'chunklist' => null, 'chunklist_url' => null, 'ts' => [], 'll_hls' => null, 'm4v' => []];

            // fetch playlist
            if(function_exists('curl_init')) {
                $p = fetch_url_with_curl($playlist_url, $host, $ip_override ?: '');
            } else {
                $p = fetch_url_fallback($playlist_url);
            }
            $res['playlist'] = ['http_code' => $p['http_code'], 'content' => $p['content'], 'error' => $p['error']];

            if(!$p['content']) {
                $results[] = $res;
                continue; // next ip
            }

            // parse playlist for chunklist line (first non-comment .m3u8)
            $lines = array_filter(array_map('trim', preg_split('/\r?\n/', $p['content'])), fn($l)=>$l !== '' && $l[0] !== '#');
            $chunklist_rel = null;
            foreach($lines as $l) {
                if(strtolower(substr($l, -5)) === '.m3u8' || stripos($l, 'chunklist') !== false) {
                    $chunklist_rel = $l; break;
                }
            }
            if(!$chunklist_rel) {
                $res['chunklist'] = ['error' => 'chunklist 경로를 찾을 수 없음'];
                $results[] = $res; continue;
            }

            // build chunklist URL: if absolute, use as-is; else construct using base path
            if(strpos($chunklist_rel, 'http') === 0) {
                $chunklist_url = $chunklist_rel;
            } else {
                // if base_path is empty, use /
                $chunklist_url = $scheme . '://' . $host . ($base_path ? $base_path . '/' : '/') . $chunklist_rel;
            }

            $res['chunklist_url'] = $chunklist_url;

            // fetch chunklist
            if(function_exists('curl_init')) {
                $c = fetch_url_with_curl($chunklist_url, $host, $ip_override ?: '');
            } else {
                $c = fetch_url_fallback($chunklist_url);
            }
            $res['chunklist'] = ['http_code' => $c['http_code'], 'content' => $c['content'], 'error' => $c['error']];

            if(!$c['content']) { $results[] = $res; continue; }

            // TS list for regular HLS
            $clines = array_filter(array_map('trim', preg_split('/\r?\n/', $c['content'])), fn($l)=>$l !== '' && $l[0] !== '#');
            foreach($clines as $line) {
                if(strtolower(substr($line, -3)) === '.ts') {
                    $res['ts'][] = (strpos($line, 'http') === 0) ? $line : rtrim(dirname($chunklist_url), '/') . '/' . $line;
                }
            }

            // detect LL-HLS LAST-MSN
            if(preg_match('/LAST-MSN\s*=\s*(\d+)/i', $c['content'], $m)) {
                $last_msn = $m[1];
                $ll_url = $chunklist_url . "?_HLS_msn={$last_msn}&_HLS_part=1";
                $res['ll_hls'] = ['url' => $ll_url];

                if(function_exists('curl_init')) {
                    $ll = fetch_url_with_curl($ll_url, $host, $ip_override ?: '');
                } else {
                    $ll = fetch_url_fallback($ll_url);
                }
                $res['ll_hls']['http_code'] = $ll['http_code'];
                $res['ll_hls']['content'] = $ll['content'];

                if($ll['content']) {
                    $ll_lines = array_filter(array_map('trim', preg_split('/\r?\n/', $ll['content'])), fn($l)=>$l !== '' && $l[0] !== '#');
                    foreach($ll_lines as $ln) {
                        if(strtolower(substr($ln, -4)) === '.m4v' || strtolower(substr($ln, -4)) === '.m4a' || strtolower(substr($ln, -4)) === '.mp4') {
                            $res['m4v'][] = (strpos($ln, 'http') === 0) ? $ln : rtrim(dirname($ll_url), '/') . '/' . $ln;
                        }
                    }
                }
            }

            $results[] = $res;
        }
    }
}
?>

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>m3u8 Crawler</title>
  <style>
    body{font-family:system-ui,Arial;padding:16px}
    textarea{width:100%;height:160px}
    pre{background:#f6f8fa;padding:8px;border-radius:6px;overflow:auto}
    .box{border:1px solid #ddd;padding:10px;margin:10px 0;border-radius:6px}
    .ok{color:green}.err{color:red}
  </style>
</head>
<body>
<h2>Streaming Check</h2>
<form method="post">
  playlist.m3u8 URL:<br>
  <input type="text" name="playlist_url" size="80" value="<?php echo isset($_POST['playlist_url']) ? htmlspecialchars($_POST['playlist_url']) : ''; ?>"><br><br>
  IP 리스트 (한 줄에 하나씩, 비워두면 기본 DNS):<br>
  <textarea name="ip_override" rows="4"><?php echo isset($_POST['ip_override']) ? htmlspecialchars($_POST['ip_override']) : ''; ?></textarea><br>
  <button type="submit">실행</button>
</form>

<?php if(!empty($results)): ?>
  <?php foreach($results as $r): ?>
    <div class="box">
      <strong>IP:</strong> <?php echo htmlspecialchars($r['ip']); ?><br>
      <strong>Playlist HTTP:</strong> <?php echo htmlspecialchars($r['playlist']['http_code'] ?? 0); ?>
      <?php if(!empty($r['playlist']['error'])): ?> <span class="err"><?php echo htmlspecialchars($r['playlist']['error']); ?></span><?php endif; ?><br>
      <?php if(!empty($r['playlist']['content'])): ?>
        <details><summary>playlist body</summary><pre><?php echo htmlspecialchars(substr($r['playlist']['content'],0,8000)); ?></pre></details>
      <?php endif; ?>

      <strong>Chunklist URL:</strong> <?php echo htmlspecialchars($r['chunklist_url'] ?? '-'); ?><br>
      <strong>Chunklist HTTP:</strong> <?php echo htmlspecialchars($r['chunklist']['http_code'] ?? 0); ?>
      <?php if(!empty($r['chunklist']['error'])): ?> <span class="err"><?php echo htmlspecialchars($r['chunklist']['error']); ?></span><?php endif; ?><br>
      <?php if(!empty($r['chunklist']['content'])): ?>
        <details><summary>chunklist body</summary><pre><?php echo htmlspecialchars(substr($r['chunklist']['content'],0,8000)); ?></pre></details>
      <?php endif; ?>

      <?php if(!empty($r['ll_hls'])): ?>
        <strong>LL-HLS URL:</strong> <?php echo htmlspecialchars($r['ll_hls']['url']); ?><br>
        <strong>LL-HLS HTTP:</strong> <?php echo htmlspecialchars($r['ll_hls']['http_code']); ?><br>
        <?php if(!empty($r['ll_hls']['content'])): ?>
          <details><summary>LL-HLS body</summary><pre><?php echo htmlspecialchars(substr($r['ll_hls']['content'],0,8000)); ?></pre></details>
        <?php endif; ?>
        <?php if(!empty($r['m4v'])): ?>
          <strong>m4v/mp4 files:</strong>
          <ul><?php foreach($r['m4v'] as $m): echo '<li>'.htmlspecialchars($m).'</li>'; endforeach; ?></ul>
        <?php endif; ?>
      <?php endif; ?>

      <?php if(!empty($r['ts'])): ?>
        <strong>TS files:</strong>
        <ul><?php foreach($r['ts'] as $t): echo '<li>'.htmlspecialchars($t).'</li>'; endforeach; ?></ul>
      <?php endif; ?>

    </div>
  <?php endforeach; ?>
<?php endif; ?>

</body>
</html>

