Skip to content

Instantly share code, notes, and snippets.

@esterTion
Last active August 9, 2023 20:11
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save esterTion/c673a5e2547cd54c202f129babaf601d to your computer and use it in GitHub Desktop.
Save esterTion/c673a5e2547cd54c202f129babaf601d to your computer and use it in GitHub Desktop.
<?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);
}
}

WebSocket endpoint: wss://arc.estertion.win:616

WebSocket commands:

  • <UserCode> [constant from] [constant to] (e.g. 000007357 10 12)
    • UserCode must be 9 digits
    • constant from & constant to in range from 1 to 12
  • lookup <UserName> (e.g. lookup tairitsu)
    • UserName valid characters: a-z 0-9
    • case insensitive
  • constants
    • fetch current constants in database

WebSocket response:

  • pure string: contains following commands
    • queried
    • invalid id
    • bye
    • error <error_name>
  • array buffer: brotli compressed json, has property cmd and data
    • songtitle: title objects from songlist
    • userinfo: user info from friend list response
    • scores: fetched song scores
    • lookup_result: results for user lookup
    • constants: constants in database
@JoinChang
Copy link

JoinChang commented Jun 17, 2019

什么意思,自己写的客户端?

是,请问这种行为是不允许的吗?刚才试了一下浏览器也无法使用查询器了

@esterTion
Copy link
Author

esterTion commented Jun 17, 2019

啊,我脑残了
我把arc.estertion.win加了分区dns,忘了改这边的
wss://arc-src.estertion.win:616


网页的话刚更新了,刷新应该就可以

@JoinChang
Copy link

啊,我脑残了
我把arc.estertion.win加了分区dns,忘了改这边的
wss://arc-src.estertion.win:616

网页的话刚更新了,刷新应该就可以

好的,谢谢

@mathlover
Copy link

请问是否可以允许用机器人来进行使用您的网页的查询功能... 可以限制查询频率。

@esterTion
Copy link
Author

请问是否可以允许用机器人来进行使用您的网页的查询功能... 可以限制查询频率。

没问题,自己注意不要太快就行

@esterTion
Copy link
Author

啊,我脑残了
我把arc.estertion.win加了分区dns,忘了改这边的
wss://arc-src.estertion.win:616
网页的话刚更新了,刷新应该就可以

好的,谢谢

我在txy上面搭了端口转发,现在国内直接连arc.estertion.win:616的话应该更快

@Naoloco
Copy link

Naoloco commented Jun 30, 2019

您好,我是一位大三的資訊系學生

最近想在其他遊戲也做一個可供查詢的系統
請問這是透過後端php抓網站api獲取數據的方式嗎

據了解,達成這項功能是需要跟官方取得API的
在這方面想請教您的作法,例如:如果是使用api抓取資料的話,當初是如何跟616溝通獲得這份數據的呢
如果不是,那又是如何實現的呢
還望能得到您的幫助,感激不盡

@hyun17daily
Copy link

hyun17daily commented Aug 1, 2019

(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).

@esterTion
Copy link
Author

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).

Do you mean player name lookup?
This is done by crawling server data back in March this year, right before v2.0.0 released.
There was an api for arbitrary player info lookup, and after I (also some other cooperating devs) grabbed full info at the time, that api was removed in v2.0.0, so now this info is not that easy obtainable.

@esterTion
Copy link
Author

在這方面想請教您的作法,例如:如果是使用api抓取資料的話,當初是如何跟616溝通獲得這份數據的呢
如果不是,那又是如何實現的呢

无沟通,直接游戏抓包使用的
至今查分器依然不被官方承认(详见官方discord Announcement频道)

@Misaka12456
Copy link

Misaka12456 commented Aug 12, 2019

您好,请问您的网站提供直接查询的api吗(就是http get或者post传回来个json字符串;是您的网站的api,616不给api)
我主要是java,php稍微难一点的就看得我脑壳疼

@esterTion
Copy link
Author

您好,请问您的网站提供直接查询的api吗(就是http get或者post传回来个json字符串;是您的网站的api,616不给api)
我主要是java,php稍微难一点的就看得我脑壳疼

websocket命令词参考 https://redive.estertion.win/arcaea/probe/main.js 搜索.send

@Misaka12456
Copy link

J2SE(C/S模式)非常难弄WebSocket.......
我弄个J2SE的程序没必要上J2EE Web(B/S模式)吧……

@mathlover
Copy link

想查一个用户所有歌曲的成绩,就要一口气发送131首歌*3个难度=393个请求.... 我想问一下这种数据请求量,容易导致查分账号被封号么.... 对于616来说这个还是挺容易检测的

@esterTion
Copy link
Author

esterTion commented Aug 14, 2019

游戏api是有一个批量接口的,最早的时候只要不超过uri长度限制,塞多少个都可以,那会儿我塞了15个
后来我爬玩家信息的时候算是过度滥用了,现在这个批量接口超过6个子请求直接封号(上一个版本是5个,这次220更新在启动时多了一个world/me的请求,所以变成6个了)
至于频率上目前还没有给限制,反正这东西要封也是很容易的事,看lowiro怎么想了

@JoinChang
Copy link

请问获取recent的成绩一般是通过添加好友获取的吗?

@mathlover
Copy link

mathlover commented Aug 14, 2019

请问获取recent的成绩一般是通过添加好友获取的吗?

添加好友成功的response json就包括了好友的recent成绩, 当然如果已经是好友的话,可以直接getuserinfo请求获取recent成绩

@JoinChang
Copy link

所以一般都是通过这个方法来获取recent的是吧。随后还需要删除好友

@JoinChang
Copy link

那获取b30一般是怎么获取的,只有好友ID

@esterTion
Copy link
Author

网页上都写过了,定数获取到ptt-3,本地排序

@esterTion
Copy link
Author

esterTion commented Sep 8, 2019 via email

@Silver144
Copy link

你好,调用delete返回false和401的含义一般是什么意思呢,这时虽然返回false但还是删除成功了

@shainting
Copy link

您好,請問如果只要取得該玩家的ptt值的話,是否能夠只用api就可以了呢?因為我想要用google apps script然後寫到google sheets上面,但是google apps script本身還不支援websocket的部分

@Misaka12456
Copy link

Misaka12456 commented Mar 22, 2020 via email

@Jim-Luo
Copy link

Jim-Luo commented Sep 5, 2020

我把楼主的ws用python稍微封装了一下,不会的可以试试这个https://github.com/littlebutt/Arcapi

@Mqpzi
Copy link

Mqpzi commented Sep 21, 2020

同求可直接get或post的api接口()php真的不会用orz

@swssxy
Copy link

swssxy commented Mar 31, 2021

php真的看不懂,我打开人就直接傻了
同求可以直接get或post的api

Copy link

ghost commented Feb 9, 2022

请问为什么我发送_'384317410 8 12'_请求返回的一直是曲目列表和曲师列表?不知道怎么处理
我是在wss://arc-src.estertion.win:616 请求的
可以在chara_x@126.com联系我

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment