Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<?php
// https://gist.github.com/esterTion/c673a5e2547cd54c202f129babaf601d
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", "2.2.0");
define("ARCAEA_APP_VER_FULL", "2.2.0.0");
define('API_VER', '6');
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://[::]:616", $context);
$ws_worker->transport = 'ssl';
// 2 processes
$ws_worker->count = 2;
// 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);
};
// 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->getRemoteIp()."\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->getRemoteIp()."\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";
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->getRemoteIp()."\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');
$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 > 9950000) return $constant + 1.5 + ($score - 9950000) / 100000;
else if ($score > 9800000) return $constant + 1.0 + ($score - 9800000) / 400000;
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.95e6) {
$return = $rating - 1.5 - ($score - 9.95e6) / 0.3e6 * 3.0;
} else if ($score > 9.8e6) {
$return = $rating - 1.0 - ($score - 9.8e6) / 0.3e6 * 0.75;
} 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();
curl_setopt_array($curl, [
CURLOPT_URL => 'http://127.0.0.1:6160/'.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
]);
$addResult = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status === 401) {
curl_close($curl);
call_user_func($callback, 'error', 'account is locked');
return;
}
if ($status !== 200) {
curl_close($curl);
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));
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 => 'http://127.0.0.1:6160/'.API_VER.'/compose/aggregate?calls='.$calls
]);
$fetchResult = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status !== 200) {
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 => 'http://127.0.0.1:6160/'.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

This comment has been minimized.

Copy link

JoinChang commented Jun 17, 2019

你好,为什么我这边无法查询 API,websocket返回 (Opcode -1)

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Jun 17, 2019

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

@JoinChang

This comment has been minimized.

Copy link

JoinChang commented Jun 17, 2019

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

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Jun 17, 2019

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


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

@JoinChang

This comment has been minimized.

Copy link

JoinChang commented Jun 17, 2019

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

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

好的,谢谢

@mathlover

This comment has been minimized.

Copy link

mathlover commented Jun 18, 2019

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Jun 18, 2019

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

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Jun 18, 2019

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

好的,谢谢

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

@Naoloco

This comment has been minimized.

Copy link

Naoloco commented Jun 30, 2019

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

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

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

@hyun17daily

This comment has been minimized.

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

This comment has been minimized.

Copy link
Owner Author

esterTion commented Aug 1, 2019

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

This comment has been minimized.

Copy link
Owner Author

esterTion commented Aug 1, 2019

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

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

@Misaka12456

This comment has been minimized.

Copy link

Misaka12456 commented Aug 12, 2019

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Aug 12, 2019

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

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

@Misaka12456

This comment has been minimized.

Copy link

Misaka12456 commented Aug 12, 2019

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

@mathlover

This comment has been minimized.

Copy link

mathlover commented Aug 14, 2019

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Aug 14, 2019

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

@JoinChang

This comment has been minimized.

Copy link

JoinChang commented Aug 14, 2019

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

@mathlover

This comment has been minimized.

Copy link

mathlover commented Aug 14, 2019

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

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

@JoinChang

This comment has been minimized.

Copy link

JoinChang commented Aug 14, 2019

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

@JoinChang

This comment has been minimized.

Copy link

JoinChang commented Aug 14, 2019

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Aug 16, 2019

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

@esterTion

This comment has been minimized.

Copy link
Owner Author

esterTion commented Sep 8, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.