Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<?php
chdir(__DIR__);
require_once __DIR__ . '/../webpthumb/Workerman-master/Autoloader.php';
use Workerman\Worker;
use Workerman\Protocols\Websocket;
use Workerman\Lib\Timer;
$known_code = new PDO('sqlite:arc-user.db');
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.0.0");
define("ARCAEA_APP_VER_FULL", "2.0.0.1");
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)
{
global $known_code;
Timer::del($connection->msgTimeout);
// fetch constants
if ($data === '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();
}
// player name lookup
if (substr($data, 0, 7) === 'lookup ') {
$stmt = $known_code->prepare('SELECT * FROM user_info WHERE lower(name)=?');
if ($stmt->execute([strtolower(substr($data, 7))])) {
$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
file_put_contents(__DIR__.'/arc.log', date('[m/d H:i:s] ').$connection->getRemoteIp()."\t".$data."\n", FILE_APPEND);
if (!preg_match('/^\d{9}$/', $data)) {
$connection->websocketType = Websocket::BINARY_TYPE_BLOB;
$connection->send('invalid id');
return $connection->close();
}
$connection->send('queried');
$connection->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER;
$songlist = json_decode(file_get_contents('songlist'), true);
$songTitleData = [];
foreach ($songlist['songs'] as &$song) {
$songTitleData[$song['id']] = $song['title_localized'];
}
$connection->send(brotli_compress(json_encode(['cmd'=>'songtitle', 'data'=>$songTitleData]), 5));
$userCode = $data;
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)) {
$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 = 0;
addAndFetchScore($userCode, $userId, getRandomToken(), function ($cmd, $data) use(&$offset, $ptts, $songmap, &$connection, $userCode, &$known_code) {
switch ($cmd) {
case 'find_user': {
// 有code-id对应关系,直接查找
if ($userId) {
foreach ($data['friends'] as $friend) {
if ($friend['user_id'] == $userId) {
return ['success'=>true, 'info'=>$friend];
}
}
return ['success'=>false, 'delete'=>array_map(function($i){return $i['user_id'];}, $data['friends'])];
}
$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, 'delete'=>array_map(function($i){return $i['user_id'];}, $data['friends'])];
}
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);
$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);
$userptt = ($data['rating']/100);
$connection->userptt = $userptt;
//echo 'Fetched user info: user ptt '.$userptt."\n";
foreach ($data['recent_score'] as &$item) {
$item['constant'] = $songmap[$item['song_id']][$item['difficulty']] ?: 0;
//$item['rating'] = getRating($item['constant'], $item['score']);
}
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]));
$connection->send(brotli_compress(json_encode(['cmd'=>'userinfo', 'data'=>$data]), 5));
return;
}
case 'pending_fetch': {
$pending = [];
for ($i = 0; $i < 5 && $offset + $i < count($ptts); $i++) {
if ($ptts[$offset + $i][0] < $connection->userptt - 3) {
$i++;
break;
}
$pending[] = [
'id' => $ptts[$offset + $i][1],
'difficulty' => $ptts[$offset + $i][2]
];
}
$offset += count($pending);
//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']);
}
//print_r($data);
//echo "fetched ".count($data)."\n";
//file_put_contents("test_".ceil($offset/15).".json", json_encode(['cmd'=>'scores', 'data' => $data]));
$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 addAndFetchScore($userCode, $userId, $authToken, $callback) {
// 添加
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'http://127.0.0.1:6160/5/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;
}
$addResult = json_decode($addResult, true);
$userInfo = call_user_func($callback, 'find_user', [
'ucode' => $userCode,
'uid' => $userId,
'friends' => $addResult['value']['friends']
]);
if (!$userInfo['success']) {
call_user_func($callback, 'error', 'add');
foreach ($userInfo['delete'] as $userId) {
curl_setopt_array($curl, [
CURLOPT_URL => 'http://127.0.0.1:6160/5/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(200e3);
}
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/5/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);
}
// 删除
curl_setopt_array($curl, [
CURLOPT_URL => 'http://127.0.0.1:6160/5/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='.$userInfo['user_id']
]);
$deleteResult = json_decode(curl_exec($curl), true);
call_user_func($callback, 'deleted', NULL);
curl_close($curl);
}
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.