<?php
// Douyin share link parser implemented in PHP 8.1.
// This script accepts a Douyin share text containing a short link (v.douyin.com)
// and extracts the long URL, video ID, API URL and direct download URL.
// It does not download the video; it only returns the extracted links.

// Helper function to extract first occurrence of a v.douyin.com short URL from the share text.
function extractShortUrl(string $shareText): ?string {
    $pattern = '/https?:\/\/v\\.douyin\\.com\\/[a-zA-Z0-9_-]+\\/?/';
    if (preg_match($pattern, $shareText, $matches)) {
        return $matches[0];
    }
    return null;
}

// Helper function to perform a HTTP request and return headers and body.
// If $returnBody is false, only headers are returned.
function httpRequest(string $url, bool $returnBody = true): array {
    $ch = curl_init($url);
    // Common headers: use a mobile User‑Agent like in the Python script.
    $headers = [
        'User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
    ];
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // We'll include headers in output for redirect location extraction.
    curl_setopt($ch, CURLOPT_HEADER, true);
    // Do not automatically follow redirects – we need the Location header.
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    // Some Douyin servers use SNI/SSL with older cert chains; ignore SSL verification to avoid errors.
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

    // For HEAD requests, we can optionally disable the body.
    if (!$returnBody) {
        curl_setopt($ch, CURLOPT_NOBODY, true);
    }

    $response = curl_exec($ch);
    if ($response === false) {
        $error = curl_error($ch);
        curl_close($ch);
        return ['error' => $error];
    }

    // Separate headers and body.
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $rawHeaders = substr($response, 0, $headerSize);
    $body = $returnBody ? substr($response, $headerSize) : '';

    curl_close($ch);

    return [
        'status' => $statusCode,
        'headers' => $rawHeaders,
        'body' => $body
    ];
}

// Extract Location header from raw headers string.
function extractLocation(string $rawHeaders): ?string {
    // Headers may include multiple Location lines if there are multiple redirects; we take the first.
    if (preg_match('/^Location:\\s*(.+)$/im', $rawHeaders, $matches)) {
        // Trim whitespace and return.
        return trim($matches[1]);
    }
    return null;
}

// Main logic: given share text, extract results.
// Returns an array with keys: longUrl, videoId, apiUrl, downloadUrl, errors (optional).
function parseDouyinShare(string $shareText): array {
    $result = [
        'shortUrl' => null,
        'longUrl' => null,
        'videoId' => null,
        'apiUrl' => null,
        'downloadUrl' => null,
        'errors' => []
    ];

    // 1. Extract short URL.
    $shortUrl = extractShortUrl($shareText);
    if (!$shortUrl) {
        $result['errors'][] = '无法从分享文本中找到抖音短链接。';
        return $result;
    }
    $result['shortUrl'] = $shortUrl;

    // 2. Fetch redirect from short URL to get long URL.
    $shortResp = httpRequest($shortUrl, false);
    if (isset($shortResp['error'])) {
        $result['errors'][] = '请求短链接失败: ' . $shortResp['error'];
        return $result;
    }
    if (!in_array($shortResp['status'], [301, 302])) {
        $result['errors'][] = '短链接未返回重定向，状态码: ' . $shortResp['status'];
        return $result;
    }
    $longUrl = extractLocation($shortResp['headers']);
    if (!$longUrl) {
        $result['errors'][] = '未在短链接响应头中找到 Location。';
        return $result;
    }
    $result['longUrl'] = $longUrl;

    // 3. Fetch long URL page content to extract video_id.
    $pageResp = httpRequest($longUrl, true);
    if (isset($pageResp['error'])) {
        $result['errors'][] = '请求长链接失败: ' . $pageResp['error'];
        return $result;
    }
    // Search for video_id within page body.
    if (preg_match('/video_id=([a-zA-Z0-9]+)/', $pageResp['body'], $vidMatches)) {
        $videoId = $vidMatches[1];
        $result['videoId'] = $videoId;
    } else {
        $result['errors'][] = '未能在页面内容中找到 video_id。';
        return $result;
    }

    // 4. Construct API URL for no‑watermark video.
    $apiUrl = sprintf('https://api.amemv.com/aweme/v1/play/?video_id=%s&ratio=1080p&line=0', $result['videoId']);
    $result['apiUrl'] = $apiUrl;

    // 5. Fetch API URL to get direct download URL.
    $apiResp = httpRequest($apiUrl, false);
    if (isset($apiResp['error'])) {
        $result['errors'][] = '请求播放接口失败: ' . $apiResp['error'];
        return $result;
    }
    if (!in_array($apiResp['status'], [301, 302])) {
        $result['errors'][] = '播放接口未返回重定向，状态码: ' . $apiResp['status'];
        return $result;
    }
    $downloadUrl = extractLocation($apiResp['headers']);
    if (!$downloadUrl) {
        $result['errors'][] = '未在播放接口响应头中找到视频直链。';
        return $result;
    }
    $result['downloadUrl'] = $downloadUrl;

    return $result;
}

