Initial commit

This commit is contained in:
net909
2023-09-28 16:21:06 +08:00
parent 498772eb04
commit ba7be004c0
640 changed files with 87745 additions and 0 deletions
+477
View File
@@ -0,0 +1,477 @@
<?php
namespace app\lib;
use Exception;
class BilibiliHelper
{
private $cookie;
private $token;
private $mixinKey;
public static $qualitys = ['127'=>'8K 超高清', '126'=>'杜比视界', '125'=>'HDR 真彩', '120'=>'4K 超清', '116'=>'1080P 高帧率', '112'=>'1080P 高码率', '80'=>'1080P 高清', '74'=>'720P 高帧率', '64'=>'720P 高清', '48'=>'720P 高清', '32'=>'480P 清晰', '16'=>'360P 流畅', '6'=>'240P 极速'];
public static $qualitys_audio = ['30216'=>'64K', '30232'=>'132K', '30280'=>'192K'];
public function __construct($cookie = null, $token = null)
{
$this->cookie = $cookie; //For WEB
$this->token = $token; //For APP/TV
}
//获取登录信息
public function login_info()
{
$url = 'https://api.bilibili.com/x/web-interface/nav';
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取登录状态失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
return true;
}elseif($arr['code'] == -101){
throw new Exception('COOKIE已失效');
}else{
throw new Exception('获取登录状态失败 '.$arr['message']);
}
}
//获取用户上传视频信息
public function ugc_video_info($querystring){
$url = 'https://api.bilibili.com/x/web-interface/view?'.$querystring;
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频信息失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
return $arr['data'];
}else{
throw new Exception('获取视频信息失败:'.$arr['message']);
}
}
//获取正版视频信息
public function pgc_video_info($ep_id){
$url = 'https://api.bilibili.com/pgc/view/web/season?ep_id='.$ep_id;
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频信息失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['result']['episodes'])) throw new Exception('获取视频信息失败,返回内容错误');
$data = null;
foreach($arr['result']['episodes'] as $row){
if($ep_id == $row['id']){
$data = $row;
}
}
if(empty($data))throw new Exception('获取视频信息失败,未找到对应视频信息');
return $data;
}else{
throw new Exception('获取视频信息失败:'.$arr['message']);
}
}
//获取正版视频信息
public function pgc_video_info_by_ssid($season_id){
$url = 'https://api.bilibili.com/pgc/view/web/season?season_id='.$season_id;
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频信息失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['result']['episodes'])) throw new Exception('获取视频信息失败,返回内容错误');
$data = $arr['result']['episodes'][0];
if(empty($data))throw new Exception('获取视频信息失败,未找到对应视频信息');
return $data;
}else{
throw new Exception('获取视频信息失败:'.$arr['message']);
}
}
//获取课堂视频信息
public function pugv_video_info($ep_id){
$url = 'https://api.bilibili.com/pugv/view/web/season?ep_id='.$ep_id;
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频信息失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['data']['episodes'])) throw new Exception('获取视频信息失败,返回内容错误');
$data = null;
foreach($arr['data']['episodes'] as $row){
if($ep_id == $row['id']){
$data = $row;
}
}
if(empty($data))throw new Exception('获取视频信息失败,未找到对应视频信息');
return $data;
}else{
throw new Exception('获取视频信息失败:'.$arr['message']);
}
}
//获取视频弹幕
public function get_video_comment($cid){
$danmu_xml = $this->curl('https://comment.bilibili.com/'.$cid.'.xml');
if(!$danmu_xml){
return msg('error','获取弹幕内容失败');
}
$dom = new \DOMDocument();
$dom->loadXML($danmu_xml);
$result = $this->getArray($dom->documentElement);
return isset($result['d']) ? $result['d'] : [];
}
//用户上传视频解析(支持外链)
public function get_video_url($aid, $cid){
$param = [
'avid' => $aid,
'cid' => $cid,
'qn' => '120',
'otype' => 'json',
'fourk' => '1',
'fnver' => '0',
'fnval' => '128',
'player' => '3',
'platform' => 'html5',
'high_quality' => '1',
];
$url = 'https://api.bilibili.com/x/player/playurl?'.http_build_query($param);
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频下载链接失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['data']['durl'])) throw new Exception('获取视频下载链接失败,返回内容错误');
$url = $arr['data']['durl'][0]['url'];
$size = $arr['data']['durl'][0]['size'];
$quality = $arr['data']['support_formats'][0]['new_description'];
return ['url'=>$url, 'size'=>$size, 'quality'=>$quality, 'format'=>$arr['data']['format'], 'codec'=>$this->get_codec($arr['data']['video_codecid'])];
}else{
throw new Exception('获取视频下载链接失败 '.$arr['message']);
}
}
//用户上传视频解析
public function ugc_video_parse($aid, $cid){
$param = [
'avid' => $aid,
'cid' => $cid,
'qn' => '0',
'type' => '',
'otype' => 'json',
'fourk' => '1',
'fnver' => '0',
'fnval' => '4048',
];
$url = 'https://api.bilibili.com/x/player/playurl?'.http_build_query($param);
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频下载链接失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['data']['dash'])) throw new Exception('获取视频下载链接失败,返回内容错误');
return $this->video_data_handle($arr['data']);
}else{
throw new Exception('获取视频下载链接失败 '.$arr['message']);
}
}
//用户上传视频解析(TV接口)
public function ugc_video_parse_tv($aid, $cid){
$param = [
'avid' => $aid,
'cid' => $cid,
'qn' => '0',
'type' => '',
'otype' => 'json',
'fnver' => '0',
'fnval' => '4048',
'device' => 'android',
'platform' => 'android',
'mobi_app' => 'android_tv_yst',
'npcybs' => '0',
'force_host' => '2',
'build' => '102801',
];
if($this->token){
$param['access_key'] = $this->token;
}
$url = 'https://api.snm0516.aisee.tv/x/tv/ugc/playurl?'.http_build_query($param);
$ret = $this->curl($url);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频下载链接失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['dash'])) throw new Exception('获取视频下载链接失败,返回内容错误');
return $this->video_data_handle($arr);
}else{
throw new Exception('获取视频下载链接失败 '.$arr['message']);
}
}
//正版视频解析
public function pgc_video_parse($aid, $cid, $epid, $is_cheese=false){
$param = [
'avid' => $aid,
'cid' => $cid,
'qn' => '0',
'type' => '',
'otype' => 'json',
'fourk' => '1',
'fnver' => '0',
'fnval' => '4048',
'module' => 'bangumi',
'ep_id' => $epid,
'session' => ''
];
$url = 'https://api.bilibili.com/pgc/player/web/playurl?'.http_build_query($param);
if($is_cheese){
$url = str_replace('/pgc/','/pugv/',$url);
}
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频下载链接失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['result']['dash'])) throw new Exception('获取视频下载链接失败,返回内容错误');
return $this->video_data_handle($arr['result']);
}elseif($arr['code'] == -10403 && !$is_cheese){
$url = 'https://www.bilibili.com/bangumi/play/ep'.$epid;
$ret = $this->curl($url, null, $this->cookie.';CURRENT_FNVAL=4048;');
preg_match('!window\.__playinfo__=([\s\S]*?)<\/script>!',$ret,$match);
if(isset($match[1])){
$arr = json_decode($match[1], true);
}else{
throw new Exception('获取视频下载链接失败 '.$arr['message']);
}
}else{
throw new Exception('获取视频下载链接失败 '.$arr['message']);
}
}
//正版视频解析(TV接口)
public function pgc_video_parse_tv($aid, $cid, $epid, $is_cheese=false){
$param = [
'appkey' => '4409e2ce8ffd12b8',
'aid' => $aid,
'cid' => $cid,
'qn' => '0',
'module' => 'bangumi',
'ep_id' => $epid,
'expire' => '0',
'fnval' => '80',
'fnver' => '0',
'fourk' => '1',
'mid' => '0',
'otype' => 'json',
'device' => 'android',
'platform' => 'android',
'mobi_app' => 'android_tv_yst',
'npcybs' => '0',
'build' => '102801',
'ts' => time()
];
if($this->token){
$param['access_key'] = $this->token;
}
$param['sign'] = $this->tv_get_sign($param);
$url = 'https://api.snm0516.aisee.tv/pgc/player/api/playurltv?'.http_build_query($param);
if($is_cheese){
$url = str_replace('/pgc/','/pugv/',$url);
}
$ret = $this->curl($url);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取视频下载链接失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['dash'])) throw new Exception('获取视频下载链接失败,返回内容错误');
return $this->video_data_handle($arr);
}else{
throw new Exception('获取视频下载链接失败 '.$arr['message']);
}
}
private function video_data_handle($data){
$video = [];
$audio = [];
$timelength = round($data['timelength']/1000);
if($data['dash']['video']){
foreach($data['dash']['video'] as $row){
if(preg_match('!://(.*:\\d+)/!',$row['base_url'],$match)){ //替换PCDN
$row['base_url'] = str_replace($match[1], 'upos-sz-mirrorcoso1.bilivideo.com', $row['base_url']);
}
$size = round($timelength * $row['bandwidth'] / 8);
$video[] = ['url'=>$row['base_url'], 'quality'=>self::$qualitys[$row['id']], 'bandwidth'=>round($row['bandwidth']/1000), 'size' => $size, 'codec'=>$this->get_codec($row['codecid']), 'ratio'=>$row['width'].'×'.$row['height'], 'fps'=>$row['frame_rate']];
}
}
if($data['dash']['audio']){
foreach($data['dash']['audio'] as $row){
$size = round($timelength * $row['bandwidth'] / 8);
$audio[] = ['url'=>$row['base_url'], 'quality'=>self::$qualitys_audio[$row['id']], 'bandwidth'=>round($row['bandwidth']/1000), 'size' => $size, 'codec'=>str_replace(['mp4a.40.2','ec-3'], ['M4A', 'AC3'], $row['codecs'])];
}
}
return ['video'=>$video, 'audio'=>$audio];
}
//获取音乐信息
public function get_audio_info($sid){
$url = 'https://www.bilibili.com/audio/music-service-c/web/song/info?sid='.$sid;
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取音乐信息失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
return $arr['data'];
}else{
throw new Exception('获取音乐信息失败:'.$arr['message']);
}
}
//音乐解析
public function get_audio_url($sid){
$url = 'https://www.bilibili.com/audio/music-service-c/web/url?sid='.$sid.'&privilege=2&quality=2';
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('获取音乐下载链接失败');
}elseif(isset($arr['code']) && $arr['code'] == 0){
if(!isset($arr['data']['cdns'])) throw new Exception('获取音乐下载链接失败,返回内容错误');
$url = $arr['data']['cdns'][0];
$size = $arr['data']['size'];
return ['url'=>$url, 'size'=>$size, 'quality'=>'MP3192K'];
}else{
throw new Exception('获取音乐下载链接失败:'.$arr['message']);
}
}
private function curl($url,$data=null,$cookie=null,$referer=null){
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
$httpheader[] = "Accept: application/json";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Accept-Encoding: gzip,deflate,sdch";
$httpheader[] = "Connection: keep-alive";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
if($data){
if(is_array($data)) $data=http_build_query($data);
curl_setopt($ch, CURLOPT_POSTFIELDS,$data);
curl_setopt($ch, CURLOPT_POST,1);
}
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_REFERER, $referer?$referer:'https://www.bilibili.com/');
if($cookie){
curl_setopt($ch,CURLOPT_COOKIE, $cookie);
}
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44');
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
$ret=curl_exec($ch);
curl_close($ch);
return $ret;
}
private function get_codec($codecid){
switch($codecid){
case 13:
return 'AV1';break;
case 12:
return 'HEVC';break;
case 7:
return 'AVC';break;
default:
return 'UNKNOWN';break;
}
}
private function tv_get_sign($param){
$key = '59b43e04ad6965f34319062b478f83dd';
ksort($param);
$signstr = http_build_query($param);
return md5($signstr.$key);
}
private function getArray($node) {
$array = false;
if ($node->hasAttributes()) {
foreach ($node->attributes as $attr) {
$array[$attr->nodeName] = $attr->nodeValue;
}
}
if ($node->hasChildNodes()) {
if ($node->childNodes->length == 1) {
$array[$node->firstChild->nodeName] = $this->getArray($node->firstChild);
} else {
foreach ($node->childNodes as $childNode) {
if ($childNode->nodeType != XML_TEXT_NODE) {
$array[$childNode->nodeName][] = $this->getArray($childNode);
}
}
}
} else {
return $node->nodeValue;
}
return $array;
}
private function encWbi($params){
$mixin_key = $this->getMixinKey();
$curr_time = time();
$chr_filter = "/[!'()*]/";
$query = [];
$params['wts'] = $curr_time;
ksort($params);
foreach ($params as $key => $value) {
$value = preg_replace($chr_filter, '', $value);
$query[] = urlencode($key) . '=' . urlencode($value);
}
$query = implode('&', $query);
$wbi_sign = md5($query . $mixin_key);
return $query . '&w_rid=' . $wbi_sign;
}
private function getMixinKey(){
if(!empty($this->mixinKey)) return $this->mixinKey;
$url = 'https://api.bilibili.com/x/web-interface/nav';
$ret = $this->curl($url, null, $this->cookie);
$arr = json_decode($ret, true);
if(!$arr){
throw new Exception('请求失败');
}
if(!isset($arr['data']['wbi_img'])){
throw new Exception('获取WbiKeys失败');
}
$img_url = $arr['data']['wbi_img']['img_url'];
$sub_url = $arr['data']['wbi_img']['sub_url'];
$img_key = substr(basename($img_url), 0, strpos(basename($img_url), '.'));
$sub_key = substr(basename($sub_url), 0, strpos(basename($sub_url), '.'));
$key = $img_key . $sub_key;
$mixinKeyEncTab = [
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
36, 20, 34, 44, 52
];
$t = '';
foreach ($mixinKeyEncTab as $n) $t .= $key[$n];
$this->mixinKey = substr($t, 0, 32);
return $this->mixinKey;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace app\lib;
class EnvOperation
{
private $env;
private $exampleEnv;
public function __construct($exampleEnv)
{
$this->exampleEnv = $exampleEnv;
}
/**
* @return mixed
*/
public function getEnv()
{
return $this->env;
}
/**
* @param mixed $env
*/
public function setEnv($env): void
{
$this->env = $env;
}
public function purify($env = [])
{
preg_match_all('#{{(.+?)}}#', $this->env, $matches, PREG_SET_ORDER);
foreach ($matches as $v) {
$list = explode(':', $v[1]);
$value = isset($env[$list[0]]) ? $env[$list[0]] : '';
$defaultValue = isset($list[1]) ? $list[1] : '';
$type = isset($list[2]) ? $list[2] : '';
if ($type === 'bool') {
$value = var_export(boolval($value), 1);
}
if (empty($value)) {
$value = $defaultValue;
}
$this->env = preg_replace('#' . $v[0] . '#', $value, $this->env);
}
}
public function set($key, $newValue)
{
if (is_null($this->env)) {
$this->env = preg_replace('#{{' . $key . '}}#', $newValue, $this->exampleEnv);
} else {
$this->env = preg_replace('#{{' . $key . '}}#', $newValue, $this->env);
}
}
public function save()
{
return file_put_contents(app()->getRootPath() . '.env', $this->env);
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace app\lib;
use think\facade\Db;
class ExecSQL
{
private $errors = [];
/**
* @return array
*/
public function getErrors(): array
{
return $this->errors;
}
/**
* @param $sql array|string SQL语句 传入字符串类型需以\n分割
* @return int 返回影响行数
*/
public function exec($sql)
{
$sql = $this->purify($sql);
$number = 0;
foreach ($sql as $key => $line) {
try {
$number += Db::execute($line);
} catch (\Exception $e) {
$this->errors[] = '第' . $key . '行:' . iconv('utf-8','utf-8//IGNORE',$e->getMessage());
}
}
return $number;
}
public function purify($sql)
{
$tmp = '';
$purify = [];
if (!is_array($sql)) {
$sql = explode("\n", $sql);
}
foreach ($sql as $key => &$line) {
$line = trim($line);
if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
unset($sql[$key]);
continue;
}
$tmp .= $line;
if (substr($line, -1, 1) == ';') {
unset($sql[$key]);
$purify[] = $tmp;
$tmp = '';
}
unset($sql[$key]);
}
return $purify;
}
}
+146
View File
@@ -0,0 +1,146 @@
<?php
namespace app\lib;
/**
* 极验3.0 lib
*/
class GeetestLib
{
const SDK_VERSION = 'php_3.0.0';
const JSON_FORMAT = "1";
private $geetest_id;
private $geetest_key;
public function __construct($geetest_id, $geetest_key) {
$this->geetest_id = $geetest_id;
$this->geetest_key = $geetest_key;
}
//验证初始化
public function pre_process($params) {
if(!empty($this->geetest_id) && !empty($this->geetest_key)){
return $this->pre_process_api($params);
}else{
return $this->pre_process_demo($params);
}
}
private function pre_process_api($params) {
$public_params = [
'digestmod' => 'md5',
'gt' => $this->geetest_id,
'sdk' => self::SDK_VERSION,
'json_format' => self::JSON_FORMAT
];
$params = array_merge($params, $public_params);
$url = 'http://api.geetest.com/register.php?' . http_build_query($params);
$res = get_curl($url);
$arr = json_decode($res, true);
if($arr && isset($arr['challenge'])){
return $this->success_process($arr['challenge']);
}else{
return $this->failback_process();
}
}
private function success_process($challenge) {
$challenge = md5($challenge . $this->geetest_key);
$result = array(
'success' => 1,
'gt' => $this->geetest_id,
'challenge' => $challenge,
'new_captcha'=>true
);
return $result;
}
private function failback_process() {
$challenge = md5(uniqid(mt_rand(), true) . microtime());
$result = array(
'success' => 0,
'gt' => $this->geetest_id,
'challenge' => $challenge,
'new_captcha'=>true
);
return $result;
}
private function pre_process_demo($params) {
$url = 'https://www.geetest.com/demo/gt/register-fullpage?t=' . time() . "123";
$referer = 'https://www.geetest.com/demo/slide-popup.html';
$data = get_curl($url, 0, $referer);
$arr = json_decode($data, true);
if($arr && isset($arr['challenge'])){
return $arr;
}else{
return $this->failback_process();
}
}
//正常流程下(即验证初始化成功),二次验证
public function success_validate($challenge, $validate, $seccode, $params) {
if(!empty($this->geetest_id) && !empty($this->geetest_key)){
return $this->success_validate_api($challenge, $validate, $seccode, $params);
}else{
return $this->success_validate_demo($challenge, $validate, $seccode);
}
}
private function success_validate_api($challenge, $validate, $seccode, $params) {
if (!$this->check_validate($challenge, $validate)) {
return false;
}
$public_params = [
'seccode' => $seccode,
'challenge' => $challenge,
'captchaid' => $this->geetest_id,
'sdk' => self::SDK_VERSION,
'json_format' => self::JSON_FORMAT
];
$params = array_merge($params, $public_params);
$url = 'http://api.geetest.com/validate.php';
$res = get_curl($url, http_build_query($params));
$arr = json_decode($res, true);
if($arr && isset($arr['seccode'])){
if($arr['seccode'] == md5($seccode)){
return true;
}
}
return false;
}
private function check_validate($challenge, $validate) {
if (strlen($validate) != 32) {
return false;
}
if (md5($this->geetest_key . 'geetest' . $challenge) != $validate) {
return false;
}
return true;
}
private function success_validate_demo($challenge, $validate, $seccode) {
$params = [
'geetest_challenge' => $challenge,
'geetest_validate' => $validate,
'geetest_seccode' => $seccode
];
$url = 'https://www.geetest.com/demo/gt/validate-fullpage';
$referer = 'https://www.geetest.com/demo/slide-popup.html';
$data = get_curl($url, http_build_query($params), $referer);
$arr = json_decode($data, true);
if($arr && $arr['status'] == 'success'){
return true;
}
return false;
}
//异常流程下(即验证初始化失败,宕机模式),二次验证
public function fail_validate($challenge, $validate, $seccode) {
if(md5($challenge) == $validate){
return true;
}else{
return false;
}
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace app\lib;
use Exception;
/**
* class Ip2Region
* 为兼容老版本调度而创建
* @author Anyon<[email protected]>
* @datetime 2022/07/18
*/
class Ip2Region
{
/**
* 查询实例对象
* @var XdbSearcher
*/
private $searcher;
/**
* 初始化构造方法
* @throws Exception
*/
public function __construct()
{
$this->searcher = XdbSearcher::newWithFileOnly(dirname(__FILE__) . '/ip2region.xdb');
}
/**
* 兼容原 memorySearch 查询
* @param string $ip
* @return string
* @throws Exception
*/
public function search($ip)
{
return $this->searcher->search($ip);
}
/**
* destruct method
* resource destroy
*/
public function __destruct()
{
$this->searcher->close();
unset($this->searcher);
}
}
+239
View File
@@ -0,0 +1,239 @@
<?php
namespace app\lib;
/** php QQWry获取ip接口 支持省 城市 完整版
* IP 地理位置查询类 修改自 CoolCode.CN
* 由于使用UTF8编码 如果使用纯真IP地址库的话 需要对返回结果进行编码转换
* @author liu21st <[email protected]>
*/
class IpLocation {
/**
* QQWry.Dat文件指针
*
* @var resource
*/
private $fp;
/**
* 第一条IP记录的偏移地址
*
* @var int
*/
private $firstip;
/**
* 最后一条IP记录的偏移地址
*
* @var int
*/
private $lastip;
/**
* IP记录的总条数(不包含版本信息记录)
*
* @var int
*/
private $totalip;
/**
* 构造函数,打开 QQWry.Dat 文件并初始化类中的信息
*
* @param string $filename
* @return IpLocation
*/
public function __construct() {
$this->fp = 0;
$filename = dirname(__FILE__)."/QQWry.dat";
if (($this->fp = fopen($filename, 'rb')) !== false) {
$this->firstip = $this->getlong();
$this->lastip = $this->getlong();
$this->totalip = ($this->lastip - $this->firstip) / 7;
}
}
/**
* 返回读取的长整型数
*
* @access private
* @return int
*/
private function getlong() {
//将读取的little-endian编码的4个字节转化为长整型数
$result = unpack('Vlong', fread($this->fp, 4));
return $result['long'];
}
/**
* 返回读取的3个字节的长整型数
*
* @access private
* @return int
*/
private function getlong3() {
//将读取的little-endian编码的3个字节转化为长整型数
$result = unpack('Vlong', fread($this->fp, 3).chr(0));
return $result['long'];
}
/**
* 返回压缩后可进行比较的IP地址
*
* @access private
* @param string $ip
* @return string
*/
private function packip($ip) {
// 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False,
// 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串
return pack('N', intval(ip2long($ip)));
}
/**
* 返回读取的字符串
*
* @access private
* @param string $data
* @return string
*/
private function getstring($data = "") {
$char = fread($this->fp, 1);
while (ord($char) > 0) { // 字符串按照C格式保存,以\0结束
$data .= $char; // 将读取的字符连接到给定字符串之后
$char = fread($this->fp, 1);
}
return $data;
}
/**
* 返回地区信息
*
* @access private
* @return string
*/
private function getarea() {
$byte = fread($this->fp, 1); // 标志字节
switch (ord($byte)) {
case 0: // 没有区域信息
$area = "";
break;
case 1:
case 2: // 标志字节为1或2,表示区域信息被重定向
fseek($this->fp, $this->getlong3());
$area = $this->getstring();
break;
default: // 否则,表示区域信息没有被重定向
$area = $this->getstring($byte);
break;
}
return $area;
}
private $provinces = array("黑龙江省","辽宁省","吉林省","河北省","河南省","湖北省","湖南省","山东省","山西省","陕西省",
"安徽省","浙江省","江苏省","福建省","广东省","海南省","四川省","云南省","贵州省","青海省","甘肃省",
"江西省","台湾省","内蒙古","宁夏","新疆","西藏","广西","北京市","上海市","天津市","重庆市","香港","澳门");
/**
* 根据所给 IP 地址或域名返回所在地区信息
*
* @access public
* @param string $ip
* @return array
*/
public function getlocation($ip='') {
if (!$this->fp) return null; // 如果数据文件没有被正确打开,则直接返回空
if(empty($ip)) return null;
$location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址
$ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址
// 不合法的IP地址会被转化为255.255.255.255
// 对分搜索
$l = 0; // 搜索的下边界
$u = $this->totalip; // 搜索的上边界
$findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息)
while ($l <= $u) { // 当上边界小于下边界时,查找失败
$i = floor(($l + $u) / 2); // 计算近似中间记录
fseek($this->fp, $this->firstip + $i * 7);
$beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址
// strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式
// 以便用于比较,后面相同。
if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时
$u = $i - 1; // 将搜索的上边界修改为中间记录减一
}
else {
fseek($this->fp, $this->getlong3());
$endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址
if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时
$l = $i + 1; // 将搜索的下边界修改为中间记录加一
}
else { // 用户的IP在中间记录的IP范围内时
$findip = $this->firstip + $i * 7;
break; // 则表示找到结果,退出循环
}
}
}
//获取查找到的IP地理位置信息
fseek($this->fp, $findip);
$location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址
$offset = $this->getlong3();
fseek($this->fp, $offset);
$location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址
$byte = fread($this->fp, 1); // 标志字节
switch (ord($byte)) {
case 1: // 标志字节为1,表示国家和区域信息都被同时重定向
$countryOffset = $this->getlong3(); // 重定向地址
fseek($this->fp, $countryOffset);
$byte = fread($this->fp, 1); // 标志字节
switch (ord($byte)) {
case 2: // 标志字节为2,表示国家信息又被重定向
fseek($this->fp, $this->getlong3());
$location['country'] = $this->getstring();
fseek($this->fp, $countryOffset + 4);
$location['area'] = $this->getarea();
break;
default: // 否则,表示国家信息没有被重定向
$location['country'] = $this->getstring($byte);
$location['area'] = $this->getarea();
break;
}
break;
case 2: // 标志字节为2,表示国家信息被重定向
fseek($this->fp, $this->getlong3());
$location['country'] = $this->getstring();
fseek($this->fp, $offset + 8);
$location['area'] = $this->getarea();
break;
default: // 否则,表示国家信息没有被重定向
$location['country'] = $this->getstring($byte);
$location['area'] = $this->getarea();
break;
}
if (trim($location['country']) == 'CZ88.NET') { // CZ88.NET表示没有有效信息
$location['country'] = '未知';
}
if (trim($location['area']) == 'CZ88.NET') {
$location['area'] = '';
}
$location['country'] = @iconv('gbk','utf-8',$location['country']); //转换格式,防止乱码
$location['area'] = @iconv('gbk','utf-8',$location['area']); //转换格式,防止乱码
foreach($this->provinces as $v) {
if(strpos($location['country'],$v) === 0) {
$location['province'] = $v;
$location['city'] = str_replace($v,'',$location['country']);
break;
}
}
if(empty($location['province'])) $location['province'] = $location['country'];
if(empty($location['city'])) $location['city'] = $location['country'];
return $location;
}
/**
* 析构函数,用于在页面执行结束后自动关闭打开的文件。
*
*/
public function __destruct() {
if ($this->fp) {
fclose($this->fp);
}
$this->fp = 0;
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace app\lib;
/*
* 快捷登录接口
*/
class Oauth{
private $apiurl;
private $appid;
private $appkey;
private $callback;
function __construct($config){
$this->apiurl = $config['apiurl'].'connect.php';
$this->appid = $config['appid'];
$this->appkey = $config['appkey'];
$this->callback = $config['callback'];
}
//获取登录跳转url
public function login($type, $state){
//-------构造请求参数列表
$keysArr = array(
"act" => "login",
"appid" => $this->appid,
"appkey" => $this->appkey,
"type" => $type,
"redirect_uri" => $this->callback,
"state" => $state
);
$login_url = $this->apiurl.'?'.http_build_query($keysArr);
$response = get_curl($login_url);
$arr = json_decode($response,true);
return $arr;
}
//登录成功返回网站
public function callback($code){
//-------请求参数列表
$keysArr = array(
"act" => "callback",
"appid" => $this->appid,
"appkey" => $this->appkey,
"code" => $code
);
//------构造请求access_token的url
$token_url = $this->apiurl.'?'.http_build_query($keysArr);
$response = get_curl($token_url);
$arr = json_decode($response,true);
return $arr;
}
//查询用户信息
public function query($type, $social_uid){
//-------请求参数列表
$keysArr = array(
"act" => "query",
"appid" => $this->appid,
"appkey" => $this->appkey,
"type" => $type,
"social_uid" => $social_uid
);
//------构造请求access_token的url
$token_url = $this->apiurl.'?'.http_build_query($keysArr);
$response = get_curl($token_url);
$arr = json_decode($response,true);
return $arr;
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
namespace app\lib;
use think\Exception;
use think\facade\Db;
/**
* 实例化后需要保存zip文件到 $zipFilepath
* Class Plugin
* @package app\lib
*/
class Plugin
{
private $tmpPath = '';
private $uniqid = '';
private $zipFilename = '';
private $zipFilepath = '';
private $tmpDirPath = '';
private $pluginPath = '';
private $pluginAuthor = '';
private $pluginName = '';
private $pluginClass = '';
public function __construct()
{
$this->tmpPath = app()->getRuntimePath() . '/tmp/';
$this->uniqid = uniqid();
$this->zipFilename = $this->uniqid . '.zip';
$this->zipFilepath = $this->tmpPath . $this->zipFilename;
$this->tmpDirPath = $this->tmpPath . $this->uniqid . '/';;
if (!file_exists($this->tmpPath)) {
mkdir($this->tmpPath, 0777, true);
}
}
private function unzip()
{
if (!file_exists($this->zipFilepath)) {
throw new Exception('压缩包不存在请重试');
}
if (!unzip($this->zipFilepath, $this->tmpDirPath)) {
throw new Exception('解压失败');
}
return true;
}
private function checkPlugin()
{
$tree_relative = tree_relative($this->tmpDirPath);
$arr1 = array_keys($tree_relative);
$pluginAuthor = reset($arr1);
if (empty($pluginAuthor)) {
throw new Exception('插件目录格式有误,安装失败');
}
$arr2 = array_keys($tree_relative[$pluginAuthor]);
$pluginName = reset($arr2);
if (empty($pluginAuthor) || empty($pluginName)) {
throw new Exception('插件目录格式有误,安装失败');
}
if (!file_exists("$this->tmpDirPath/$pluginAuthor/$pluginName/Install.php")) {
throw new Exception('插件缺失Install.php,安装失败');
}
return [
$pluginAuthor,
$pluginName,
];
}
private function clearOld()
{
if (!file_exists(dirname($this->pluginPath))) {
mkdir(dirname($this->pluginPath), 0777, true);
}
del_tree($this->pluginPath);
}
public function install()
{
try {
$this->unzip();
$checkPlugin = $this->checkPlugin();
$this->pluginAuthor = $checkPlugin[0];
$this->pluginName = $checkPlugin[1];
$this->pluginPath = plugin_path_get() . "/$this->pluginAuthor/$this->pluginName";
//清空旧插件
$this->clearOld();
//移动文件
rename("$this->tmpDirPath/$this->pluginAuthor/$this->pluginName", $this->pluginPath);
// 执行Install.php
require "$this->pluginPath/Install.php";
$this->pluginClass = "$this->pluginAuthor\\$this->pluginName";
$class = "plugin\\$this->pluginClass\\Install";
if (!class_exists($class)) {
throw new Exception("插件缺失类$this->pluginClass,安装失败");
}
$install = new $class();
$model = Db::name('plugin')->where('class', $this->pluginClass)->find();
if (!$model) {
$model['title'] = '插件' . $this->uniqid;
$model['alias'] = $this->uniqid;
$model['class'] = $this->pluginClass;
$model['desc'] = '';
$model['category_id'] = 0;
$model['request_count'] = 0;
}
$pluginconfig = $install->Install();
if(isset($pluginconfig['title'])) $model['title'] = $pluginconfig['title'];
if(isset($pluginconfig['alias'])) $model['alias'] = $pluginconfig['alias'];
if(isset($pluginconfig['class'])) $model['class'] = $pluginconfig['class'];
if(isset($pluginconfig['desc'])) $model['desc'] = $pluginconfig['desc'];
//判断alias是否重复
$model2 = Db::name('plugin')->where('alias', $model['alias'])->find();
if ($model2 && $model2['id'] !== $model['id']) {
$model['alias'] .= "_$this->uniqid";
}
$model['id'] = Db::name('plugin')->cache('plugins')->insertGetId($model);
} catch (\Exception $e) {
@del_tree($this->pluginPath);
return msg('error', $e->getMessage());
} finally {
@del_tree($this->tmpDirPath);
@unlink($this->zipFilepath);
}
return msg('ok', '安装成功', $model);
}
/**
* @return string
*/
public function getZipFilepath(): string
{
return $this->zipFilepath;
}
}
+170
View File
@@ -0,0 +1,170 @@
<?php
namespace app\lib;
/**
* QQ群相关操作类
*/
use Exception;
class QQGroup{
private $uin;
private $cookie;
private $gtk;
private $ua = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36';
public function __construct($uin, $cookie){
$this->uin=$uin;
preg_match('/skey=(.{10});/',$cookie,$skey);
$this->gtk=$this->getGTK($skey[1]);
$this->cookie=$cookie;
}
private function getGTK($skey){
$len = strlen($skey);
$hash = 5381;
for ($i = 0; $i < $len; $i++) {
$hash += ($hash << 5 & 2147483647) + ord($skey[$i]) & 2147483647;
$hash &= 2147483647;
}
return $hash & 2147483647;
}
//QQ群列表
public function grouplist($onlyadmin = false){
$url = 'https://qun.qq.com/cgi-bin/qun_mgr/get_group_list';
$post = 'bkn='.$this->gtk;
$data = get_curl($url,$post,'https://qun.qq.com/member.html',$this->cookie,0,$this->ua);
$arr = json_decode($data,true);
//print_r($arr);exit;
if(!$arr){
throw new Exception('QQ群列表获取失败!');
} elseif(isset($arr['ec']) && $arr['ec']==0) {
$group = [];
if(isset($arr['create'])){
foreach($arr['create'] as $row){
$group[] = $row;
}
}
if(isset($arr['manage'])){
foreach($arr['manage'] as $row){
$group[] = $row;
}
}
if(!$onlyadmin && isset($arr['join'])){
foreach($arr['join'] as $row){
$group[] = $row;
}
}
return $group;
} elseif($arr['ec']==1 || $arr['ec']==4) {
session('qq_cookie_qun', null);
throw new Exception('当前QQ登录状态已失效,请重新登录!');
} else {
throw new Exception('QQ群列表获取失败!'.$arr['em']);
}
}
//QQ群成员列表
public function groupmemberlist($groupid, $start, $end){
$url='https://qun.qq.com/cgi-bin/qun_mgr/search_group_members';
$post='gc='.$groupid.'&st='.$start.'&end='.$end.'&sort=0&bkn='.$this->gtk;
$data = get_curl($url,$post,'https://qun.qq.com/member.html',$this->cookie,0,$this->ua);
$arr = json_decode($data,true);
if (!$arr) {
throw new Exception('QQ群成员获取失败!');
}elseif ($arr["ec"] == 1) {
throw new Exception('SKEY已失效!');
}elseif ($arr["ec"]!=0){
throw new Exception('QQ群成员获取失败!'.$arr['em']);
}
$data = array();
$data['code'] = 0;
$data['count'] = $arr['count'];
$data['mems'] = $arr['mems'];
if($end<$arr['count'])$data['start'] = $end+1;
else $data['start'] = 0;
return $data;
}
//群公告列表
public function announcelist($groupid, $start){
$url='https://web.qun.qq.com/cgi-bin/announce/list_announce';
$post='bkn='.$this->gtk.'&qid='.$groupid.'&ft=23&s='.$start.'&n=10&ni=1&i=1';
$data = get_curl($url,$post,'https://web.qun.qq.com/announce/index.html',$this->cookie,0,$this->ua);
$arr = json_decode($data,true);
if (!$arr) {
throw new Exception('公告列表获取失败!');
}elseif ($arr["ec"] == 1) {
session('qq_cookie_qun', null);
throw new Exception('当前QQ登录状态已失效,请重新登录!');
}elseif ($arr["ec"]!=0){
throw new Exception('公告列表获取失败!'.$arr['em']);
}
if(!isset($arr['feeds']) || !$arr['feeds'])return [];
$uinlist = [];
foreach($arr['ui'] as $uin => $row){
$uinlist[$uin] = $row['n'];
}
$list = [];
foreach($arr['feeds'] as $row){
$msg = $row['msg']['text'];
if(mb_strlen($msg, 'utf-8')>30)$msg=mb_substr($msg, 0, 30, 'utf-8').'...';
if($row['pinned']==1)$msg = '<font color="red">[顶]</font>'.$msg;
$list[] = ['fid'=>$row['fid'], 'uin'=>$row['u'], 'nick'=>$uinlist[$row['u']]?$uinlist[$row['u']]:$row['u'], 'time'=>date("Y-m-d H:i:s", $row['pubt']), 'msg'=>$msg];
}
return $list;
}
//删除群公告
public function delannounce($groupid, $fid){
$url='https://web.qun.qq.com/cgi-bin/announce/del_feed';
$post='fid='.$fid.'&ft=23&bkn='.$this->gtk.'&qid='.$groupid.'&op=0';
$data = get_curl($url,$post,'https://web.qun.qq.com/announce/index.html',$this->cookie,0,$this->ua);
$arr = json_decode($data,true);
if(isset($arr["ec"]) && ($arr["ec"]==0 || $arr["ec"]==14)){
return true;
}elseif ($arr["ec"] == 1) {
throw new Exception('SKEY已失效!');
}else{
throw new Exception('公告删除失败!'.$arr['em']);
}
}
//解散群
public function dismissgroup($groupuin){
$resultarr = array(11=>'需要验证码', 13=>'号码异常,暂时不允许解散', 15=>'为了企业信息安全,请登录企业帐户中心进行解散操作。', 16=>'公益群暂不支持解散。', 17=>'该群被转让不足28天,暂时还不能解散。', 25=>'付费2000人群不可解散。', 51=>'您的群已绑定了教育机构,如需进行此操作,请先与机构解绑。');
$url = 'https://id.qq.com/qun/dismiss_group';
$referrer = 'https://id.qq.com/proxy.html';
$post = 'vc=undefined&gc='.$groupuin.'&uin='.$this->uin.'&s=1&bkn='.$this->gtk;
$data = get_curl($url,$post,$referrer,$this->cookie,0,$this->ua);
$arr = json_decode($data,true);
if(isset($arr["ec"]) && $arr["ec"]==0){
return true;
}elseif ($arr["ec"] == 1) {
session('qq_cookie_qqid', null);
throw new Exception('当前QQ登录状态已失效,请重新登录!');
}elseif(isset($arr['ec']) && array_key_exists($arr['ec'],$resultarr)){
throw new Exception($resultarr[$arr['ec']]);
}else{
throw new Exception('解散群失败,可能非群主或群不存在。返回信息:'.$data);
}
}
//获取加群链接
public function getjoinlink($groupuin){
$url = 'https://admin.qun.qq.com/cgi-bin/qun_admin/get_join_link';
$referrer = 'https://admin.qun.qq.com/create/share/index.html?ptlang=2052&groupUin='.$groupuin;
$post = 'gc='.$groupuin.'&type=1&bkn='.$this->gtk;
$data = get_curl($url,$post,$referrer,$this->cookie);
$arr = json_decode($data,true);
if (isset($arr["ec"]) && $arr['ec']==0) {
return $arr['url'];
}elseif($arr['ec']==1){
session('qq_cookie_qun', null);
throw new Exception('加群链接获取失败,原因:SKEY已失效');
}else{
throw new Exception('加群链接获取失败 '.$arr['em']);
}
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
namespace app\lib;
use Zxing\QrReader;
class QQLogin{
private $ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36';
public function getqrpic($daid){
if(empty($daid))return array('saveOK'=>-1,'msg'=>'daid不能为空');
if($daid == '5'){
$url='https://ssl.ptlogin2.qq.com/ptqrshow?appid=549000912&e=2&l=M&s=4&d=72&v=4&t=0.5409099'.time().'&daid=5&pt_3rd_aid=0&u1=https%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone';
$referer='https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https%3A//qzs.qq.com/qzone/v6/portal/proxy.html&daid=5&&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=549000912&style=22&target=self&s_url=https%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone';
}else{
$url='https://ssl.ptlogin2.qq.com/ptqrshow?appid=716027609&e=2&l=M&s=4&d=72&v=4&t=0.5409099'.time().'&daid='.$daid.'&pt_3rd_aid=100384226';
$referer='https://xui.ptlogin2.qq.com/cgi-bin/xlogin?daid='.$daid.'&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&target=self&s_url=https:%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html?para%3Dizone&pt_no_auth=0&appid=716027609&pt_3rd_aid=100384226';
}
$arr=$this->get_curl_split($url,$referer);
preg_match('/qrsig=(.*?);/',$arr['header'],$match);
if($qrsig=$match[1]){
$qrcode = new QrReader($arr['body'], QrReader::SOURCE_TYPE_BLOB);
$code_url = $qrcode->text();
return array('saveOK'=>0,'qrsig'=>$qrsig,'data'=>base64_encode($arr['body']),'url'=>$code_url);
}else{
return array('saveOK'=>1,'msg'=>'二维码获取失败');
}
}
public function qrlogin($daid,$s_url,$qrsig){
if(empty($daid)||empty($s_url))return array('saveOK'=>-1,'msg'=>'daid和s_url不能为空');
if(empty($qrsig))return array('saveOK'=>-1,'msg'=>'qrsig不能为空');
if($daid == '5'){
$url='https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&ptqrtoken='.$this->getqrtoken($qrsig).'&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-'.time().'000&js_ver=23042119&js_type=1&login_sig=&pt_uistyle=40&aid=549000912&daid=5&';
}else{
$url='https://ssl.ptlogin2.qq.com/ptqrlogin?u1='.urlencode($s_url).'&ptqrtoken='.$this->getqrtoken($qrsig).'&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-'.time().'0000&js_ver=10194&js_type=1&login_sig=&pt_uistyle=40&aid=716027609&daid='.$daid.'&pt_3rd_aid=100384226&';
}
$ret = $this->get_curl($url,0,'https://xui.ptlogin2.qq.com/','qrsig='.$qrsig.'; ',1);
if(preg_match("/ptuiCB\('(.*?)'\)/", $ret, $arr)){
$r=explode("','",str_replace("', '","','",$arr[1]));
if($r[0]==0){
preg_match('/uin=(\d+)&/',$ret,$uin);
$uin=$uin[1];
preg_match('/skey=@(.{9});/',$ret,$skey);
preg_match('/superkey=(.*?);/',$ret,$superkey);
$data=$this->get_curl($r[2],0,0,0,1);
if($data) {
preg_match_all('/Set-Cookie: (.*?);/i',$data,$matchs);
$cookie='';
foreach ($matchs[1] as $val) {
if(substr($val,-1)=='=')continue;
$cookie.=$val.'; ';
}
$cookie = substr($cookie,0,-2);
}
if($cookie){
return array('saveOK'=>0,'uin'=>$uin,'cookie'=>$cookie,'nickname'=>$r[5]);
}else{
return array('saveOK'=>6,'msg'=>'登录成功,获取相关信息失败!'.$r[2]);
}
}elseif($r[0]==65){
return array('saveOK'=>1,'msg'=>'二维码已失效。');
}elseif($r[0]==66){
return array('saveOK'=>2,'msg'=>'二维码未失效。');
}elseif($r[0]==67){
return array('saveOK'=>3,'msg'=>'正在验证二维码。');
}else{
return array('saveOK'=>6,'msg'=>$r[4]);
}
}else{
return array('saveOK'=>6,'msg'=>$ret);
}
}
private function getqrtoken($qrsig){
$len = strlen($qrsig);
$hash = 0;
for($i = 0; $i < $len; $i++){
$hash += (($hash << 5) & 2147483647) + ord($qrsig[$i]) & 2147483647;
$hash &= 2147483647;
}
return $hash & 2147483647;
}
private function get_curl($url,$post=0,$referer=0,$cookie=0,$header=0,$ua=0,$nobaody=0,$noproxy=0){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: application/json";
$httpheader[] = "Accept-Encoding: gzip,deflate,sdch";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: keep-alive";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
if($post){
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
if($header){
curl_setopt($ch, CURLOPT_HEADER, TRUE);
}
if($cookie){
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
if($referer){
curl_setopt($ch, CURLOPT_REFERER, $referer);
}
if($ua){
curl_setopt($ch, CURLOPT_USERAGENT,$ua);
}else{
curl_setopt($ch, CURLOPT_USERAGENT,$this->ua);
}
if($nobaody){
curl_setopt($ch, CURLOPT_NOBODY,1);
}
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
private function get_curl_split($url,$referer=0){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: */*";
$httpheader[] = "Accept-Encoding: gzip,deflate,sdch";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: keep-alive";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
curl_setopt($ch, CURLOPT_HEADER, TRUE);
curl_setopt($ch, CURLOPT_USERAGENT,$this->ua);
if($referer){
curl_setopt($ch, CURLOPT_REFERER, $referer);
}
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
$ret = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($ret, 0, $headerSize);
$body = substr($ret, $headerSize);
$ret=array();
$ret['header']=$header;
$ret['body']=$body;
curl_close($ch);
return $ret;
}
}
+209
View File
@@ -0,0 +1,209 @@
<?php
namespace app\lib;
/**
* QQ空间工具类
*/
use Exception;
class QQTool{
private $uin;
private $cookie;
private $gtk;
private $skey;
public function __construct($uin,$cookie,$is_skey = false){
$this->uin=$uin;
$this->cookie=$cookie;
if($is_skey){
$this->skey=getSubstr($cookie, 'skey=', ';');
$this->gtk=$this->getGTK($this->skey);
}else{
$pskey=getSubstr($cookie, 'p_skey=', ';');
$this->gtk=$this->getGTK($pskey);
}
}
private function getGTK($skey){
$len = strlen($skey);
$hash = 5381;
for ($i = 0; $i < $len; $i++) {
$hash += ($hash << 5 & 2147483647) + ord($skey[$i]) & 2147483647;
$hash &= 2147483647;
}
return $hash & 2147483647;
}
private function getGTK2($skey){
$salt = 5381;
$md5key = 'tencentQQVIP123443safde&!%^%1282';
$hash = array();
$hash[] = ($salt << 5);
for($i = 0; $i < strlen($skey); $i ++)
{
$ASCIICode = mb_convert_encoding($skey[$i], 'UTF-32BE', 'UTF-8');
$ASCIICode = hexdec(bin2hex($ASCIICode));
$hash[] = (($salt << 5) + $ASCIICode);
$salt = $ASCIICode;
}
$md5str = md5(implode($hash) . $md5key);
return $md5str;
}
//好友与分组列表
public function friendlist(){
$url = 'https://mobile.qzone.qq.com/friend/mfriend_list?g_tk='.$this->gtk.'&res_uin='.$this->uin.'&res_type=normal&format=json&count_per_page=10&page_index=0&page_type=0&mayknowuin=&qqmailstat=';
$json = get_curl($url,0,1,$this->cookie);
$json = mb_convert_encoding($json, "UTF-8", "UTF-8");
$arr = json_decode($json, true);
if(!$arr){
throw new Exception('好友列表获取失败!');
}elseif(isset($arr['code']) && $arr['code']==0){
return $arr["data"];
}elseif ($arr["code"] == -3000) {
session('qq_cookie_qzone', null);
throw new Exception('当前QQ登录状态已失效,请重新登录!');
}elseif (isset($arr["message"])) {
throw new Exception('好友列表获取失败!'.$arr["message"]);
}else{
throw new Exception('好友列表获取失败!');
}
}
//说说列表
public function shuoshuolist($count){
$url='https://mobile.qzone.qq.com/list?g_tk='.$this->gtk.'&res_attach=&format=json&list_type=shuoshuo&action=0&res_uin='.$this->uin.'&count='.$count;
$data = get_curl($url,0,1,$this->cookie);
$arr=json_decode($data,true);
if (isset($arr['code']) && $arr['code']==0) {
if(isset($arr['data']['vFeeds']))
return $arr['data']['vFeeds'];
else
return $arr['data']['feeds']['vFeeds'];
}elseif ($arr["code"] == -3000) {
session('qq_cookie_qzone', null);
throw new Exception('当前QQ登录状态已失效,请重新登录!');
}elseif (isset($arr["message"])) {
throw new Exception('说说列表获取失败!'.$arr["message"]);
}else{
throw new Exception('说说列表获取失败!');
}
}
//说说最多点赞数
public function shuoshuozancount($count){
$zan = 0;
$list = $this->shuoshuolist($count);
foreach($list as $row){
if($row['like']['num']>$zan) $zan=$row['like']['num'];
}
return $zan;
}
//秒赞检测
public function mzjc(){
$arr = $this->friendlist();
$friend=$arr["list"];
$gpnames=$arr["gpnames"];
foreach($gpnames as $gprow){
$gpid=$gprow['gpid'];
$gpname[$gpid]=$gprow['gpname'];
}
$arr = $this->shuoshuolist('5');
$qqrow=array();
$qquins=array();
foreach ($arr as $row ) {
$url='https://users.qzone.qq.com/cgi-bin/likes/get_like_list_app?uin='.$this->uin.'&unikey='.urlencode($row['comm']['curlikekey']).'&begin_uin=0&query_count=200&if_first_page=1&g_tk='.$this->gtk;
$data2 = get_curl($url,0,'https://user.qzone.qq.com/',$this->cookie);
if(!$data2){
throw new Exception('说说点赞列表获取失败!可更新SKEY后重试');
}
preg_match('/_Callback\((.*?)\)\;/is',$data2,$json);
$arr2=json_decode($json[1],true);
$data2=$arr2['data']['like_uin_info'];
foreach ($data2 as $row2 ) {
$fuin=$row2['fuin'];
if(isset($qqrow[$fuin])){$qqrow[$fuin]++;}
else {$qqrow[$fuin]=1;$qquins[]=$fuin;}
}
}
$mzcount=count($qqrow);
foreach ($friend as $row3 ) {
$fuin=$row3['uin'];
if(isset($qqrow[$fuin]))$list['mz']=$qqrow[$fuin];
else $list['mz']=0;
$list['uin']=$row3['uin'];
$list['name']=$row3['nick'];
if($row3['remark'])$list['remark']=$row3['remark'];
else $list['remark']=$row3['nick'];
$list['groupid']=$row3['groupid'];
$result['friend'][]=$list;
unset($list);
}
rsort($result['friend']);
$friend=$result['friend'];
$fcount=count($friend);
$array=array();
foreach($friend as $nrow){
if($nrow['mz']) $array[$nrow['groupid']]['mzcount']=$array[$nrow['groupid']]['mzcount']+1;
$array[$nrow['groupid']][]=$nrow;
}
$friend=$array;
return [$fcount, $mzcount, $friend, $gpnames];
}
//查询当前是否VIP
public function getisvip(){
$data=get_curl('https://cgi.vip.qq.com/unipay/init?format=json&aid=vipminipay.pingtai.vipsite.nav_new&platform=pc&version=-1&isbreak=0&g_tk='.$this->getGTK2($this->skey),0,'https://vip.qq.com/',$this->cookie);
$arr=json_decode($data,true);
if($arr['ret']==-7) {
throw new Exception('SKEY已失效!');
}
$isqqvip=$arr['recParam']['is_vip'];
return $isqqvip;
}
//修改QQ昵称
public function setnickname($nickname){
$url="https://h5.qzone.qq.com/proxy/domain/w.qzone.qq.com/cgi-bin/user/cgi_apply_updateuserinfo_new?g_tk=".$this->gtk;
$data="qzreferrer=http%3A%2F%2Fctc.qzs.qq.com%2Fqzone%2Fv6%2Fsetting%2Fprofile%2Fprofile.html%3Ftab%3Dbase&nickname=".urlencode($nickname)."&emoji=&sex=1&birthday=2015-01-01&province=0&city=PAR&country=FRA&marriage=6&bloodtype=5&hp=0&hc=PAR&hco=FRA&career=&company=&cp=0&cc=0&cb=&cco=0&lover=&islunar=0&mb=1&uin=".$this->uin."&pageindex=1&nofeeds=1&fupdate=1&format=json";
$return=get_curl($url,$data,$url,$this->cookie);
$arr=json_decode($return,true);
if(!$arr){
throw new Exception('更换昵称失败');
}elseif(isset($arr['code']) && $arr['code']==0){
return true;
}elseif($arr["code"] == -3000) {
session('qq_cookie_qzone', null);
throw new Exception('当前QQ登录状态已失效,请重新登录!');
}elseif(isset($arr['message'])){
throw new Exception($arr['message']);
}else{
throw new Exception('更换昵称失败');
}
}
public function set_online_status($model, $desc, $imei){
$pt4_token = getSubstr($this->cookie, 'pt4_token=', ';');
$ua = 'Mozilla/5.0 (Linux; Android 12; IN2010 Build/RKQ1.211119.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/97.0.4692.98 Mobile Safari/537.36 V1_AND_SQ_8.8.68_2538_YYB_D A_8086800 QQ/8.8.88 NetType/4G';
$data = json_encode(['13031'=>['req'=>['sModel'=>$model, 'iAppType'=>3, 'sIMei'=>$imei, 'sVer'=>'8.8.88', 'sManu'=>'', 'lUin'=>intval($this->uin), 'bShowInfo'=>true, 'sDesc'=>$desc, 'sModelShow'=> $model]]]);
$url = 'https://proxy.vac.qq.com/cgi-bin/srfentry.fcgi?ts='.time().'000&g_tk='.$this->gtk.'&data='.rawurlencode($data).'&pt4_token='.urlencode($pt4_token);
$data = get_curl($url, 0, 'https://proxy.vac.qq.com/', $this->cookie, 0, $ua);
$arr = json_decode($data, true);
if(!$arr){
throw new Exception('修改在线状态失败');
}elseif(isset($arr['ecode']) && $arr['ecode']==0){
if(isset($arr['13031']['ret']) && $arr['13031']['ret']==0){
return true;
}else{
throw new Exception('修改在线状态失败,'.$arr['13031']['msg']);
}
}else{
throw new Exception('修改在线状态失败,'.$data);
}
}
}
BIN
View File
Binary file not shown.
+370
View File
@@ -0,0 +1,370 @@
<?php
namespace app\lib;
use Exception;
// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
//
// @Author Lion <[email protected]>
// @Date 2022/06/21
class XdbSearcher
{
const HeaderInfoLength = 256;
const VectorIndexRows = 256;
const VectorIndexCols = 256;
const VectorIndexSize = 8;
const SegmentIndexSize = 14;
// xdb file handle
private $handle = null;
// header info
private $header = null;
private $ioCount = 0;
// vector index in binary string.
// string decode will be faster than the map based Array.
private $vectorIndex = null;
// xdb content buffer
private $contentBuff = null;
// ---
// static function to create searcher
/**
* @throws Exception
*/
public static function newWithFileOnly($dbFile)
{
return new XdbSearcher($dbFile, null, null);
}
/**
* @throws Exception
*/
public static function newWithVectorIndex($dbFile, $vIndex)
{
return new XdbSearcher($dbFile, $vIndex);
}
/**
* @throws Exception
*/
public static function newWithBuffer($cBuff)
{
return new XdbSearcher(null, null, $cBuff);
}
// --- End of static creator
/**
* initialize the xdb searcher
* @throws Exception
*/
function __construct($dbFile = null, $vectorIndex = null, $cBuff = null)
{
// check the content buffer first
if ($cBuff != null) {
$this->vectorIndex = null;
$this->contentBuff = $cBuff;
} else {
// 加载默认数据文件 by Anyon
if (is_null($dbFile)) {
$dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'ip2region.xdb';
}
// open the xdb binary file
$this->handle = fopen($dbFile, "r");
if ($this->handle === false) {
throw new Exception("failed to open xdb file '%s'", $dbFile);
}
$this->vectorIndex = $vectorIndex;
}
}
function close()
{
if ($this->handle != null) {
fclose($this->handle);
}
}
function getIOCount()
{
return $this->ioCount;
}
/**
* find the region info for the specified ip address
* @throws Exception
*/
function search($ip)
{
// check and convert the sting ip to a 4-bytes long
if (is_string($ip)) {
$t = self::ip2long($ip);
if ($t === null) {
throw new Exception("invalid ip address `$ip`");
}
$ip = $t;
}
// reset the global counter
$this->ioCount = 0;
// locate the segment index block based on the vector index
$il0 = ($ip >> 24) & 0xFF;
$il1 = ($ip >> 16) & 0xFF;
$idx = $il0 * self::VectorIndexCols * self::VectorIndexSize + $il1 * self::VectorIndexSize;
if ($this->vectorIndex != null) {
$sPtr = self::getLong($this->vectorIndex, $idx);
$ePtr = self::getLong($this->vectorIndex, $idx + 4);
} elseif ($this->contentBuff != null) {
$sPtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx);
$ePtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx + 4);
} else {
// read the vector index block
$buff = $this->read(self::HeaderInfoLength + $idx, 8);
if ($buff === null) {
throw new Exception("failed to read vector index at ${idx}");
}
$sPtr = self::getLong($buff, 0);
$ePtr = self::getLong($buff, 4);
}
// printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr);
// binary search the segment index to get the region info
$dataLen = 0;
$dataPtr = null;
$l = 0;
$h = ($ePtr - $sPtr) / self::SegmentIndexSize;
while ($l <= $h) {
$m = ($l + $h) >> 1;
$p = $sPtr + $m * self::SegmentIndexSize;
// read the segment index
$buff = $this->read($p, self::SegmentIndexSize);
if ($buff == null) {
throw new Exception("failed to read segment index at ${p}");
}
$sip = self::getLong($buff, 0);
if ($ip < $sip) {
$h = $m - 1;
} else {
$eip = self::getLong($buff, 4);
if ($ip > $eip) {
$l = $m + 1;
} else {
$dataLen = self::getShort($buff, 8);
$dataPtr = self::getLong($buff, 10);
break;
}
}
}
// match nothing interception.
// @TODO: could this even be a case ?
// printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr);
if ($dataPtr == null) {
return null;
}
// load and return the region data
$buff = $this->read($dataPtr, $dataLen);
if ($buff == null) {
return null;
}
return $buff;
}
// read specified bytes from the specified index
private function read($offset, $len)
{
// check the in-memory buffer first
if ($this->contentBuff != null) {
return substr($this->contentBuff, $offset, $len);
}
// read from the file
$r = fseek($this->handle, $offset);
if ($r == -1) {
return null;
}
$this->ioCount++;
$buff = fread($this->handle, $len);
if ($buff === false) {
return null;
}
if (strlen($buff) != $len) {
return null;
}
return $buff;
}
// --- static util functions ----
// convert a string ip to long
public static function ip2long($ip)
{
$ip = ip2long($ip);
if ($ip === false) {
return null;
}
// convert signed int to unsigned int if on 32 bit operating system
if ($ip < 0 && PHP_INT_SIZE == 4) {
$ip = sprintf("%u", $ip);
}
return $ip;
}
// read a 4bytes long from a byte buffer
public static function getLong($b, $idx)
{
$val = (ord($b[$idx])) | (ord($b[$idx + 1]) << 8)
| (ord($b[$idx + 2]) << 16) | (ord($b[$idx + 3]) << 24);
// convert signed int to unsigned int if on 32 bit operating system
if ($val < 0 && PHP_INT_SIZE == 4) {
$val = sprintf("%u", $val);
}
return $val;
}
// read a 2bytes short from a byte buffer
public static function getShort($b, $idx)
{
return ((ord($b[$idx])) | (ord($b[$idx + 1]) << 8));
}
// load header info from a specified file handle
public static function loadHeader($handle)
{
if (fseek($handle, 0) == -1) {
return null;
}
$buff = fread($handle, self::HeaderInfoLength);
if ($buff === false) {
return null;
}
// read bytes length checking
if (strlen($buff) != self::HeaderInfoLength) {
return null;
}
// return the decoded header info
return [
'version' => self::getShort($buff, 0),
'indexPolicy' => self::getShort($buff, 2),
'createdAt' => self::getLong($buff, 4),
'startIndexPtr' => self::getLong($buff, 8),
'endIndexPtr' => self::getLong($buff, 12)
];
}
// load header info from the specified xdb file path
public static function loadHeaderFromFile($dbFile)
{
$handle = fopen($dbFile, 'r');
if ($handle === false) {
return null;
}
$header = self::loadHeader($handle);
fclose($handle);
return $header;
}
// load vector index from a file handle
public static function loadVectorIndex($handle)
{
if (fseek($handle, self::HeaderInfoLength) == -1) {
return null;
}
$rLen = self::VectorIndexRows * self::VectorIndexCols * self::SegmentIndexSize;
$buff = fread($handle, $rLen);
if ($buff === false) {
return null;
}
if (strlen($buff) != $rLen) {
return null;
}
return $buff;
}
// load vector index from a specified xdb file path
public static function loadVectorIndexFromFile($dbFile)
{
$handle = fopen($dbFile, 'r');
if ($handle === false) {
return null;
}
$vIndex = self::loadVectorIndex($handle);
fclose($handle);
return $vIndex;
}
// load the xdb content from a file handle
public static function loadContent($handle)
{
if (fseek($handle, 0, SEEK_END) == -1) {
return null;
}
$size = ftell($handle);
if ($size === false) {
return null;
}
// seek to the head for reading
if (fseek($handle, 0) == -1) {
return null;
}
$buff = fread($handle, $size);
if ($buff === false) {
return null;
}
// read length checking
if (strlen($buff) != $size) {
return null;
}
return $buff;
}
// load the xdb content from a file path
public static function loadContentFromFile($dbFile)
{
$str = file_get_contents($dbFile, false);
if ($str === false) {
return null;
} else {
return $str;
}
}
public static function now()
{
return (microtime(true) * 1000);
}
}