<?php
declare(strict_types=1);

session_start();

header('X-Content-Type-Options: nosniff');

const UC_LOGIN_URL =
    'https://api.open.uc.cn/cas/custom/login?custom_login_type=mobile&client_id=381&display=pc&spm_b=weblogin';

const UC_LOGIN_BASE =
    'https://api.open.uc.cn';

const UC_DRIVE =
    'https://drive.uc.cn';

const UC_CLIENT_ID = '381';

function jsonOut(array $data, int $status = 200): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');

    echo json_encode(
        $data,
        JSON_UNESCAPED_UNICODE |
        JSON_UNESCAPED_SLASHES
    );

    exit;
}

function ua(): string
{
    return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
        . 'AppleWebKit/537.36 (KHTML, like Gecko) '
        . 'Chrome/148.0.0.0 Safari/537.36';
}

/**
 * 从响应头提取 Set-Cookie。
 */
function extractCookies(string $headers): array
{
    $cookies = [];

    if (preg_match_all(
        '/^Set-Cookie:\s*([^;\r\n]+)/mi',
        $headers,
        $matches
    )) {
        foreach ($matches[1] as $cookie) {
            $cookie = trim($cookie);

            if (str_contains($cookie, '=')) {
                $name = substr($cookie, 0, strpos($cookie, '='));

                if ($name !== '') {
                    $cookies[$name] = $cookie;
                }
            }
        }
    }

    return $cookies;
}

/**
 * 合并 PHP Session 中保存的 UC 登录页 Cookie。
 */
function cookieString(): string
{
    $cookies = $_SESSION['uc_page_cookies'] ?? [];

    if (!is_array($cookies)) {
        return '';
    }

    return implode('; ', array_values($cookies));
}

function saveResponseCookies(string $headers): void
{
    $newCookies = extractCookies($headers);

    if (!isset($_SESSION['uc_page_cookies'])) {
        $_SESSION['uc_page_cookies'] = [];
    }

    foreach ($newCookies as $name => $value) {
        $_SESSION['uc_page_cookies'][$name] = $value;
    }
}

/**
 * 普通 UC HTTP 请求。
 */
function ucRequest(
    string $url,
    string $method = 'GET',
    ?string $body = null,
    array $headers = []
): array {
    $ch = curl_init();

    $defaultHeaders = [
        'Accept: application/json, text/plain, */*',
        'User-Agent: ' . ua(),
        'Origin: ' . UC_LOGIN_BASE,
        'Referer: ' . UC_LOGIN_URL,
    ];

    $cookie = cookieString();

    if ($cookie !== '') {
        $defaultHeaders[] = 'Cookie: ' . $cookie;
    }

    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => array_merge(
            $defaultHeaders,
            $headers
        ),
        CURLOPT_CONNECTTIMEOUT => 15,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_ENCODING => '',
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }

    $raw = curl_exec($ch);

    if ($raw === false) {
        $error = curl_error($ch);
        curl_close($ch);

        return [
            'ok' => false,
            'status' => 0,
            'headers' => '',
            'body' => '',
            'error' => $error,
        ];
    }

    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

    $responseHeaders = substr($raw, 0, $headerSize);
    $responseBody = substr($raw, $headerSize);

    curl_close($ch);

    saveResponseCookies($responseHeaders);

    return [
        'ok' => $status >= 200 && $status < 400,
        'status' => $status,
        'headers' => $responseHeaders,
        'body' => $responseBody,
        'error' => null,
    ];
}

/**
 * 从 HTML 中尝试提取动态参数。
 *
 * 不伪造参数，只读取 UC 当前登录页实际提供的值。
 */
function extractLoginParams(string $html): array
{
    $result = [
        'sig' => '',
        'session_id' => '',
        'token' => '',
        'key' => '',
        'nc_appkey' => 'CF_APP_uc_usercenter',
    ];

    foreach (array_keys($result) as $name) {

        $patterns = [
            '/name=["\']' . preg_quote($name, '/') .
            '["\'][^>]*value=["\']([^"\']*)["\']/i',

            '/id=["\']' . preg_quote($name, '/') .
            '["\'][^>]*value=["\']([^"\']*)["\']/i',

            '/["\']' . preg_quote($name, '/') .
            '["\']\s*:\s*["\']([^"\']*)["\']/i',
        ];

        foreach ($patterns as $pattern) {

            if (preg_match($pattern, $html, $m)) {
                $result[$name] = html_entity_decode(
                    (string)$m[1],
                    ENT_QUOTES | ENT_HTML5,
                    'UTF-8'
                );

                break;
            }
        }
    }

    return $result;
}

