| 
          <?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); | 
        
        
           | 
            } | 
        
        
           | 
          } | 
        
  
我把楼主的ws用python稍微封装了一下,不会的可以试试这个https://github.com/littlebutt/Arcapi