fixed แต่งตัวไม่ได้
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* สาธารณะ — ระบบ "รหัสผู้เล่น" สำหรับ sync โปรไฟล์ข้าม browser/เครื่อง (Guest)
|
||||
* ผูกกับ jdPlayerKey (guest account: providerUserId = key) เหมือน player-coins.php
|
||||
*
|
||||
* GET ?action=code&playerKey=...&displayName=...&agentId=...
|
||||
* → คืนรหัสของบัญชีนี้ (สร้างถ้ายังไม่มี) + อัปเดตชื่อ/agent ฝั่ง server
|
||||
* { ok, code, codeDisplay }
|
||||
*
|
||||
* POST { action:'redeem', code }
|
||||
* → ดึงโปรไฟล์จากรหัส → คืน playerKey + ข้อมูลโปรไฟล์ของบัญชีนั้น
|
||||
* { ok, playerKey, coins, colorThemeIndex, skinToneIndex, displayName, agentId }
|
||||
*
|
||||
* รหัสที่ไม่ถูกใช้เกิน 30 วัน → หมดอายุ (ลบแบบ lazy ตอนมีการเรียก)
|
||||
*/
|
||||
require __DIR__ . '/_common.php';
|
||||
|
||||
const LINK_CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; // ตัด 0 O 1 I L ที่กำกวม
|
||||
const LINK_CODE_LEN = 8;
|
||||
const LINK_CODE_TTL_DAYS = 30;
|
||||
|
||||
function valid_player_key(string $key): bool
|
||||
{
|
||||
return (bool)preg_match('/^[a-zA-Z0-9_-]{8,128}$/', $key);
|
||||
}
|
||||
|
||||
function sanitize_display_name(string $raw): string
|
||||
{
|
||||
$s = trim($raw);
|
||||
$s = preg_replace('/[\x00-\x1f\x7f]/u', '', $s) ?? '';
|
||||
if (function_exists('mb_substr')) {
|
||||
$s = mb_substr($s, 0, 24);
|
||||
} else {
|
||||
$s = substr($s, 0, 24);
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
function sanitize_agent_id(string $raw): string
|
||||
{
|
||||
return preg_match('/^\d{6}$/', trim($raw)) ? trim($raw) : '';
|
||||
}
|
||||
|
||||
function normalize_code(string $raw): string
|
||||
{
|
||||
$s = strtoupper(trim($raw));
|
||||
$s = str_replace([' ', '-', '_'], '', $s);
|
||||
// กันสับสน: ผู้ใช้พิมพ์ 0/O/1/I/L → map เข้าตัวที่ใช้จริง
|
||||
$s = strtr($s, ['0' => 'O', '1' => 'I']);
|
||||
return $s;
|
||||
}
|
||||
|
||||
function generate_code(): string
|
||||
{
|
||||
$n = strlen(LINK_CODE_ALPHABET);
|
||||
$out = '';
|
||||
for ($i = 0; $i < LINK_CODE_LEN; $i++) {
|
||||
$out .= LINK_CODE_ALPHABET[random_int(0, $n - 1)];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function format_code_display(string $code): string
|
||||
{
|
||||
if (strlen($code) === 8) {
|
||||
return substr($code, 0, 4) . '-' . substr($code, 4, 4);
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
function code_is_expired($lastUsedAt): bool
|
||||
{
|
||||
if (!$lastUsedAt) {
|
||||
return false;
|
||||
}
|
||||
$ts = strtotime((string)$lastUsedAt);
|
||||
if ($ts === false) {
|
||||
return false;
|
||||
}
|
||||
return (time() - $ts) > (LINK_CODE_TTL_DAYS * 86400);
|
||||
}
|
||||
|
||||
/** ลบรหัสที่หมดอายุออกจากทุกบัญชี (lazy GC) — แก้ใน $store ตรง ๆ */
|
||||
function gc_expired_codes(array &$store): bool
|
||||
{
|
||||
$changed = false;
|
||||
foreach ($store['accounts'] as $i => $a) {
|
||||
if (!empty($a['linkCode']) && code_is_expired($a['linkCodeLastUsedAt'] ?? null)) {
|
||||
unset($store['accounts'][$i]['linkCode']);
|
||||
$store['accounts'][$i]['linkCodeLastUsedAt'] = null;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
function code_in_use(array $store, string $code, ?string $exceptKey = null): bool
|
||||
{
|
||||
foreach ($store['accounts'] as $a) {
|
||||
if (($a['linkCode'] ?? '') !== $code) {
|
||||
continue;
|
||||
}
|
||||
if ($exceptKey !== null && ($a['providerUserId'] ?? '') === $exceptKey) {
|
||||
continue;
|
||||
}
|
||||
if (!code_is_expired($a['linkCodeLastUsedAt'] ?? null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function profile_payload(array $a): array
|
||||
{
|
||||
return [
|
||||
'playerKey' => $a['providerUserId'] ?? '',
|
||||
'coins' => max(0, (int)($a['coins'] ?? 0)),
|
||||
'colorThemeIndex' => ($v = (int)($a['lobbyColorThemeIndex'] ?? 0)) >= 1 && $v <= 8 ? $v : null,
|
||||
'skinToneIndex' => ($s = (int)($a['lobbySkinToneIndex'] ?? 0)) >= 1 && $s <= 3 ? $s : null,
|
||||
'displayName' => (string)($a['displayName'] ?? ''),
|
||||
'agentId' => (string)($a['agentDisplayId'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
|
||||
if ($method === 'GET') {
|
||||
$action = (string)($_GET['action'] ?? 'code');
|
||||
if ($action !== 'code') {
|
||||
json_response(['ok' => false, 'error' => 'unknown action'], 400);
|
||||
}
|
||||
$key = trim((string)($_GET['playerKey'] ?? ''));
|
||||
if (!valid_player_key($key)) {
|
||||
json_response(['ok' => false, 'error' => 'playerKey ต้องยาว 8–128 (a-z 0-9 _ -)'], 400);
|
||||
}
|
||||
$displayName = sanitize_display_name((string)($_GET['displayName'] ?? ''));
|
||||
$agentId = sanitize_agent_id((string)($_GET['agentId'] ?? ''));
|
||||
|
||||
$store = read_store();
|
||||
gc_expired_codes($store);
|
||||
$now = gmdate('c');
|
||||
$idx = -1;
|
||||
foreach ($store['accounts'] as $i => $a) {
|
||||
if (($a['loginType'] ?? '') === 'guest' && ($a['providerUserId'] ?? '') === $key) {
|
||||
$idx = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($idx < 0) {
|
||||
$store['accounts'][] = [
|
||||
'id' => new_id(),
|
||||
'email' => '',
|
||||
'displayName' => $displayName,
|
||||
'loginType' => 'guest',
|
||||
'providerUserId' => $key,
|
||||
'notes' => 'auto: player-link',
|
||||
'blocked' => false,
|
||||
'coins' => 0,
|
||||
'agentDisplayId' => $agentId,
|
||||
'createdAt' => $now,
|
||||
'updatedAt' => $now,
|
||||
];
|
||||
$idx = count($store['accounts']) - 1;
|
||||
} else {
|
||||
if ($displayName !== '') {
|
||||
$store['accounts'][$idx]['displayName'] = $displayName;
|
||||
}
|
||||
if ($agentId !== '') {
|
||||
$store['accounts'][$idx]['agentDisplayId'] = $agentId;
|
||||
}
|
||||
$store['accounts'][$idx]['updatedAt'] = $now;
|
||||
}
|
||||
|
||||
$code = (string)($store['accounts'][$idx]['linkCode'] ?? '');
|
||||
if ($code === '' || code_is_expired($store['accounts'][$idx]['linkCodeLastUsedAt'] ?? null)) {
|
||||
do {
|
||||
$code = generate_code();
|
||||
} while (code_in_use($store, $code, $key));
|
||||
$store['accounts'][$idx]['linkCode'] = $code;
|
||||
}
|
||||
$store['accounts'][$idx]['linkCodeLastUsedAt'] = $now;
|
||||
|
||||
if (!write_store($store)) {
|
||||
json_response(['ok' => false, 'error' => 'สร้างรหัสไม่สำเร็จ'], 500);
|
||||
}
|
||||
json_response(['ok' => true, 'code' => $code, 'codeDisplay' => format_code_display($code)]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$body = require_json_body();
|
||||
$action = (string)($body['action'] ?? 'redeem');
|
||||
if ($action !== 'redeem') {
|
||||
json_response(['ok' => false, 'error' => 'unknown action'], 400);
|
||||
}
|
||||
$code = normalize_code((string)($body['code'] ?? ''));
|
||||
if (!preg_match('/^[A-Z2-9]{' . LINK_CODE_LEN . '}$/', $code)) {
|
||||
json_response(['ok' => false, 'error' => 'รูปแบบรหัสไม่ถูกต้อง'], 400);
|
||||
}
|
||||
|
||||
$store = read_store();
|
||||
$sweep = gc_expired_codes($store);
|
||||
$now = gmdate('c');
|
||||
$found = -1;
|
||||
foreach ($store['accounts'] as $i => $a) {
|
||||
if (($a['linkCode'] ?? '') === $code && !code_is_expired($a['linkCodeLastUsedAt'] ?? null)) {
|
||||
$found = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($found < 0) {
|
||||
if ($sweep) {
|
||||
write_store($store);
|
||||
}
|
||||
json_response(['ok' => false, 'error' => 'ไม่พบรหัสนี้ หรือรหัสหมดอายุแล้ว'], 404);
|
||||
}
|
||||
|
||||
$store['accounts'][$found]['linkCodeLastUsedAt'] = $now;
|
||||
$store['accounts'][$found]['updatedAt'] = $now;
|
||||
write_store($store);
|
||||
|
||||
$payload = profile_payload($store['accounts'][$found]);
|
||||
$payload['ok'] = true;
|
||||
json_response($payload);
|
||||
}
|
||||
|
||||
json_response(['ok' => false, 'error' => 'Use GET or POST'], 405);
|
||||
Reference in New Issue
Block a user