/**
 * 自动初始化 UC 手机登录页面。
 */
function initLogin(): array
{
    $response = ucRequest(
        UC_LOGIN_URL,
        'GET'
    );

    if (!$response['ok']) {
        return [
            'success' => false,
            'error' => '无法打开 UC 登录页面',
            'http_status' => $response['status'],
        ];
    }

    $params = extractLoginParams($response['body']);

    $_SESSION['uc_login_params'] = $params;

    return [
        'success' => true,
        'params_found' => [
            'sig' => $params['sig'] !== '',
            'session_id' => $params['session_id'] !== '',
            'token' => $params['token'] !== '',
            'key' => $params['key'] !== '',
        ],
    ];
}

function currentLoginParams(): array
{
    if (
        !isset($_SESSION['uc_login_params']) ||
        !is_array($_SESSION['uc_login_params'])
    ) {
        initLogin();
    }

    $params = $_SESSION['uc_login_params'] ?? [];

    if (!is_array($params)) {
        $params = [];
    }

    return [
        'sig' => (string)($params['sig'] ?? ''),
        'session_id' => (string)($params['session_id'] ?? ''),
        'token' => (string)($params['token'] ?? ''),
        'key' => (string)($params['key'] ?? ''),
        'nc_appkey' => 'CF_APP_uc_usercenter',
    ];
}

/**
 * 初始化登录。
 */
if (
    $_SERVER['REQUEST_METHOD'] === 'GET' &&
    ($_GET['action'] ?? '') === 'init'
) {
    $result = initLogin();

    jsonOut($result);
}