// If the form is submitted, parse the share text.
$shareTextInput = $_POST['share_text'] ?? '';
$parsedResult = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $parsedResult = parseDouyinShare($shareTextInput);
}

?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>抖音分享解析工具</title>
    <style>
    * {
        box-sizing: border-box;
    }

    body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
                     "PingFang SC", "Hiragino Sans GB",
                     "Microsoft YaHei", Arial, sans-serif;
        margin: 0;
        padding: 0;
        background: linear-gradient(135deg, #eef2f7, #f8fafc);
        display: flex;
        justify-content: center;
        align-items: flex-start;
        min-height: 100vh;
        color: #1f2937;
    }

    .container {
        background: #ffffff;
        margin-top: 60px;
        padding: 28px 32px 32px;
        border-radius: 14px;
        width: 100%;
        max-width: 680px;
        box-shadow:
            0 10px 30px rgba(0, 0, 0, 0.08),
            0 2px 8px rgba(0, 0, 0, 0.04);
        transition: box-shadow 0.3s ease;
    }

    .container:hover {
        box-shadow:
            0 16px 40px rgba(0, 0, 0, 0.1),
            0 4px 12px rgba(0, 0, 0, 0.06);
    }

    h1 {
        margin: 0 0 20px;
        font-size: 1.7rem;
        font-weight: 700;
        text-align: center;
        letter-spacing: 0.5px;
        color: #111827;
    }

    label {
        display: block;
        margin-bottom: 10px;
        font-size: 0.95rem;
        font-weight: 600;
        color: #374151;
    }

    textarea {
        width: 100%;
        min-height: 140px;
        padding: 14px 16px;
        border-radius: 10px;
        border: 1px solid #d1d5db;
        background: #f9fafb;
        resize: vertical;
        font-size: 0.95rem;
        line-height: 1.6;
        transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
    }

    textarea:focus {
        outline: none;
        border-color: #6366f1;
        background: #ffffff;
        box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
    }

    button {
        display: block;
        width: 100%;
        margin-top: 16px;
        padding: 12px 0;
        border-radius: 10px;
        border: none;
        background: linear-gradient(135deg, #6366f1, #4f46e5);
        color: #ffffff;
        font-size: 1rem;
        font-weight: 600;
        letter-spacing: 0.5px;
        cursor: pointer;
        transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease;
    }

    button:hover {
        transform: translateY(-1px);
        box-shadow: 0 8px 18px rgba(79, 70, 229, 0.35);
        background: linear-gradient(135deg, #4f46e5, #4338ca);
    }

    button:active {
        transform: translateY(0);
        box-shadow: 0 4px 10px rgba(79, 70, 229, 0.25);
    }

    .result {
        margin-top: 28px;
        padding-top: 20px;
        border-top: 1px dashed #e5e7eb;
    }

    .error {
        background: #fef2f2;
        color: #b91c1c;
        padding: 10px 14px;
        border-radius: 8px;
        font-weight: 600;
        margin-bottom: 14px;
        border: 1px solid #fecaca;
    }

    .link-list {
        list-style: none;
        padding: 0;
        margin: 0;
    }

    .link-list li {
        margin-bottom: 12px;
        padding: 10px 12px;
        background: #f9fafb;
        border-radius: 8px;
        border: 1px solid #e5e7eb;
        word-break: break-all;
        transition: background 0.2s ease, border-color 0.2s ease;
    }

    .link-list li:hover {
        background: #f3f4f6;
        border-color: #d1d5db;
    }

    .link-label {
        display: block;
        font-weight: 600;
        margin-bottom: 4px;
        color: #111827;
    }

    .link-list a {
        color: #4f46e5;
        text-decoration: none;
    }

    .link-list a:hover {
        text-decoration: underline;
    }
</style>

</head>
<body>
    <div class="container">
        <h1>抖音分享解析工具</h1>
        <form method="post">
            <label for="share_text">分享文本或链接：</label>
            <textarea id="share_text" name="share_text" placeholder="在此粘贴抖音分享的内容（如：8.79 复制打开抖音，看看【惜光的作品】快看！我拍到了中国画里面的太阳 # 夕阳 # 摄影... https://v.douyin.com/Why9DR1dkCw/ WMW:/ T@y.GI 11/11 ）"><?php echo htmlspecialchars($shareTextInput, ENT_QUOTES | ENT_HTML5); ?></textarea>
            <button type="submit">解析</button>
        </form>

        <?php if ($parsedResult !== null): ?>
            <div class="result">
                <?php if (!empty($parsedResult['errors'])): ?>
                    <div class="error">
                        <?php foreach ($parsedResult['errors'] as $err): ?>
                            <div><?php echo htmlspecialchars($err, ENT_QUOTES | ENT_HTML5); ?></div>
                        <?php endforeach; ?>
                    </div>
                <?php else: ?>
                    <ul class="link-list">
                        <li><span class="link-label">Video ID：</span><?php echo htmlspecialchars($parsedResult['videoId'], ENT_QUOTES | ENT_HTML5); ?></li>
                        <li><span class="link-label">无水印播放接口：</span><a href="<?php echo htmlspecialchars($parsedResult['apiUrl'], ENT_QUOTES | ENT_HTML5); ?>" target="_blank" rel="noopener"><?php echo htmlspecialchars($parsedResult['apiUrl'], ENT_QUOTES | ENT_HTML5); ?></a></li>
                        <li><span class="link-label">无水印下载直链：</span><a href="<?php echo htmlspecialchars($parsedResult['downloadUrl'], ENT_QUOTES | ENT_HTML5); ?>" target="_blank" rel="noopener"><?php echo htmlspecialchars($parsedResult['downloadUrl'], ENT_QUOTES | ENT_HTML5); ?></a></li>
                        <li><span class="link-label">短链接：</span><a href="<?php echo htmlspecialchars($parsedResult['shortUrl'], ENT_QUOTES | ENT_HTML5); ?>" target="_blank" rel="noopener"><?php echo htmlspecialchars($parsedResult['shortUrl'], ENT_QUOTES | ENT_HTML5); ?></a></li>
                        <li><span class="link-label">长链接：</span><a href="<?php echo htmlspecialchars($parsedResult['longUrl'], ENT_QUOTES | ENT_HTML5); ?>" target="_blank" rel="noopener"><?php echo htmlspecialchars($parsedResult['longUrl'], ENT_QUOTES | ENT_HTML5); ?></a></li>
                    </ul>
                <?php endif; ?>
            </div>
        <?php endif; ?>
    </div>
</body>
</html>