first commit
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
// 简单允许方法:GET
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'GET') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method Not Allowed'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 只有在携带 type=xianyu 参数时才允许返回
|
||||
$reqType = $_GET['type'] ?? null;
|
||||
if ($reqType !== 'xianyu') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid request parameter', 'need_type' => 'xianyu'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../lib/db.php';
|
||||
require_once __DIR__ . '/../lib/external.php';
|
||||
|
||||
$cfg = require __DIR__ . '/../config.php';
|
||||
|
||||
$externalBase = $cfg['external_base_url'] ?? '';
|
||||
$deviceCode = $cfg['device_code'] ?? '';
|
||||
$deviceCodeMd5 = $cfg['device_code_md5'] ?? '';
|
||||
$sqlitePath = $cfg['sqlite_path'] ?? (__DIR__ . '/../data/sendcard.sqlite');
|
||||
|
||||
if ($externalBase === '' || $deviceCode === '' || $deviceCodeMd5 === '') {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Config missing'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$externalUrl = $externalBase . '?device_code=' . rawurlencode($deviceCode) . '&device_code_md5=' . rawurlencode($deviceCodeMd5);
|
||||
|
||||
try {
|
||||
$resp = sendcard_fetch_credentials($externalUrl);
|
||||
$httpStatus = $resp['http_status'];
|
||||
$json = $resp['json'];
|
||||
|
||||
$externalMsg = isset($json['msg']) ? (string)$json['msg'] : null;
|
||||
$externalCode = isset($json['code']) ? (string)$json['code'] : null;
|
||||
|
||||
$data = is_array($json['data'] ?? null) ? $json['data'] : null;
|
||||
if ($data === null) {
|
||||
http_response_code(502);
|
||||
echo json_encode([
|
||||
'error' => 'External response missing data',
|
||||
'external_http_status' => $httpStatus,
|
||||
'external_response' => $json,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 仅给请求端返回 token;其它字段仅用于 SQLite 备档
|
||||
$token = isset($data['token']) ? (string)$data['token'] : null;
|
||||
|
||||
$pdo = sendcard_get_pdo($sqlitePath);
|
||||
sendcard_init_db($pdo);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO cursor_login_backups (
|
||||
external_msg, external_code,
|
||||
external_id, email, token, createTime, lastTokenTime, status,
|
||||
deviceCode, activationCode, useTime, lastId, deleted, emailLastStatus,
|
||||
useCount, pwd, type, updateTime, banName,
|
||||
webToken, cpName, comeStatus, comePushTime,
|
||||
freeSevenStatus, windsurfUseStatus, windsurfStatus, windsurfToken,
|
||||
windsurfPwd, windsurfUseTime, raw_json
|
||||
) VALUES (
|
||||
:external_msg, :external_code,
|
||||
:external_id, :email, :token, :createTime, :lastTokenTime, :status,
|
||||
:deviceCode, :activationCode, :useTime, :lastId, :deleted, :emailLastStatus,
|
||||
:useCount, :pwd, :type, :updateTime, :banName,
|
||||
:webToken, :cpName, :comeStatus, :comePushTime,
|
||||
:freeSevenStatus, :windsurfUseStatus, :windsurfStatus, :windsurfToken,
|
||||
:windsurfPwd, :windsurfUseTime, :raw_json
|
||||
)'
|
||||
);
|
||||
|
||||
// 一次回调:把 external data 的字段拆出来逐列存储
|
||||
$stmt->execute([
|
||||
':external_msg' => $externalMsg,
|
||||
':external_code' => $externalCode,
|
||||
':external_id' => isset($data['id']) ? (int)$data['id'] : null,
|
||||
':email' => $data['email'] ?? null,
|
||||
':token' => $token,
|
||||
':createTime' => $data['createTime'] ?? null,
|
||||
':lastTokenTime' => $data['lastTokenTime'] ?? null,
|
||||
':status' => isset($data['status']) ? (int)$data['status'] : null,
|
||||
':deviceCode' => $data['deviceCode'] ?? null,
|
||||
':activationCode' => $data['activationCode'] ?? null,
|
||||
':useTime' => $data['useTime'] ?? null,
|
||||
':lastId' => isset($data['lastId']) ? (int)$data['lastId'] : null,
|
||||
':deleted' => isset($data['deleted']) ? (int)$data['deleted'] : null,
|
||||
':emailLastStatus' => isset($data['emailLastStatus']) ? (int)$data['emailLastStatus'] : null,
|
||||
':useCount' => isset($data['useCount']) ? (int)$data['useCount'] : null,
|
||||
':pwd' => $data['pwd'] ?? null,
|
||||
':type' => isset($data['type']) ? (int)$data['type'] : null,
|
||||
':updateTime' => $data['updateTime'] ?? null,
|
||||
':banName' => $data['banName'] ?? null,
|
||||
':webToken' => $data['webToken'] ?? null,
|
||||
':cpName' => $data['cpName'] ?? null,
|
||||
':comeStatus' => isset($data['comeStatus']) ? (int)$data['comeStatus'] : null,
|
||||
':comePushTime' => $data['comePushTime'] ?? null,
|
||||
':freeSevenStatus' => isset($data['freeSevenStatus']) ? (int)$data['freeSevenStatus'] : null,
|
||||
':windsurfUseStatus' => isset($data['windsurfUseStatus']) ? (int)$data['windsurfUseStatus'] : null,
|
||||
':windsurfStatus' => isset($data['windsurfStatus']) ? (int)$data['windsurfStatus'] : null,
|
||||
':windsurfToken' => $data['windsurfToken'] ?? null,
|
||||
':windsurfPwd' => $data['windsurfPwd'] ?? null,
|
||||
':windsurfUseTime' => $data['windsurfUseTime'] ?? null,
|
||||
':raw_json' => json_encode($json, JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
|
||||
if ($token === null || $token === '') {
|
||||
http_response_code(502);
|
||||
echo json_encode(['error' => 'External response missing token', 'external_http_status' => $httpStatus], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 只返回 token 给请求端
|
||||
echo $token;
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(502);
|
||||
echo json_encode(['error' => 'Bad Gateway', 'detail' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
|
||||
// 简单允许方法:GET
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'GET') {
|
||||
|
||||
http_response_code(405);
|
||||
|
||||
echo json_encode(['error' => 'Method Not Allowed'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 只有在携带 type=xianyu 参数时才允许返回
|
||||
|
||||
$reqType = $_GET['type'] ?? null;
|
||||
|
||||
if ($reqType !== 'xianyu') {
|
||||
|
||||
http_response_code(400);
|
||||
|
||||
echo json_encode(['error' => 'Invalid request parameter', 'need_type' => 'xianyu'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
require_once __DIR__ . '/../lib/db.php';
|
||||
|
||||
require_once __DIR__ . '/../lib/external.php';
|
||||
|
||||
|
||||
$cfg = require __DIR__ . '/../config.php';
|
||||
|
||||
|
||||
$externalBase = $cfg['external_base_url'] ?? '';
|
||||
|
||||
$deviceCode = $cfg['device_code'] ?? '';
|
||||
|
||||
$deviceCodeMd5 = $cfg['device_code_md5'] ?? '';
|
||||
|
||||
$sqlitePath = $cfg['sqlite_path'] ?? (__DIR__ . '/../data/sendcard.sqlite');
|
||||
|
||||
|
||||
if ($externalBase === '' || $deviceCode === '' || $deviceCodeMd5 === '') {
|
||||
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode(['error' => 'Config missing'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
$externalUrl = $externalBase . '?device_code=' . rawurlencode($deviceCode) . '&device_code_md5=' . rawurlencode($deviceCodeMd5);
|
||||
|
||||
|
||||
try {
|
||||
|
||||
$resp = sendcard_fetch_credentials($externalUrl);
|
||||
|
||||
$httpStatus = $resp['http_status'];
|
||||
|
||||
$json = $resp['json'];
|
||||
|
||||
|
||||
$externalMsg = isset($json['msg']) ? (string)$json['msg'] : null;
|
||||
|
||||
$externalCode = isset($json['code']) ? (string)$json['code'] : null;
|
||||
|
||||
|
||||
$data = is_array($json['data'] ?? null) ? $json['data'] : null;
|
||||
|
||||
if ($data === null) {
|
||||
|
||||
http_response_code(502);
|
||||
|
||||
echo json_encode(['error' => 'External response missing data'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 仅给请求端返回 token;其它字段仅用于 SQLite 备档
|
||||
|
||||
$token = isset($data['token']) ? (string)$data['token'] : null;
|
||||
|
||||
|
||||
$pdo = sendcard_get_pdo($sqlitePath);
|
||||
|
||||
sendcard_init_db($pdo);
|
||||
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
|
||||
'INSERT INTO cursor_login_backups (
|
||||
|
||||
external_msg, external_code,
|
||||
|
||||
external_id, email, token, createTime, lastTokenTime, status,
|
||||
|
||||
deviceCode, activationCode, useTime, lastId, deleted, emailLastStatus,
|
||||
|
||||
useCount, pwd, type, updateTime, banName,
|
||||
|
||||
webToken, cpName, comeStatus, comePushTime,
|
||||
|
||||
freeSevenStatus, windsurfUseStatus, windsurfStatus, windsurfToken,
|
||||
|
||||
windsurfPwd, windsurfUseTime, raw_json
|
||||
|
||||
) VALUES (
|
||||
|
||||
:external_msg, :external_code,
|
||||
|
||||
:external_id, :email, :token, :createTime, :lastTokenTime, :status,
|
||||
|
||||
:deviceCode, :activationCode, :useTime, :lastId, :deleted, :emailLastStatus,
|
||||
|
||||
:useCount, :pwd, :type, :updateTime, :banName,
|
||||
|
||||
:webToken, :cpName, :comeStatus, :comePushTime,
|
||||
|
||||
:freeSevenStatus, :windsurfUseStatus, :windsurfStatus, :windsurfToken,
|
||||
|
||||
:windsurfPwd, :windsurfUseTime, :raw_json
|
||||
|
||||
)'
|
||||
|
||||
);
|
||||
|
||||
|
||||
// 一次回调:把 external data 的字段拆出来逐列存储
|
||||
|
||||
$stmt->execute([
|
||||
|
||||
':external_msg' => $externalMsg,
|
||||
|
||||
':external_code' => $externalCode,
|
||||
|
||||
':external_id' => isset($data['id']) ? (int)$data['id'] : null,
|
||||
|
||||
':email' => $data['email'] ?? null,
|
||||
|
||||
':token' => $token,
|
||||
|
||||
':createTime' => $data['createTime'] ?? null,
|
||||
|
||||
':lastTokenTime' => $data['lastTokenTime'] ?? null,
|
||||
|
||||
':status' => isset($data['status']) ? (int)$data['status'] : null,
|
||||
|
||||
':deviceCode' => $data['deviceCode'] ?? null,
|
||||
|
||||
':activationCode' => $data['activationCode'] ?? null,
|
||||
|
||||
':useTime' => $data['useTime'] ?? null,
|
||||
|
||||
':lastId' => isset($data['lastId']) ? (int)$data['lastId'] : null,
|
||||
|
||||
':deleted' => isset($data['deleted']) ? (int)$data['deleted'] : null,
|
||||
|
||||
':emailLastStatus' => isset($data['emailLastStatus']) ? (int)$data['emailLastStatus'] : null,
|
||||
|
||||
':useCount' => isset($data['useCount']) ? (int)$data['useCount'] : null,
|
||||
|
||||
':pwd' => $data['pwd'] ?? null,
|
||||
|
||||
':type' => isset($data['type']) ? (int)$data['type'] : null,
|
||||
|
||||
':updateTime' => $data['updateTime'] ?? null,
|
||||
|
||||
':banName' => $data['banName'] ?? null,
|
||||
|
||||
':webToken' => $data['webToken'] ?? null,
|
||||
|
||||
':cpName' => $data['cpName'] ?? null,
|
||||
|
||||
':comeStatus' => isset($data['comeStatus']) ? (int)$data['comeStatus'] : null,
|
||||
|
||||
':comePushTime' => $data['comePushTime'] ?? null,
|
||||
|
||||
':freeSevenStatus' => isset($data['freeSevenStatus']) ? (int)$data['freeSevenStatus'] : null,
|
||||
|
||||
':windsurfUseStatus' => isset($data['windsurfUseStatus']) ? (int)$data['windsurfUseStatus'] : null,
|
||||
|
||||
':windsurfStatus' => isset($data['windsurfStatus']) ? (int)$data['windsurfStatus'] : null,
|
||||
|
||||
':windsurfToken' => $data['windsurfToken'] ?? null,
|
||||
|
||||
':windsurfPwd' => $data['windsurfPwd'] ?? null,
|
||||
|
||||
':windsurfUseTime' => $data['windsurfUseTime'] ?? null,
|
||||
|
||||
':raw_json' => json_encode($json, JSON_UNESCAPED_UNICODE),
|
||||
|
||||
]);
|
||||
|
||||
|
||||
if ($token === null || $token === '') {
|
||||
|
||||
http_response_code(502);
|
||||
|
||||
echo json_encode(['error' => 'External response missing token', 'external_http_status' => $httpStatus], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 只返回 token 给请求端
|
||||
|
||||
echo json_encode(['token' => $token], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
|
||||
http_response_code(502);
|
||||
|
||||
echo json_encode(['error' => 'Bad Gateway', 'detail' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 定时轮询拉取外部凭证并追加写入本地 txt(逻辑与 getcard 调同一 external 接口,仅触发节奏不同)。
|
||||
*
|
||||
* 设备:使用 config['poll'] 的机器 B 的 device_code / device_code_md5;getcard.php 使用顶层机器 A。
|
||||
* 原因:上游常对单设备或请求频次有限制,即时请求走 A、定时轮询走 B,避免抢同一套额度。
|
||||
*
|
||||
* 间隔:基准 5 分钟 ±3 分钟随机,且不少于 4 分钟(由「下次执行时间」控制)。
|
||||
* 宝塔 / Linux:计划任务每分钟执行一次本脚本;未到点会直接退出,不会请求外部接口。
|
||||
*
|
||||
* 示例 crontab(把路径改成你的站点根目录下的本文件):
|
||||
* * * * * * /usr/bin/php /www/wwwroot/你的域名/api/getcard_poll.php >>/www/wwwroot/你的域名/data/getcard_poll_cron.log 2>&1
|
||||
*/
|
||||
|
||||
$cfg = require __DIR__ . '/../config.php';
|
||||
|
||||
$isCli = PHP_SAPI === 'cli';
|
||||
$cronKey = isset($cfg['poll_cron_key']) ? (string)$cfg['poll_cron_key'] : '';
|
||||
if (!$isCli) {
|
||||
if ($cronKey === '' || (($_GET['key'] ?? '') !== $cronKey)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Forbidden';
|
||||
exit;
|
||||
}
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
$dataDir = __DIR__ . '/../data';
|
||||
if (!is_dir($dataDir)) {
|
||||
if (!@mkdir($dataDir, 0755, true) && !is_dir($dataDir)) {
|
||||
sendcard_poll_out("Cannot create data directory: {$dataDir}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$statePath = isset($cfg['poll_state_json']) && $cfg['poll_state_json'] !== ''
|
||||
? (string)$cfg['poll_state_json']
|
||||
: $dataDir . DIRECTORY_SEPARATOR . 'getcard_poll_state.json';
|
||||
$logPath = isset($cfg['poll_log_txt']) && $cfg['poll_log_txt'] !== ''
|
||||
? (string)$cfg['poll_log_txt']
|
||||
: $dataDir . DIRECTORY_SEPARATOR . 'getcard_poll_log.txt';
|
||||
|
||||
$externalBase = trim((string)($cfg['external_base_url'] ?? ''));
|
||||
if ($externalBase === '') {
|
||||
sendcard_poll_out("Config missing: external_base_url\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$externalUrl = sendcard_poll_build_external_url($cfg);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
sendcard_poll_out('Config invalid: ' . $e->getMessage() . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$fh = fopen($statePath, 'c+');
|
||||
if ($fh === false) {
|
||||
sendcard_poll_out("Cannot open state file: {$statePath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!flock($fh, LOCK_EX)) {
|
||||
fclose($fh);
|
||||
sendcard_poll_out("Cannot lock state file\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$raw = stream_get_contents($fh);
|
||||
$state = is_string($raw) && $raw !== '' ? json_decode($raw, true) : null;
|
||||
if (!is_array($state)) {
|
||||
$state = [];
|
||||
}
|
||||
$nextRunAt = isset($state['next_run_at']) ? (int)$state['next_run_at'] : 0;
|
||||
|
||||
if ($now < $nextRunAt) {
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
$wait = $nextRunAt - $now;
|
||||
sendcard_poll_out("Skip: next run in {$wait}s (at " . date('Y-m-d H:i:s', $nextRunAt) . ")\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../lib/external.php';
|
||||
|
||||
$linePrefix = '[' . date('Y-m-d H:i:s') . '] ';
|
||||
$logBlock = '';
|
||||
|
||||
try {
|
||||
$resp = sendcard_fetch_credentials($externalUrl);
|
||||
$httpStatus = $resp['http_status'];
|
||||
$json = $resp['json'];
|
||||
$token = isset($json['data']['token']) ? (string)$json['data']['token'] : null;
|
||||
$logBlock = $token !== null && $token !== ''
|
||||
? $token . "\n"
|
||||
: $linePrefix . "http={$httpStatus} token missing: " . json_encode($json, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
} catch (Throwable $e) {
|
||||
$logBlock = $linePrefix . 'ERROR ' . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// $intervalSec = max(4 * 60, 5 * 60 + random_int(-3 * 60, 3 * 60));
|
||||
$intervalSec = random_int(540, 840);
|
||||
$newNext = $now + $intervalSec;
|
||||
$state['next_run_at'] = $newNext;
|
||||
$state['last_run_at'] = $now;
|
||||
$state['last_interval_sec'] = $intervalSec;
|
||||
|
||||
rewind($fh);
|
||||
ftruncate($fh, 0);
|
||||
fwrite($fh, json_encode($state, JSON_UNESCAPED_UNICODE));
|
||||
fflush($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
|
||||
if ($logBlock !== '') {
|
||||
file_put_contents($logPath, $logBlock, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
sendcard_poll_out(
|
||||
$logBlock . "Scheduled next run in {$intervalSec}s (at " . date('Y-m-d H:i:s', $newNext) . ")\n"
|
||||
);
|
||||
exit(0);
|
||||
|
||||
function sendcard_poll_out(string $msg): void
|
||||
{
|
||||
echo $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询请求 URL:使用 config['poll'] 下的 device_code、device_code_md5 作为查询参数值(与 getcard 顶层 device_* 分离)。
|
||||
*
|
||||
* @param array<string, mixed> $cfg
|
||||
*/
|
||||
function sendcard_poll_build_external_url(array $cfg): string
|
||||
{
|
||||
$base = trim((string)($cfg['external_base_url'] ?? ''));
|
||||
if ($base === '') {
|
||||
throw new InvalidArgumentException('external_base_url');
|
||||
}
|
||||
|
||||
$poll = $cfg['poll'] ?? null;
|
||||
if (!is_array($poll)) {
|
||||
throw new InvalidArgumentException("config['poll'] 须为数组且含 device_code、device_code_md5");
|
||||
}
|
||||
$dc = trim((string)($poll['device_code'] ?? ''));
|
||||
$dm = trim((string)($poll['device_code_md5'] ?? ''));
|
||||
if ($dc === '' || $dm === '') {
|
||||
throw new InvalidArgumentException("config['poll']['device_code'] 与 ['device_code_md5'] 勿留空");
|
||||
}
|
||||
|
||||
return $base . '?device_code=' . rawurlencode($dc) . '&device_code_md5=' . rawurlencode($dm);
|
||||
}
|
||||
Reference in New Issue
Block a user