/**
 * AJAX 接口。
 */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $action = (string)($_POST['action'] ?? '');

    /**
     * 初始化。
     */
    if ($action === 'init') {

        unset($_SESSION['uc_login_params']);

        $result = initLogin();

        jsonOut($result);
    }

    /**
     * 获取验证码。
     */
    if ($action === 'send_sms') {

        $phone = trim(
            (string)($_POST['phone'] ?? '')
        );

        /*
         * 这里只做基本手机号格式检查。
         */
        if (!preg_match('/^\+?[0-9]{6,20}$/', $phone)) {
            jsonOut([
                'success' => false,
                'error' => '手机号格式不正确',
            ], 400);
        }

        $params = currentLoginParams();

        /*
         * 如果登录页没有提供动态参数，
         * 重新初始化一次。
         */
        if (
            $params['session_id'] === '' ||
            $params['token'] === '' ||
            $params['key'] === ''
        ) {
            unset($_SESSION['uc_login_params']);

            $init = initLogin();

            if (!$init['success']) {
                jsonOut($init, 502);
            }

            $params = currentLoginParams();
        }

        $post = http_build_query([
            'display' => 'pc',
            'client_id' => UC_CLIENT_ID,

            'captcha_id' => '',
            'captcha_val' => '',

            'login_name' => $phone,

            'sig' => $params['sig'],
            'session_id' => $params['session_id'],
            'token' => $params['token'],
            'key' => $params['key'],

            'cwk_data' => '',
            'nvc_data' => '',
            'scene' => '',
            'captcha_data' => '',
            'scene_v2' => '',
        ]);

        $url =
            UC_LOGIN_BASE .
            '/cas/custom/login/sendSmsCode' .
            '?custom_login_type=mobile';

        $response = ucRequest(
            $url,
            'POST',
            $post,
            [
                'Content-Type: application/x-www-form-urlencoded',
            ]
        );

        $json = json_decode(
            $response['body'],
            true
        );

        if (!is_array($json)) {
            jsonOut([
                'success' => false,
                'stage' => 'send_sms',
                'http_status' => $response['status'],
                'error' => 'UC 返回的不是 JSON',
            ], 502);
        }

        /*
         * 不把动态 token / sig 等返回给前端。
         */
        jsonOut([
            'success' =>
                ($response['status'] >= 200 &&
                 $response['status'] < 300),

            'stage' => 'send_sms',

            'http_status' => $response['status'],

            'uc' => $json,
        ]);
    }

    /**
     * 短信验证码登录。
     */
    if ($action === 'login') {

        $phone = trim(
            (string)($_POST['phone'] ?? '')
        );

        $smsCode = trim(
            (string)($_POST['sms_code'] ?? '')
        );

        if (!preg_match('/^\+?[0-9]{6,20}$/', $phone)) {
            jsonOut([
                'success' => false,
                'error' => '手机号格式不正确',
            ], 400);
        }

        if (
            $smsCode === '' ||
            !preg_match('/^[0-9]{4,8}$/', $smsCode)
        ) {
            jsonOut([
                'success' => false,
                'error' => '短信验证码格式不正确',
            ], 400);
        }

        $params = currentLoginParams();

        /*
         * 根据你之前实际抓到的 commit 请求结构。
         */
        $post = http_build_query([
            'client_id' => UC_CLIENT_ID,

            'redirect_uri' => UC_DRIVE,
            'display' => 'pc',
            'change_uid' => '0',
            'transmission_mode' => 'pm',

            'sig' => $params['sig'],
            'session_id' => $params['session_id'],
            'token' => $params['token'],
            'key' => $params['key'],

            'nc_appkey' => 'CF_APP_uc_usercenter',

            'origin' => UC_DRIVE . '/',

            'login_name' => $phone,
            'sms_code' => $smsCode,

            /*
             * 你抓包里存在的登录字段。
             */
            '登录' => '',
        ]);

        $url =
            UC_LOGIN_BASE .
            '/cas/custom/login/commit' .
            '?custom_login_type=mobile';

        $response = ucRequest(
            $url,
            'POST',
            $post,
            [
                'Content-Type: application/x-www-form-urlencoded',
            ]
        );

        $json = json_decode(
            $response['body'],
            true
        );

        /*
         * 验证码使用完后不再保存。
         */
        $smsCode = '';

        if (!is_array($json)) {
            jsonOut([
                'success' => false,
                'stage' => 'login',
                'http_status' => $response['status'],
                'error' => 'UC 返回的不是 JSON',
            ], 502);
        }

        if (
            isset($json['success']) &&
            $json['success'] === true
        ) {

            /*
             * UC 登录成功后，服务器响应的 Set-Cookie
             * 已经由 ucRequest() 保存到当前 Session。
             */

            $_SESSION['uc_logged_in'] = true;

            $data = $json['data'] ?? [];

            $_SESSION['uc_user'] = [
                'nickname' =>
                    (string)($data['nickname'] ?? ''),

                'uid' =>
                    (string)($data['uid'] ?? ''),
            ];

            jsonOut([
                'success' => true,
                'message' => 'UC 登录成功',

                'user' => [
                    'nickname' =>
                        $_SESSION['uc_user']['nickname'],

                    'uid' =>
                        $_SESSION['uc_user']['uid'],
                ],
            ]);
        }

        jsonOut([
            'success' => false,
            'stage' => 'login',
            'http_status' => $response['status'],
            'uc' => $json,
        ], 401);
    }

    /**
     * 当前登录状态。
     */
    if ($action === 'status') {

        $logged =
            !empty($_SESSION['uc_logged_in']);

        $user =
            $_SESSION['uc_user'] ?? null;

        jsonOut([
            'success' => true,
            'logged_in' => $logged,
            'user' => $user,
        ]);
    }

    /**
     * 清除当前 PHP 登录会话。
     */
    if ($action === 'logout') {

        $_SESSION = [];

        if (ini_get('session.use_cookies')) {

            $params = session_get_cookie_params();

            setcookie(
                session_name(),
                '',
                time() - 42000,
                $params['path'],
                $params['domain'],
                $params['secure'],
                $params['httponly']
            );
        }

        session_destroy();

        jsonOut([
            'success' => true,
            'message' => '已退出当前登录会话',
        ]);
    }

    jsonOut([
        'success' => false,
        'error' => '未知 action',
    ], 400);
}

?>
<!doctype html>
<html lang="zh-CN">

<head>

<meta charset="utf-8">

<meta
    name="viewport"
    content="width=device-width,
             initial-scale=1,
             maximum-scale=1,
             user-scalable=no"
>

<title>UC 手机登录</title>

<style>

*{
    box-sizing:border-box;
    -webkit-tap-highlight-color:transparent;
}

html,
body{
    margin:0;
    padding:0;
    min-height:100%;
}

body{
    background:
        radial-gradient(
            circle at 20% 10%,
            rgba(110,80,255,.16),
            transparent 35%
        ),
        #080b12;

    color:#f5f7fb;

    font-family:
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        "PingFang SC",
        "Microsoft YaHei",
        sans-serif;

    padding:28px 16px;
}

