diff --git a/src/plugin/Barrage.php b/src/plugin/Barrage.php index dd8d674..da1e460 100644 --- a/src/plugin/Barrage.php +++ b/src/plugin/Barrage.php @@ -134,13 +134,13 @@ class Barrage */ private static function privateSendMsg($info): bool { - //TODO 短期功能 有需求就修改 + //TODO 短期功能 有需求就修改 // todo 加入直播间 $response = self::sendMsg($info); if (isset($response['code']) && $response['code'] == 0) { Log::info('弹幕发送成功'); return true; } else { - Log::warning("弹幕发送失败, CODE -> {$response['code']} MSG -> {$response['msg']} "); + Log::warning("弹幕发送失败, CODE -> {$response['code']} MSG -> {$response} "); return false; } } diff --git a/src/tool/Common.php b/src/tool/Common.php index 0738cd4..b8566fa 100644 --- a/src/tool/Common.php +++ b/src/tool/Common.php @@ -12,6 +12,18 @@ namespace BiliHelper\Tool; class Common { + + /** + * @use 获取十三位时间戳 + * @return int + */ + public static function getMillisecond(): int + { + list($t1, $t2) = explode(' ', microtime()); + // return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); + return intval(sprintf('%u', (floatval($t1) + floatval($t2)) * 1000)); + } + /** * @use 替换字符串 * @param $str @@ -21,7 +33,7 @@ class Common * @param string $charset * @return string */ - public static function replaceStar($str, $start, $end = 0, $dot = "*", $charset = "UTF-8") + public static function replaceStar($str, $start, $end = 0, $dot = "*", $charset = "UTF-8"): string { $len = mb_strlen($str, $charset); if ($start == 0 || $start > $len) { diff --git a/src/util/XliveHeartBeat.php b/src/util/XliveHeartBeat.php index 06e5075..0c5cf3d 100644 --- a/src/util/XliveHeartBeat.php +++ b/src/util/XliveHeartBeat.php @@ -32,27 +32,14 @@ trait XliveHeartBeat protected static $_default = 0; // 默认值 - - /** - * @use 重置变量 - * @param false $force - */ - protected static function resetVar($force = false) - { - if ($force) { - static::$_room_info = []; - static::$_current_room_id = 0; - - static::$_retry = 3; - static::$_count_num = 0; - static::$_count_time = 0; - } - $data = [ - 'id' => static::$_data['id'], - ]; - $data["id"][2] = 0; - static::$_data = $data; - } + // 请求配置 + protected static $_user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36'; + protected static $_headers = [ + 'content-type' => 'application/x-www-form-urlencoded', + 'origin' => 'https://live.bilibili.com', + 'referer' => 'https://live.bilibili.com/', + 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36' + ]; /** @@ -92,28 +79,214 @@ trait XliveHeartBeat $r_data = static::heartBeatIterator(); $index = static::$_data['id'][2]; if ($r_data['code'] != 0) { + Log::warning("心跳失败-$index {$r_data['message']}"); + // 失败心跳 if (static::$_retry) { - Log::warning("心跳失败-$index {$r_data['message']}"); + // 重试次数 > 1 , 不全部清除 static::resetVar(); static::$_retry -= 1; - return static::$_default; + } else { + // 重试次数 < 1 , 全部清除 + static::resetVar(true); } - } - static::$_count_num += 1; - static::$_count_time += $r_data['heartbeat_interval']; + return static::$_default; + } else { + // 成功心跳 + static::$_count_num += 1; + static::$_count_time += $r_data['heartbeat_interval']; - // 最大次数限制 - if ($max_num <= static::$_count_num) { - // 成功在id为{room_id}的直播间发送完{ii}次心跳,退出直播心跳(达到最大心跳次数) + // 最大次数限制 + if ($max_num <= static::$_count_num) { + // 成功在id为{room_id}的直播间发送完{ii}次心跳,退出直播心跳(达到最大心跳次数) + } + // 最大时间限制 + if ($max_time <= static::$_count_time) { + //成功在id为{room_id}的直播间发送第{ii}次心跳 + } + $minute = round(static::$_count_time / 60); + Log::info("已在直播间 $room_id 连续观看了 $minute 分钟"); + return $r_data['heartbeat_interval']; } - // 最大时间限制 - if ($max_time <= static::$_count_time) { - //成功在id为{room_id}的直播间发送第{ii}次心跳 - } - $minute = round(static::$_count_time / 60); - Log::info("已在直播间 $room_id 连续观看了 $minute 分钟"); - return $r_data['heartbeat_interval']; + } + /** + * @use 心跳迭代 + * @return array + */ + protected static function heartBeatIterator(): array + { +// print_r(static::$_data); + $rdata = []; + # 第1次执行 eHeartBeat + if (static::$_data['id'][2] == 0) { + $r_data = static::eHeartBeat(static::$_data['id']); + } else { + # 第1次之后执行 xHeartBeat + static::$_data['ts'] = time() * 1000; + static::$_data['s'] = static::encParamS(static::$_data, static::$_secret_rule); + if (!static::$_data['s']) { + return [404, '心跳加密错误', '心跳加密错误']; + } + $r_data = static::xHeartBeat(static::$_data['id']); + } + if ($r_data['code'] == 0) { + $rdata = $r_data['data']; + static::$_data['ets'] = $rdata['timestamp']; + static::$_data['benchmark'] = $rdata['secret_key']; + static::$_data['time'] = $rdata['heartbeat_interval']; + static::$_secret_rule = $rdata['secret_rule']; + static::$_data['id'][2] += 1; + } + Log::debug(json_encode(static::$_data['id'], true)); + return [ + 'code' => $r_data['code'], + 'message' => $r_data['message'], + 'heartbeat_interval' => array_key_exists('heartbeat_interval', $rdata) ? $rdata['heartbeat_interval'] : static::$_default + ]; + } + + /** + * @use E心跳 + * @param array $id + * @return array|false[] + */ + protected static function eHeartBeat(array $id): array + { + $url = 'https://live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/E'; + $user_info = User::parseCookies(); + $payload = [ + 'id' => json_encode([$id[0], $id[1], $id[2], $id[3]], true), + 'device' => json_encode([Generator::hash(), Generator::uuid4()], true), + 'ts' => time() * 1000, + 'is_patch' => 0, + 'heart_beat' => [], + 'ua' => static::$_user_agent, + 'csrf_token' => $user_info['token'], + 'csrf' => $user_info['token'], + 'visit_id' => '' + ]; + // print_r($payload); + Log::debug(json_encode($payload, true)); + $raw = Curl::post('pc', $url, $payload, static::$_headers); + // {'code':0,'message':'0','ttl':1,'data':{'timestamp':1595342828,'heartbeat_interval':300,'secret_key':'seacasdgyijfhofiuxoannn','secret_rule':[2,5,1,4],'patch_status':2}} + + unset($payload['id']); + static::$_data = array_merge_recursive(static::$_data, $payload); + + return json_decode($raw, true); + } + + /** + * @use X心跳 + * @param array $id + * @return array|bool[] + */ + protected static function xHeartBeat(array $id): array + { + $url = 'https://live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/X'; + $payload = [ + 's' => static::$_data['s'], + 'id' => json_encode([$id[0], $id[1], $id[2], $id[3]], true), + 'device' => static::$_data['device'], + 'ets' => static::$_data['ets'], + 'benchmark' => static::$_data['benchmark'], + 'time' => static::$_data['time'], + 'ts' => static::$_data['ts'], + 'ua' => static::$_data['ua'], + 'csrf_token' => static::$_data['csrf_token'], + 'csrf' => static::$_data['csrf'], + 'visit_id' => '' + ]; +// print_r($payload); + Log::debug(json_encode($payload, true)); + $raw = Curl::post('pc', $url, $payload, static::$_headers); + # {"code":0,"message":"0","ttl":1,"data":{"heartbeat_interval":60,"timestamp":1619419450,"secret_rule":[2,5,1,4],"secret_key":"seacasdgyijfhofiuxoannn"}} + # {'code':0,'message':'0','ttl':1,'data':{'heartbeat_interval':300,'timestamp':1595346846,'secret_rule':[2,5,1,4],'secret_key':'seacasdgyijfhofiuxoannn'}} + return json_decode($raw, true); + } + + /** + * @use 加密参数S + * @param array $t + * @param array $r + * @return string|false + */ + protected static function encParamS(array $t, array $r) + { + $headers = [ + 'Content-Type' => 'application/json', + ]; + // 加密部分 + $payload = [ + 't' => static::formatT($t), + 'r' => static::formatR($r) + ]; +// print_r($payload); + $data = Curl::put('other', static::$_enc_server, $payload, $headers); + $de_raw = json_decode($data, true); + if ($de_raw['code'] == 0) { + if (array_key_exists('s', $de_raw)) { + // Log::info("S加密成功 {$de_raw['s']}"); + return $de_raw['s']; + } + Log::warning("S加密失败 加密服务器暂时错误,请检查更换"); + } else { + Log::warning("S加密失败 {$de_raw['message']}"); + } + return false; + } + + + /** + * @use 格式T + * @param array $t + * @return array + */ + protected static function formatT(array $t): array + { +// print_r($t); + return [ + 'id' => $t['id'], + 'device' => $t['device'], + 'ets' => $t['ets'], + 'benchmark' => $t['benchmark'], + 'time' => $t['time'], + 'ts' => $t['ts'], + 'ua' => $t['ua'], + ]; + } + + /** + * @use 格式R + * @param array $r + * @return array + */ + protected static function formatR(array $r): array + { + return $r; + } + + /** + * @use 重置变量 + * @param false $force + */ + protected static function resetVar($force = false) + { + if ($force) { + static::$_room_info = []; + static::$_current_room_id = 0; + + static::$_retry = 3; + static::$_count_num = 0; + static::$_count_time = 0; + } + static::$_data = null; + static::$_data = ['id' => []]; + $data = [ + 'id' => static::$_data['id'], + ]; + $data["id"][2] = 0; + static::$_data = $data; } /** @@ -132,133 +305,4 @@ trait XliveHeartBeat return true; } - - /** - * @use 心跳迭代 - * @return array - */ - protected static function heartBeatIterator(): array - { - $rdata = []; - # 第1次执行 eHeartBeat - if (static::$_data['id'][2] == 0) { - $r_data = static::eHeartBeat(static::$_data['id']); - } else { - # 第1次之后执行 xHeartBeat - static::$_data['ts'] = time() * 1000; - static::$_data['s'] = static::encParamS(static::$_data, static::$_secret_rule); - $r_data = static::xHeartBeat(static::$_data['id']); - } - if ($r_data['code'] == 0) { - $rdata = $r_data['data']; - static::$_data['ets'] = $rdata['timestamp']; - static::$_data['benchmark'] = $rdata['secret_key']; - static::$_data['time'] = $rdata['heartbeat_interval']; - static::$_secret_rule = $rdata['secret_rule']; - static::$_data['id'][2] += 1; - } - Log::debug(json_encode(static::$_data['id'], true)); - return [ - 'code' => $r_data['code'], - 'message' => $r_data['message'], - 'heartbeat_interval' => $rdata['heartbeat_interval'] - ]; - } - - - /** - * @use E心跳 - * @param array $id - * @return array|false[] - */ - protected static function eHeartBeat(array $id): array - { - $url = 'https://live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/E'; - $headers = [ - 'Content-Type' => 'application/x-www-form-urlencoded', - 'Origin' => 'https://live.bilibili.com', - 'Referer' => 'https://live.bilibili.com/' . $id[3], - ]; - $user_info = User::parseCookies(); - $payload = [ - 'id' => json_encode([$id[0], $id[1], $id[2], $id[3]], true), - 'device' => json_encode([Generator::hash(), Generator::uuid4()], true), - 'ts' => time() * 1000, - 'is_patch' => 0, - 'heart_beat' => [], - 'ua' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0', - 'csrf_token' => $user_info['token'], - 'csrf' => $user_info['token'], - 'visit_id' => '' - ]; - // print_r($payload); - Log::debug(json_encode($payload, true)); - $raw = Curl::post('pc', $url, $payload, $headers); - // {'code':0,'message':'0','ttl':1,'data':{'timestamp':1595342828,'heartbeat_interval':300,'secret_key':'seacasdgyijfhofiuxoannn','secret_rule':[2,5,1,4],'patch_status':2}} - - unset($payload['id']); - static::$_data = array_merge_recursive(static::$_data, $payload); - - return json_decode($raw, true); - } - - /** - * @use X心跳 - * @param array $id - * @return array|bool[] - */ - protected static function xHeartBeat(array $id): array - { - $url = 'https://live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/X'; - $user_info = User::parseCookies(); - $headers = [ - 'Content-Type' => 'application/x-www-form-urlencoded', - 'Origin' => 'https://live.bilibili.com', - 'Referer' => 'https://live.bilibili.com/' . $id[3], - ]; - $payload = [ - 's' => static::$_data['s'], - 'id' => json_encode([$id[0], $id[1], $id[2], $id[3]], true), - 'device' => static::$_data['device'], - 'ets' => static::$_data['ets'], - 'benchmark' => static::$_data['benchmark'], - 'time' => static::$_data['time'], - 'ts' => static::$_data['ts'], - 'ua' => static::$_data['ua'], - 'csrf_token' => $user_info['token'], - 'csrf' => $user_info['token'], - 'visit_id' => '' - ]; -// print_r($payload); - Log::debug(json_encode($payload, true)); - $raw = Curl::post('pc', $url, $payload, $headers); - # {'code':0,'message':'0','ttl':1,'data':{'heartbeat_interval':300,'timestamp':1595346846,'secret_rule':[2,5,1,4],'secret_key':'seacasdgyijfhofiuxoannn'}} - return json_decode($raw, true); - } - - /** - * @use 加密参数S - * @param array $t - * @param array $r - * @return string|false - */ - protected static function encParamS(array $t, array $r) - { - $headers = [ - 'Content-Type' => 'application/json', - ]; - // 加密部分 - $payload = ['t' => $t, 'r' => $r]; - $data = Curl::put('other', static::$_enc_server, $payload, $headers); - $de_raw = json_decode($data, true); - if ($de_raw['code'] == 0) { - // Log::info("S加密成功 {$de_raw['s']}"); - return $de_raw['s']; - } else { - Log::warning("S加密失败 {$de_raw['message']}"); - return false; - } - } - - } \ No newline at end of file