|
<?php |
|
|
|
// https://gist.github.com/esterTion/c673a5e2547cd54c202f129babaf601d |
|
/* |
|
This code is now maintained by yojohanshinwataikei solely |
|
esterTion has retired from this project |
|
*/ |
|
|
|
chdir(__DIR__); |
|
require_once __DIR__ . '/../webpthumb/Workerman-master/Autoloader.php'; |
|
use Workerman\Worker; |
|
use Workerman\Protocols\Websocket; |
|
use Workerman\Lib\Timer; |
|
|
|
function execQuery($db, $query) { |
|
$returnVal = []; |
|
if (!$db) { |
|
//throw new Exception('Invalid db handle'); |
|
return false; |
|
} |
|
$result = $db->query($query); |
|
if ($result === false) { |
|
//throw new Exception('Failed executing query: '. $query); |
|
return []; |
|
} |
|
$returnVal = $result->fetchAll(PDO::FETCH_ASSOC); |
|
return $returnVal; |
|
} |
|
|
|
define("ARCAEA_APP_VER", "3.0.5"); |
|
define("ARCAEA_APP_VER_FULL", "3.0.5.0"); |
|
define('API_VER', '12'); |
|
define('SERVER_ENDPOINT', 'https://arcapi.lowiro.com/coffee/'); |
|
|
|
function getRandomToken() { |
|
$tokens = json_decode(file_get_contents('tokens.json'), true); |
|
return $tokens[ |
|
(time()+rand(0, 10000)) % count($tokens) |
|
]; |
|
} |
|
|
|
$context = array( |
|
'ssl' => array( |
|
'local_cert' => '/etc/stunnel/biliplus.crt', |
|
'local_pk' => '/etc/stunnel/biliplus.pem', |
|
'verify_peer' => false, |
|
'SNI_enabled' => true |
|
) |
|
); |
|
|
|
// Create a Websocket server |
|
$ws_worker = new Worker("websocket://0.0.0.0:616", $context); |
|
$ws_worker->transport = 'ssl'; |
|
|
|
// 2 processes |
|
$ws_worker->count = 4; |
|
|
|
// Emitted when new connection come |
|
$ws_worker->onConnect = function ($connection) { |
|
$connection->closed = false; |
|
$connection->msgTimeout = Timer::add(5, function()use($connection){ |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('timeout'); |
|
$connection->close(); |
|
}, null, false); |
|
$connection->onWebSocketConnect = function ($connection) { |
|
$connection->remote_ip = $connection->getRemoteIp(); |
|
}; |
|
}; |
|
|
|
// Emitted when data received |
|
$ws_worker->onMessage = function($connection, $data) |
|
{ |
|
Timer::del($connection->msgTimeout); |
|
|
|
// fetch constants |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ').$connection->remote_ip."\t".$data."\n", FILE_APPEND); |
|
$cmd = explode(' ', $data); |
|
if (empty($cmd)) { |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('invalid cmd'); |
|
return $connection->close(); |
|
} |
|
if ($cmd[0] === 'constants') { |
|
$songlist = json_decode(file_get_contents('songlist'), true); |
|
$songTitleData = []; |
|
foreach ($songlist['songs'] as &$song) { |
|
$songTitleData[$song['id']] = $song['title_localized']; |
|
} |
|
$connection->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER; |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'songtitle', 'data'=>$songTitleData]), 5)); |
|
|
|
require '/data/home/web/task/mysql.php'; |
|
$mysqli->select_db('arc'); |
|
$select = $mysqli->query('SELECT id,difficulty,constant FROM chart_constants'); |
|
$songmap = []; |
|
while ( ($row = $select->fetch_array(MYSQLI_ASSOC)) !== NULL ) { |
|
if(!isset($songmap[$row['id']])) $songmap[$row['id']] = []; |
|
$songmap[$row['id']][$row['difficulty']] = $row['constant']; |
|
} |
|
$mysqli->close(); |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'constants', 'data'=>$songmap]), 5)); |
|
|
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('bye'); |
|
return $connection->close(); |
|
} |
|
|
|
$known_code = new PDO('sqlite:arc-user.db'); |
|
$known_code->query('PRAGMA synchronous=3'); |
|
// player name lookup |
|
if ($cmd[0] === 'lookup' && count($cmd) >= 2) { |
|
if (!preg_match('/^[a-zA-Z0-9]+$/', $cmd[1])) { |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('invalid id'); |
|
return $connection->close(); |
|
} |
|
$stmt = $known_code->prepare('SELECT * FROM user_info WHERE lower(name)=?'); |
|
if ($stmt->execute([strtolower($cmd[1])])) { |
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC); |
|
$connection->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER; |
|
foreach($result as &$row) $row['data_time'] = date('Y/m/d', $row['data_time']); |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'lookup_result', 'data'=>$result]), 5)); |
|
} |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('bye'); |
|
return $connection->close(); |
|
} |
|
|
|
// player score probe |
|
if (!preg_match('/^\d{9}$/', $cmd[0])) { |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('invalid id'); |
|
return $connection->close(); |
|
} |
|
$connection->send('queried'); |
|
$connection->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER; |
|
$connection->constFrom = 0; |
|
$connection->constTo = 12; |
|
if (count($cmd) == 3) { |
|
$connection->constFrom = $cmd[1] |0; |
|
$connection->constTo = $cmd[2] |0; |
|
} |
|
|
|
$songlist = json_decode(file_get_contents('songlist'), true); |
|
$songTitleData = []; |
|
$songDateData = []; |
|
foreach ($songlist['songs'] as &$song) { |
|
$songTitleData[$song['id']] = $song['title_localized']; |
|
$songDateData[$song['id']] = $song['date']; |
|
} |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'songtitle', 'data'=>$songTitleData]), 5)); |
|
|
|
$userCode = $cmd[0]; |
|
if ($userCode === '000007357') { |
|
$connection->send(brotli_compress(file_get_contents("test_user.json"), 5)); |
|
$i = 1; |
|
while (file_exists("test_$i.json")) { |
|
usleep(100000); |
|
$connection->send(brotli_compress(file_get_contents("test_$i.json"), 5)); |
|
$i++; |
|
} |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('bye'); |
|
return $connection->close(); |
|
} |
|
|
|
if ($userCode === '000000001' || $userCode === '000000002') { |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'userinfo', 'data'=>[ |
|
'join_date'=>1489517588494, |
|
'name' => ['', 'Hikari', 'Tairitsu'][(int)$userCode], |
|
'rating' => 616, |
|
'recent_score'=>[], |
|
'user_code'=>$userCode, |
|
'user_id'=>1000000 + $userCode |
|
]]), 5)); |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('bye'); |
|
return $connection->close(); |
|
} |
|
|
|
$findId = execQuery($known_code, 'SELECT id FROM known_code WHERE code="'.$userCode.'"'); |
|
if (empty($findId) || empty($findId [0])) { |
|
$userId = 0; |
|
} else { |
|
$userId = $findId[0]['id']; |
|
} |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ').$connection->remote_ip."\t".$userCode."\t".$userId."\n", FILE_APPEND); |
|
require '/data/home/web/task/mysql.php'; |
|
$mysqli->select_db('arc'); |
|
$select = $mysqli->query('SELECT id,difficulty,constant FROM chart_constants'); |
|
$ptts = []; |
|
$songmap = []; |
|
while ( ($row = $select->fetch_array(MYSQLI_ASSOC)) !== NULL ) { |
|
$ptts[] = [floatval($row['constant']), $row['id'], $row['difficulty']]; |
|
if(!isset($songmap[$row['id']])) $songmap[$row['id']] = []; |
|
$songmap[$row['id']][$row['difficulty']] = $row['constant']; |
|
} |
|
$mysqli->close(); |
|
usort($ptts, function ($a, $b){return $b[0] > $a[0] ? 1 : -1;} ); |
|
$offset = count($ptts); |
|
for ($i = 0; $i<count($ptts); $i++) { |
|
if ($ptts[$i][0] < $connection->constTo) { |
|
$offset = $i; |
|
break; |
|
} |
|
} |
|
|
|
addAndFetchScore($userCode, $userId, getRandomToken(), function ($cmd, $data) use(&$offset, $ptts, $songmap, &$connection, $userId, $userCode, &$known_code, $songDateData) { |
|
switch ($cmd) { |
|
case 'find_user': { |
|
// 有code-id对应关系,直接查找 |
|
if ($userId) { |
|
foreach ($data['friends'] as $friend) { |
|
if ($friend['user_id'] == $userId) { |
|
return ['success'=>true, 'info'=>$friend]; |
|
} |
|
} |
|
$known_code->prepare('DELETE FROM known_code WHERE code=?')->execute([$userCode]); |
|
} |
|
$insertStmt = $known_code->prepare('INSERT OR IGNORE INTO known_code (code,id) VALUES (?,?)'); |
|
// 只有一个好友,直接记录并返回 |
|
if (count($data['friends']) == 1) { |
|
$insertStmt->execute([ |
|
$data['ucode'], |
|
$data['friends'][0]['user_id'] |
|
]); |
|
return ['success'=>true, 'info'=>$data['friends'][0]]; |
|
} |
|
// 遍历好友列表,查找对应关系 |
|
$testStmt = $known_code->prepare('SELECT code FROM known_code WHERE id=?'); |
|
$noRecord = []; |
|
foreach ($data['friends'] as $friend) { |
|
$testStmt->execute([$friend['user_id']]); |
|
$row = $testStmt->fetch(); |
|
if (empty($row)) { |
|
$noRecord[] = $friend; |
|
} |
|
} |
|
// 只有一个好友未记录,记录并返回 |
|
if (count($noRecord) == 1) { |
|
$insertStmt->execute([ |
|
$data['ucode'], |
|
$noRecord[0]['user_id'] |
|
]); |
|
return ['success'=>true, 'info'=>$noRecord[0]]; |
|
} |
|
// 未匹配时清空好友 |
|
return ['success'=>false]; |
|
} |
|
case 'error': { |
|
//echo "fail $data\n"; |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('error,'.$data); |
|
$connection->close(); |
|
$connection->closed = true; |
|
return; |
|
} |
|
case 'userinfo': { |
|
//print_r($data); |
|
$known_code->beginTransaction(); |
|
if ($data['rating'] > 0) { |
|
$execData = [ |
|
$data['join_date'], |
|
$data['name'], |
|
$data['rating'], |
|
$userCode, |
|
time(), |
|
$data['user_id'] |
|
]; |
|
$known_code->prepare('INSERT OR IGNORE INTO user_info (join_date,name,rating,code,data_time,id) VALUES (?,?,?,?,?,?)')->execute($execData); |
|
$known_code->prepare('UPDATE user_info SET join_date=?,name=?,rating=?,code=?,data_time=? WHERE id=?')->execute($execData); |
|
$known_code->prepare('INSERT OR IGNORE INTO rating_record (id,date,rating) VALUES (?,?,?)')->execute([$data['user_id'], date('ymd'), $data['rating']]); |
|
} else { |
|
if ($connection->constFrom === 0) $connection->constFrom = 12; |
|
$execData = [ |
|
$data['join_date'], |
|
$data['name'], |
|
$userCode, |
|
$data['user_id'] |
|
]; |
|
$known_code->prepare('INSERT OR IGNORE INTO user_info (join_date,name,code,id) VALUES (?,?,?,?)')->execute($execData); |
|
$known_code->prepare('UPDATE user_info SET join_date=?,name=?,code=? WHERE id=?')->execute($execData); |
|
} |
|
$known_code->commit(); |
|
$userptt = ($data['rating']/100); |
|
$connection->userptt = $userptt; |
|
if ($connection->constFrom === 0) $connection->constFrom = $userptt - 3; |
|
$ratings = []; |
|
//echo 'Fetched user info: user ptt '.$userptt."\n"; |
|
if (!empty($data['recent_score'])) foreach ($data['recent_score'] as &$item) { |
|
if (!isset($item['rating'])) continue; |
|
$item['constant'] = get_constant_from_score_and_rating($item['score'], $item['rating']); |
|
$item['song_date'] = $songDateData[$item['song_id']]; |
|
if ($item['rating'] > 0) { |
|
$ratings[] = '('.implode(',', [ |
|
'"'.$item['song_id'].'"', |
|
$item['difficulty'], |
|
get_constant_from_score_and_rating($item['score'], $item['rating']), |
|
$item['score'], |
|
$item['rating'] |
|
]).')'; |
|
} |
|
} |
|
$rating_records = $known_code->prepare('SELECT date,rating FROM rating_record WHERE id=?'); |
|
$rating_records->execute([$data['user_id']]); |
|
$rating_records = $rating_records->fetchAll(PDO::FETCH_NUM); |
|
$data['rating_records'] = $rating_records; |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ').$connection->remote_ip."\t".$userCode.' --> '.number_format($data['rating']/100, 2).' '.$data['name']."\n", FILE_APPEND); |
|
//file_put_contents("test_user.json", json_encode(['cmd'=>'userinfo', 'data'=>$data])); |
|
$data['user_code'] = $userCode; |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'userinfo', 'data'=>$data]), 5)); |
|
if (!empty($ratings)) { |
|
require '/data/home/web/task/mysql.php'; |
|
$mysqli->select_db('arc'); |
|
// note:mysql use insert ignore, but sqlite use insert or ignore |
|
$mysqli->query('INSERT IGNORE INTO chart_constants (id,difficulty,constant,source_score,source_rating) VALUES '.implode(',', $ratings)); |
|
$mysqli->close(); |
|
} |
|
if ($data['rating'] <= 0 && $connection->constFrom == 12) { |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('error,potential_hidden'); |
|
} |
|
return; |
|
} |
|
case 'pending_fetch': { |
|
$pending = []; |
|
for ($i = 0; $i < 6 && $offset + $i < count($ptts); $i++) { |
|
if ($ptts[$offset + $i][0] < $connection->constFrom) { |
|
$i++; |
|
break; |
|
} |
|
$pending[] = [ |
|
'id' => $ptts[$offset + $i][1], |
|
'difficulty' => $ptts[$offset + $i][2] |
|
]; |
|
} |
|
$offset += $i; |
|
//echo "fetch page request: $offset ".count($pending)."\n"; |
|
return $pending; |
|
} |
|
case 'fetched_data': { |
|
if (empty($data)) return; |
|
foreach ($data as &$item) { |
|
$item['constant'] = $songmap[$item['song_id']][$item['difficulty']] ?: 0; |
|
$item['rating'] = getRating($item['constant'], $item['score']); |
|
$item['song_date'] = $songDateData[$item['song_id']]; |
|
} |
|
//print_r($data); |
|
//echo "fetched ".count($data)."\n"; |
|
//file_put_contents("test_".ceil($offset/15).".json", json_encode(['cmd'=>'scores', 'data' => $data])); |
|
$connection->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER; |
|
$connection->send(brotli_compress(json_encode(['cmd'=>'scores', 'data' => $data]), 5)); |
|
return; |
|
} |
|
case 'deleted': { |
|
if ($connection->closed) return; |
|
$connection->websocketType = Websocket::BINARY_TYPE_BLOB; |
|
$connection->send('bye'); |
|
return $connection->close(); |
|
} |
|
} |
|
}); |
|
}; |
|
|
|
// Emitted when connection closed |
|
/*$ws_worker->onClose = function ($connection) { |
|
};*/ |
|
|
|
// Run worker |
|
Worker::runAll(); |
|
|
|
|
|
|
|
function getRating(float $constant, int $score) { |
|
if ($score > 10000000) return $constant + 2.0; |
|
else if ($score > 9800000) return $constant + 1.0 + ($score - 9800000) / 200000; |
|
else return max($constant + ($score - 9500000) / 300000, 0); |
|
} |
|
function get_constant_from_score_and_rating(int $score, float $rating) { |
|
$return = 0; |
|
if ($score > 10e6) { |
|
$return = $rating - 2; |
|
} else if ($score > 9.8e6) { |
|
$return = $rating - 1.0 - ($score - 9.8e6) / 0.2e6; |
|
} else if ($rating > 0) { |
|
$return = $rating - ($score - 9.5e6) / 0.3e6; |
|
} |
|
return round($return * 100) / 100; |
|
} |
|
|
|
function addAndFetchScore($userCode, $userId, $authToken, $callback) { |
|
// 添加 |
|
$curl = curl_init(); |
|
$headers = ""; |
|
curl_setopt_array($curl, [ |
|
CURLOPT_URL => SERVER_ENDPOINT.API_VER.'/friend/me/add', |
|
CURLOPT_SSL_VERIFYPEER => false, |
|
CURLOPT_RETURNTRANSFER => true, |
|
CURLOPT_HEADER => FALSE, |
|
CURLOPT_HTTPHEADER => [ |
|
'Content-Type: application/x-www-form-urlencoded; charset=utf-8', |
|
'User-Agent: Arc-mobile/'.ARCAEA_APP_VER_FULL.' CFNetwork/758.3.15 Darwin/15.4.0', |
|
'Accept: */*', |
|
'Accept-Language: en-us', |
|
'Authorization: Bearer '.$authToken, |
|
'AppVersion: '.ARCAEA_APP_VER, |
|
], |
|
CURLOPT_ENCODING => 'gzip, deflate', |
|
CURLOPT_TIMEOUT=>5, |
|
CURLOPT_POST => true, |
|
CURLOPT_POSTFIELDS => 'friend_code='.$userCode, |
|
CURLOPT_HEADERFUNCTION => function($curl, $header) use (&$headers) |
|
{ |
|
$headers=$headers."\n".$header; |
|
return strlen($header); |
|
} |
|
]); |
|
$addResult = curl_exec($curl); |
|
$curl_errno=curl_errno($curl); |
|
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE); |
|
if ($status === 401 || $status === 403) { |
|
curl_close($curl); |
|
$addResult = json_decode($addResult, true); |
|
if ($addResult['error_code']===5){ |
|
call_user_func($callback, 'error', 'Please update arcaea'); |
|
}else if ($addResult['error_code']===603){ |
|
call_user_func($callback, 'error', 'account is locked'); |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ')."[locked]token:".$authToken."\n", FILE_APPEND); |
|
}else{ |
|
call_user_func($callback, 'error', 'account cannot login'); |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ')."[cannot login]token:".$authToken."\n", FILE_APPEND); |
|
} |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ')."[add error]status:".$status."\nresult:".$addResult."\nheaders:".$headers."\ncurl_errno:".$curl_errno." ".curl_strerror($curl_errno)."\n", FILE_APPEND); |
|
return; |
|
} |
|
if ($status !== 200) { |
|
curl_close($curl); |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ')."[add error]status:".$status."\nresult:".$addResult."\nheaders:".$headers."\ncurl_errno:".$curl_errno." ".curl_strerror($curl_errno)."\n", FILE_APPEND); |
|
call_user_func($callback, 'error', 'add'); |
|
return; |
|
} |
|
usleep(5e4); |
|
$addResult = json_decode($addResult, true); |
|
$userInfo = call_user_func($callback, 'find_user', [ |
|
'ucode' => $userCode, |
|
'uid' => $userId, |
|
'friends' => $addResult['value']['friends'] |
|
]); |
|
$deleteList = array_map(function($i){return $i['user_id'];}, $addResult['value']['friends']); |
|
if (!$userInfo['success']) { |
|
call_user_func($callback, 'error', 'add'); |
|
deleteFriends($deleteList, $authToken, $curl); |
|
curl_close($curl); |
|
return; |
|
} |
|
$userInfo = $userInfo['info']; |
|
call_user_func($callback, 'userinfo', $userInfo); |
|
|
|
// 获取 |
|
while (1) { |
|
$pending = call_user_func($callback, 'pending_fetch', NULL); |
|
if (empty($pending)) break; |
|
|
|
$calls = []; |
|
$responseMap = []; |
|
$id = 0; |
|
foreach ($pending as $item) { |
|
$responseMap[$id] = $item; |
|
$calls[] = [ |
|
'endpoint' => 'score/song/friend?song_id='.$item['id'].'&difficulty='.$item['difficulty'], |
|
'id' => $id++ |
|
]; |
|
} |
|
$calls = urlencode(json_encode($calls, JSON_UNESCAPED_SLASHES)); |
|
$headers = ""; |
|
curl_setopt_array($curl, [ |
|
CURLOPT_POSTFIELDS => '', |
|
CURLOPT_POST=> false, |
|
CURLOPT_HTTPHEADER => [ |
|
'Content-Type: application/x-www-form-urlencoded; charset=utf-8', |
|
'User-Agent: Arc-mobile/'.ARCAEA_APP_VER_FULL.' CFNetwork/758.3.15 Darwin/15.4.0', |
|
'Accept: */*', |
|
'Accept-Language: en-us', |
|
'Authorization: Bearer '.$authToken, |
|
'AppVersion: '.ARCAEA_APP_VER, |
|
], |
|
CURLOPT_URL => SERVER_ENDPOINT.API_VER.'/compose/aggregate?calls='.$calls, |
|
CURLOPT_HEADERFUNCTION => function($curl, $header) use (&$headers) |
|
{ |
|
$headers=$headers."\n".$header; |
|
return strlen($header); |
|
} |
|
]); |
|
$fetchResult = curl_exec($curl); |
|
$curl_errno=curl_errno($curl); |
|
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE); |
|
if ($status !== 200) { |
|
curl_close($curl); |
|
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ')."[fetch error]status:".$status."\nresult:".$fetchResult."\nheaders:".$headers."\ncurl_errno:".$curl_errno." ".curl_strerror($curl_errno)."\n", FILE_APPEND); |
|
call_user_func($callback, 'error', 'fetch'); |
|
break; |
|
} |
|
$fetchResult = json_decode($fetchResult, true); |
|
$returnVal = []; |
|
foreach ($responseMap as $id => $item) { |
|
foreach ($fetchResult['value'][$id]['value'] as $score) { |
|
if ($score['user_id'] === $userInfo['user_id']) { |
|
$returnVal[] = $score; |
|
} // if |
|
} // score |
|
} // item |
|
call_user_func($callback, 'fetched_data', $returnVal); |
|
usleep(5e4); |
|
} |
|
|
|
// 删除 |
|
deleteFriends($deleteList, $authToken, $curl); |
|
call_user_func($callback, 'deleted', NULL); |
|
curl_close($curl); |
|
} |
|
|
|
function deleteFriends($list, $authToken, $curl) { |
|
foreach ($list as $userId) { |
|
curl_setopt_array($curl, [ |
|
CURLOPT_URL => SERVER_ENDPOINT.API_VER.'/friend/me/delete', |
|
CURLOPT_HTTPHEADER => [ |
|
'Content-Type: application/x-www-form-urlencoded; charset=utf-8', |
|
'User-Agent: Arc-mobile/'.ARCAEA_APP_VER_FULL.' CFNetwork/758.3.15 Darwin/15.4.0', |
|
'Accept: */*', |
|
'Accept-Language: en-us', |
|
'Authorization: Bearer '.$authToken, |
|
'AppVersion: '.ARCAEA_APP_VER, |
|
], |
|
CURLOPT_POST => true, |
|
CURLOPT_POSTFIELDS => 'friend_id='.$userId |
|
]); |
|
curl_exec($curl); |
|
usleep(5e4); |
|
} |
|
} |
(Not related to research or development)
I have a simple question, did the Arcaea player's information leak for a while? It was a moment, but I was able to check the ID and the rank of the random player (that did not show any results before).