.wrap{
    width:100%;
    max-width:430px;
    margin:0 auto;
}

.panel{
    background:rgba(255,255,255,.055);

    border:1px solid rgba(255,255,255,.10);

    border-radius:24px;

    padding:24px;

    box-shadow:
        0 24px 70px rgba(0,0,0,.38),
        inset 0 1px 0 rgba(255,255,255,.04);

    backdrop-filter:blur(22px);
}

.logo{
    width:58px;
    height:58px;

    margin:0 auto 18px;

    border-radius:18px;

    display:flex;
    align-items:center;
    justify-content:center;

    background:
        linear-gradient(
            135deg,
            #fff,
            #cfd4ff
        );

    color:#111;

    font-size:24px;
    font-weight:800;
}

.title{
    text-align:center;
    font-size:24px;
    font-weight:750;
}

.desc{
    margin-top:8px;

    text-align:center;

    color:#858e9f;

    font-size:13px;
}

label{
    display:block;

    margin:24px 0 8px;

    color:#aeb6c6;

    font-size:13px;
}

input{
    width:100%;

    height:50px;

    border-radius:14px;

    border:1px solid rgba(255,255,255,.10);

    background:#111722;

    color:#fff;

    outline:none;

    padding:0 14px;

    font-size:15px;
}

input:focus{
    border-color:rgba(150,130,255,.65);
}

.codeRow{
    display:flex;
    gap:10px;
}

.codeRow input{
    flex:1;
}

.codeBtn{
    width:125px;

    flex-shrink:0;

    margin:0;

    padding:0;

    height:50px;

    border-radius:14px;

    background:#202737;

    color:#fff;

    border:1px solid rgba(255,255,255,.08);

    font-size:13px;
}

button{
    width:100%;

    height:50px;

    margin-top:18px;

    border:0;

    border-radius:14px;

    background:#fff;

    color:#0a0c11;

    font-size:15px;

    font-weight:750;
}

button:disabled{
    opacity:.45;
}

.status{
    margin-top:18px;

    padding:14px;

    border-radius:14px;

    background:rgba(255,255,255,.045);

    color:#aeb7c6;

    font-size:13px;

    line-height:1.7;

    word-break:break-word;
}

.success{
    color:#6ee7a0;
}

.error{
    color:#ff7777;
}

.user{
    margin-top:14px;

    padding:16px;

    border-radius:16px;

    background:rgba(110,231,160,.06);

    border:1px solid rgba(110,231,160,.12);
}

.userName{
    font-size:17px;
    font-weight:700;
}

.userUid{
    margin-top:5px;
    color:#8993a4;
    font-size:12px;
}

.logout{
    background:#202532;
    color:#fff;
}

.tip{
    margin-top:16px;

    text-align:center;

    color:#687284;

    font-size:11px;

    line-height:1.6;
}

</style>

</head>

<body>

<div class="wrap">

<div class="panel">

<div class="logo">
UC
</div>

<div class="title">
UC 网盘登录
</div>

<div class="desc">
手机号短信验证码登录
</div>

<label>
手机号
</label>

<input
    id="phone"
    type="tel"
    inputmode="tel"
    autocomplete="tel"
    placeholder="请输入手机号"
>

<label>
短信验证码
</label>

<div class="codeRow">

<input
    id="smsCode"
    type="tel"
    inputmode="numeric"
    autocomplete="one-time-code"
    placeholder="请输入验证码"
>

<button
    class="codeBtn"
    id="sendBtn"
    onclick="sendSms()"
>
获取验证码
</button>

</div>

<button
    id="loginBtn"
    onclick="login()"
>
登录 UC
</button>

<div
    class="status"
    id="status"
>
正在初始化 UC 登录……
</div>

<div
    id="userBox"
    style="display:none"
    class="user"
>
    <div
        class="userName"
        id="userName"
    ></div>

    <div
        class="userUid"
        id="userUid"
    ></div>
</div>

<button
    id="logoutBtn"
    class="logout"
    style="display:none"
    onclick="logout()"
>
退出登录
</button>

<div class="tip">
登录成功后的会话只保存在当前 PHP Session。
</div>

</div>

</div>

<script>

const phoneInput =
    document.getElementById('phone');

const codeInput =
    document.getElementById('smsCode');

const sendBtn =
    document.getElementById('sendBtn');

const loginBtn =
    document.getElementById('loginBtn');

const statusBox =
    document.getElementById('status');

