Initial commit
This commit is contained in:
@@ -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'=>'MP3(192K)'];
|
||||
}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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user