const userBox =
    document.getElementById('userBox');

const userName =
    document.getElementById('userName');

const userUid =
    document.getElementById('userUid');

const logoutBtn =
    document.getElementById('logoutBtn');

function showStatus(text, type=''){

    statusBox.className =
        'status ' + type;

    statusBox.textContent = text;
}

async function api(form){

    const response =
        await fetch(location.href,{
            method:'POST',
            body:form,
            cache:'no-store',
            credentials:'same-origin'
        });

    const text =
        await response.text();

    try{

        return JSON.parse(text);

    }catch{

        return {
            success:false,
            error:'服务器返回的不是 JSON',
            raw:text
        };
    }
}

async function init(){

    const fd =
        new FormData();

    fd.append('action','init');

    try{

        const result =
            await api(fd);

        if(result.success){

            showStatus(
                'UC 登录页面初始化完成，可以输入手机号。',
                'success'
            );

        }else{

            showStatus(
                result.error ||
                'UC 登录初始化失败',
                'error'
            );
        }

    }catch(error){

        showStatus(
            '初始化失败：' + error.message,
            'error'
        );
    }
}

async function sendSms(){

    const phone =
        phoneInput.value.trim();

    if(!phone){

        showStatus(
            '请输入手机号',
            'error'
        );

        return;
    }

    sendBtn.disabled = true;

    let seconds = 60;

    sendBtn.textContent =
        seconds + ' 秒';

    const timer =
        setInterval(()=>{

            seconds--;

            sendBtn.textContent =
                seconds + ' 秒';

            if(seconds <= 0){

                clearInterval(timer);

                sendBtn.disabled = false;

                sendBtn.textContent =
                    '获取验证码';
            }

        },1000);

    showStatus(
        '正在向 UC 请求短信验证码……'
    );

    const fd =
        new FormData();

    fd.append('action','send_sms');
    fd.append('phone',phone);

    try{

        const result =
            await api(fd);

        if(result.success){

            showStatus(
                '验证码请求已提交，请查看手机短信。',
                'success'
            );

        }else{

            clearInterval(timer);

            sendBtn.disabled = false;

            sendBtn.textContent =
                '获取验证码';

            showStatus(
                formatResult(result),
                'error'
            );
        }

    }catch(error){

        clearInterval(timer);

        sendBtn.disabled = false;

        sendBtn.textContent =
            '获取验证码';

        showStatus(
            '请求失败：' + error.message,
            'error'
        );
    }
}

async function login(){

    const phone =
        phoneInput.value.trim();

    const smsCode =
        codeInput.value.trim();

    if(!phone){

        showStatus(
            '请输入手机号',
            'error'
        );

        return;
    }

    if(!smsCode){

        showStatus(
            '请输入短信验证码',
            'error'
        );

        return;
    }

    loginBtn.disabled = true;

    showStatus(
        '正在登录 UC……'
    );

    const fd =
        new FormData();

    fd.append('action','login');
    fd.append('phone',phone);
    fd.append('sms_code',smsCode);

    try{

        const result =
            await api(fd);

        if(result.success){

            showStatus(
                '登录成功。',
                'success'
            );

            userBox.style.display =
                'block';

            userName.textContent =
                result.user?.nickname ||
                'UC 用户';

            userUid.textContent =
                'UID：' +
                (result.user?.uid || '');

            logoutBtn.style.display =
                'block';

            /*
             * 不继续保存验证码。
             */
            codeInput.value = '';

        }else{

            showStatus(
                formatResult(result),
                'error'
            );
        }

    }catch(error){

        showStatus(
            '登录请求失败：' +
            error.message,
            'error'
        );

    }finally{

        loginBtn.disabled = false;
    }
}

async function logout(){

    const fd =
        new FormData();

    fd.append('action','logout');

    const result =
        await api(fd);

    if(result.success){

        userBox.style.display =
            'none';

        logoutBtn.style.display =
            'none';

        showStatus(
            '已退出当前登录会话。'
        );
    }
}

function formatResult(result){

    if(!result){
        return '未知错误';
    }

    if(result.error){
        return result.error;
    }

    if(result.uc){

        if(result.uc.message){
            return result.uc.message;
        }

        if(result.uc.error){
            return result.uc.error;
        }

        return JSON.stringify(
            result.uc,
            null,
            2
        );
    }

    return JSON.stringify(
        result,
        null,
        2
    );
}

init();

</script>

</body>
</html>