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
+1
View File
@@ -0,0 +1 @@
deny from all
+22
View File
@@ -0,0 +1,22 @@
<?php
declare (strict_types = 1);
namespace app;
use think\Service;
/**
* 应用服务类
*/
class AppService extends Service
{
public function register()
{
// 服务注册
}
public function boot()
{
// 服务启动
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
declare (strict_types = 1);
namespace app;
use think\App;
use think\exception\ValidateException;
use think\Validate;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace app;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;
use Throwable;
/**
* 应用异常处理类
*/
class ExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];
/**
* 记录异常信息(包括日志或者其它方式记录)
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// 添加自定义异常处理机制
// 其他错误交给系统处理
return parent::render($request, $e);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace app;
use think\facade\View;
abstract class Plugin
{
protected $plugin;
protected $clientip;
public function __construct()
{
}
public function initialize($plugin){
$this->plugin = $plugin;
$this->clientip = real_ip();
}
protected function view($tpl = null){
if($tpl == null) $tpl = strtolower(request()->param("method", "index"));
$template = plugin_path_get($this->plugin['class']) . '/'.$tpl.'.html';
View::assign("plugin", $this->plugin);
return view($template);
}
public function alert($code, $msg = '', $url = null, $wait = 3)
{
if ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : (string)$this->app->route->buildUrl($url);
}
if(empty($msg)) $msg = '未知错误';
View::assign([
'code' => $code,
'msg' => $msg,
'url' => $url,
'wait' => $wait,
]);
return View::fetch(app()->getRootPath().'view/dispatch_jump.html');
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace app;
// 应用请求对象类
class Request extends \think\Request
{
}
+28
View File
@@ -0,0 +1,28 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
class Cron extends Command
{
protected function configure()
{
// 指令配置
$this->setName('cron')
->setDescription('定时数据清理任务');
}
protected function execute(Input $input, Output $output)
{
// 指令输出
Db::name('querycache')->where('uptime','<',date('Y-m-d H:i:s', strtotime('-30 days')))->delete();
$output->writeln('定时数据清理任务执行完毕');
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
declare (strict_types=1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
use ZipArchive;
class PluginPackage extends Command
{
protected function configure()
{
// 指令配置
$this->setName('plugin:package')
->addArgument('space', Argument::REQUIRED, '插件域,例如:utility')
->setDescription('打包指定域的所有插件');
}
protected function execute(Input $input, Output $output)
{
$space = $input->getArgument('space');
// 指令输出
$rootPath = app()->getRootPath() . '/plugin/';
if (!is_dir($rootPath . $space)) {
$output->writeln("该域不存在:[$space]");
return;
}
$plugins = glob($rootPath . $space . '/*');
$output->writeln("正在打包:[$space]下的文件");
foreach ($plugins as $plugin) {
$zip = new ZipArchive();
$filename = $rootPath . 'output/' . $space . DIRECTORY_SEPARATOR . basename($plugin) . '.zip';
if (!is_dir(dirname($filename))) {
mkdir(dirname($filename), 0777, true);
}
if ($zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
$tree_relative = tree_relative($plugin);
$files = multi2one($tree_relative, '', '/');
foreach ($files as $file) {
if ($file !== '.' && $file !== '..') {
$zip->addFile($plugin . '/' . $file, $space . '/' . basename($plugin) . '/' . $file);
}
}
}
$zip->close();
$output->writeln("打包成功:[$filename]");
}
$output->writeln("该域所有文件都已打包完成:[$space]");
}
}
+476
View File
@@ -0,0 +1,476 @@
<?php
// 应用公共文件
use think\facade\Db;
ini_set("display_errors", 1);
function template_path_get(): string
{
return app()->getRootPath() . config("view.view_dir_name") . '/index/' . config_get('template') . DIRECTORY_SEPARATOR;
}
function plugin_alias_get()
{
return trim(request()->param("alias"), '\\/');
}
function plugin_method_get()
{
return request()->param("method", "index");
}
function plugin_current_class_get($namespace)
{
return str_replace('plugin\\', '', $namespace);
}
function plugin_path_get($class = '')
{
$class = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $class);
return realpath(app()->getRootPath() . "/plugin/$class");
}
function plugin_info_get($alias = '')
{
$plugin = Db::name('plugin')->where('alias',$alias)->where('enable',1)->find();
if(!$plugin) return null;
if(!plugin_userlevel($plugin['level'])) return null;
$plugin['is_star'] = 0;
if(request()->islogin){
$stars = explode(',', request()->user['stars']);
if(in_array($plugin['id'], $stars)){
$plugin['is_star'] = 1;
}
}
return $plugin;
}
function msg($status = "ok", $message = "success", $data = [])
{
return json([
"status" => $status,
"message" => $message,
"data" => $data,
]);
}
function reset_opcache()
{
if (function_exists('opcache_reset')) opcache_reset();
}
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5($key);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if(((int)substr($result, 0, 10) == 0 || (int)substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.str_replace('=', '', base64_encode($result));
}
}
function get_curl($url, $post=0, $referer=0, $cookie=0, $header=0, $ua=0, $nobody=0, $addheader=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: close";
if($addheader){
$httpheader = array_merge($httpheader, $addheader);
}
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, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36");
}
if ($nobody) {
curl_setopt($ch, CURLOPT_NOBODY, 1);
}
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
function jsonp_decode($jsonp, $assoc = false)
{
$jsonp = trim($jsonp);
if(isset($jsonp[0]) && $jsonp[0] !== '[' && $jsonp[0] !== '{') {
$begin = strpos($jsonp, '(');
if(false !== $begin)
{
$end = strrpos($jsonp, ')');
if(false !== $end)
{
$jsonp = substr($jsonp, $begin + 1, $end - $begin - 1);
}
}
}
return json_decode($jsonp, $assoc);
}
function dgmdate($timestamp, $d_format = 'Y-m-d H:i') {
$timestamp=strtotime($timestamp);
$timestamp += 8 * 3600;
$todaytimestamp = time() - (time() + 8 * 3600) % 86400 + 8 * 3600;
$s = gmdate($d_format, $timestamp);
$time = time() + 8 * 3600 - $timestamp;
if($timestamp >= $todaytimestamp) {
if($time > 3600) {
return '<span title="'.$s.'">'.intval($time / 3600).'&nbsp;小时前</span>';
} elseif($time > 1800) {
return '<span title="'.$s.'">半小时前</span>';
} elseif($time > 60) {
return '<span title="'.$s.'">'.intval($time / 60).'&nbsp;分钟前</span>';
} elseif($time > 0) {
return '<span title="'.$s.'">'.$time.'&nbsp;秒前</span>';
} elseif($time == 0 || $time < 0) {
return '<span title="'.$s.'">刚刚</span>';
} else {
return $s;
}
} elseif(($days = intval(($todaytimestamp - $timestamp) / 86400)) >= 0 && $days < 7) {
if($days == 0) {
return '<span title="'.$s.'">昨天&nbsp;'.gmdate('H:i', $timestamp).'</span>';
} elseif($days == 1) {
return '<span title="'.$s.'">前天&nbsp;'.gmdate('H:i', $timestamp).'</span>';
} else {
return '<span title="'.$s.'">'.($days + 1).'&nbsp;天前</span>';
}
} else {
return $s;
}
}
function unzip($filepath, $filename)
{
if (!file_exists($filepath)) {
return false;
}
$zip = new ZipArchive;
if ($zip->open($filepath) === true) {
$zip->extractTo($filename);
$zip->close();
return true;
}
return false;
}
//多维转一维数组
function multi2one($data, $dir = '', $step = '')
{
$list = [];
foreach ($data as $k => $v) {
if (is_array($v)) {
$list = array_merge($list, multi2one($v, $dir . $step . $k, $step));
} else {
$list[] = ltrim($dir . $step . $v, '\\/');
}
}
return $list;
}
function tree_relative($dir)
{
if (!is_dir($dir)) {
return [basename($dir)];
}
$arr = [];
$scandir = scandir($dir);
foreach ($scandir as $v) {
if ($v != '.' && $v != '..') {
if (is_dir("$dir/$v")) {
$arr[$v] = tree_relative("$dir/$v");
} else {
$arr[] = $v;
}
}
}
return $arr;
}
function copy_dir($src, $target)
{
if (!is_dir($target)) {
mkdir($target, 0777, true);
}
foreach (glob($src . '/*') as $filename) {
$targetFilename = $target . '/' . basename($filename);
if (is_dir($filename)) {
// 如果是目录,递归合并子目录下的文件。
copy_dir($filename, $targetFilename);
} elseif (is_file($filename)) {
copy($filename, $targetFilename);
}
}
}
function del_tree($dir)
{
if (!file_exists($dir)) {
return true;
}
$files = array_diff(scandir($dir), array('.', '..'));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? del_tree("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}
function config_get($key, $default = null)
{
$value = config('sys.'.$key);
return $value ?: $default;
}
function config_set($key, $value)
{
$res = Db::name('config')->replace()->insert(['key'=>$key, 'value'=>$value]);
return $res!==false;
}
function get_version()
{
return VERSION;
}
function format_date($timestamp = null)
{
if ($timestamp === null) {
$timestamp = time();
}
return date('Y-m-d H:i:s', $timestamp);
}
//当前命名空间的包名
function base_space_name($space)
{
$str_replace = str_replace('\\', '/', $space);
return basename($str_replace);
}
if (!function_exists('str_starts_with')) {
function str_starts_with($str, $start)
{
return (@substr_compare($str, $start, 0, strlen($start)) == 0);
}
}
if (!function_exists('str_ends_with')) {
function str_ends_with(string $haystack, string $needle): bool
{
$needle_len = strlen($needle);
return ($needle_len === 0 || 0 === substr_compare($haystack, $needle, -$needle_len));
}
}
if (!function_exists('is_valid_url')) {
function is_valid_url($url = null)
{
if (empty($url)) return false;
if (!is_string($url)) return false;
$filter_var = boolval(filter_var($url, FILTER_VALIDATE_URL));
if ($filter_var) return $filter_var;
$parse_url = parse_url($url);
$path = array_pop($parse_url);
$url = str_ireplace($path, '/' . urlencode($path), $url);
return boolval(filter_var($url, FILTER_VALIDATE_URL));
}
}
function real_ip($type=0){
$ip = $_SERVER['REMOTE_ADDR'];
if($type<=0 && isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
foreach ($matches[0] AS $xip) {
if (filter_var($xip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $xip;
break;
}
}
} elseif ($type<=0 && isset($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif ($type<=1 && isset($_SERVER['HTTP_CF_CONNECTING_IP']) && filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif ($type<=1 && isset($_SERVER['HTTP_ALI_CDN_REAL_IP']) && filter_var($_SERVER['HTTP_ALI_CDN_REAL_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_ALI_CDN_REAL_IP'];
} elseif ($type<=1 && isset($_SERVER['HTTP_X_REAL_IP']) && filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
return $ip;
}
function get_ip_city($ip){
$new = new \app\lib\IpLocation();
$arr = $new->getlocation($ip);
if($arr){
return $arr['province'].$arr['city'];
}else{
return false;
}
}
function get_plugin_url($alias){
if(substr($alias,0,1) == '/' || substr($alias,0,7) == 'http://' || substr($alias,0,8) == 'https://'){
$url = $alias;
}else{
$url = '/'.$alias;
}
return $url;
}
//极验3.0服务端验证
function verify_captcha(){
if(session('gtserver') === null)return '验证加载失败';
$GtSdk = new \app\lib\GeetestLib(config_get('captcha_id'), config_get('captcha_key'));
$data = array(
'user_id' => request()->islogin?request()->user['id']:'public',
'client_type' => "web",
'ip_address' => real_ip()
);
if (session('gtserver') == 1) { //服务器正常
if ($GtSdk->success_validate(input('post.geetest_challenge'), input('post.geetest_validate'), input('post.geetest_seccode'), $data)) {
return true;
}
}else{ //服务器宕机,走failback模式
if ($GtSdk->fail_validate(input('post.geetest_challenge'), input('post.geetest_validate'), input('post.geetest_seccode'))) {
return true;
}
}
return '验证失败,请重新验证';
}
//极验4.0服务端验证(无感)
function verify_captcha4(){
if(!input('?post.captcha_id') || !input('?post.lot_number') || !input('?post.pass_token') || !input('?post.gen_time') || !input('?post.captcha_output')) return false;
$real_ip = real_ip();
$url = 'http://gt4.geetest.com/demov4/demo/login';
$param = ['captcha_id'=>input('post.captcha_id'), 'lot_number'=>input('post.lot_number'), 'pass_token'=>input('post.pass_token'), 'gen_time'=>input('post.gen_time'), 'captcha_output'=>input('post.captcha_output')];
$referer = 'http://gt4.geetest.com/demov4/invisible-bind-zh.html';
$httpheader[] = "X-Real-IP: ".$real_ip;
$httpheader[] = "X-Forwarded-For: ".$real_ip;
$data = get_curl($url.'?'.http_build_query($param),0,$referer,0,0,0,0,$httpheader);
$arr = json_decode($data, true);
if(isset($arr['result']) && $arr['result'] == 'success'){
return true;
}
return false;
}
//极验4.0服务端验证(滑动)
function verify_captcha4_slide(){
return verify_captcha4();
if(!input('?post.captcha_id') || !input('?post.lot_number') || !input('?post.pass_token') || !input('?post.gen_time') || !input('?post.captcha_output')) return false;
$url = 'http://gcaptcha4.geetest.com/validate?captcha_id='.input('post.captcha_id');
$param = ['lot_number'=>input('post.lot_number'), 'pass_token'=>input('post.pass_token'), 'gen_time'=>input('post.gen_time'), 'captcha_output'=>input('post.captcha_output')];
$param['sign_token'] = hash_hmac('sha256', $param['lot_number'], config_get('captcha_key'));
$data = get_curl($url, http_build_query($param));
$arr = json_decode($data, true);
if(isset($arr['status']) && $arr['status']=='success'){
if(isset($arr['result']) && $arr['result'] == 'success'){
return true;
}else{
return '验证失败,'.$arr['reason'];
}
}else{
return '验证失败,'.($arr['msg']?$arr['msg']:'请重新验证');
}
}
function checkdomain($domain){
if(empty($domain))return false;
if (!preg_match('/^[a-zA-Z0-9:\_\.\-]{2,512}$/i', $domain) || strpos($domain, '.') === false || substr($domain, -1) == '.' || substr($domain, 0 ,1) == '.' || strpos($domain, '*') !== false) {
return false;
}
return true;
}
/**
* 取中间文本
* @param string $str
* @param string $leftStr
* @param string $rightStr
*/
function getSubstr($str, $leftStr, $rightStr)
{
$left = strpos($str, $leftStr);
$start = $left+strlen($leftStr);
$right = strpos($str, $rightStr, $start);
if($left < 0) return '';
if($right>0){
return substr($str, $start, $right-$start);
}else{
return substr($str, $start);
}
}
function plugin_userlevel($level){
if($level > 0){
if(!request()->islogin) return false;
if(request()->user['level']<$level) return false;
}
return true;
}
function checkRefererHost(){
if(!request()->header('referer'))return false;
$url_arr = parse_url(request()->header('referer'));
$http_host = request()->header('host');
if(strpos($http_host,':'))$http_host = substr($http_host, 0, strpos($http_host, ':'));
return $url_arr['host'] === $http_host;
}
+220
View File
@@ -0,0 +1,220 @@
<?php
namespace app\controller;
use think\facade\Db;
use think\facade\Request;
use think\facade\Session;
use think\facade\View;
use app\lib\Oauth;
use think\helper\Str;
class Auth extends Base
{
public function login()
{
if(request()->islogin) {
return $this->alert('success', '已登录', '/');
}
View::assign(['is_qq'=>config_get('oauth_openqq'), 'is_wx'=>config_get('oauth_openxw')]);
return view();
}
private function oauth_config(){
return [
'apiurl' => config_get('oauth_appurl'),
'appid' => config_get('oauth_appid'),
'appkey' => config_get('oauth_appkey'),
'callback' => (string)url('/oauth/callback', [], '', true),
];
}
public function oauth()
{
$type = input('post.type');
if(!$type){
return msg('error', '登录方式不能为空');
}
$state = md5(uniqid(rand(), TRUE));
session('oauth_state', $state);
$oauth = new Oauth($this->oauth_config());
$res = $oauth->login($type, $state);
if(isset($res['code']) && $res['code']==0){
return msg('ok', 'success', $res['url']);
}else{
return msg('error', $res ? $res['msg'] : '登录地址获取失败');
}
}
public function callback()
{
$code = input('get.code');
$state = input('get.state');
if (empty($code)) {
return $this->alert('error', 'code不能为空', '/login');
}
if(!$state || $state != session('oauth_state')){
return $this->alert('error', 'state校验失败,请重新登录', '/login');
}
$oauth = new Oauth($this->oauth_config());
$res = $oauth->callback($code);
if(isset($res['code']) && $res['code']==0){
$type = $res['type'];
$openid = $res['social_uid'];
if(empty($res['nickname'])) $res['nickname'] = $type.Str::random(5);
$user = Db::name('user')->where('type', $type)->where('openid', $openid)->find();
if($user){
if($user['enable']==0){
session('user_block', '1');
return $this->alert('error', '当前用户已被禁止登录', '/');
}
$uid = $user['id'];
$password = $user['password'];
Db::name('user')->where('id', $uid)->update([
'avatar_url' => $res['faceimg'],
'loginip' => $this->clientip,
'update_time' => date('Y-m-d H:i:s')
]);
if(session('user_block') == '1'){
Db::name('user')->where('id', $uid)->update(['enable' => 0]);
return $this->alert('error', '当前用户已被禁止登录', '/');
}
}else{
$password = Str::random(16);
$uid = Db::name('user')->insertGetId([
'type' => $type,
'openid' => $openid,
'username' => $res['nickname'],
'password' => $password,
'avatar_url' => $res['faceimg'],
'regip' => $this->clientip,
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s')
]);
if(session('user_block') == '1'){
Db::name('user')->where('id', $uid)->update(['enable' => 0]);
return $this->alert('error', '当前用户已被禁止登录', '/');
}
}
$session = md5($uid.$password);
$expiretime = time()+30744000;
$token = authcode("{$uid}\t{$session}\t{$expiretime}", 'ENCODE', config_get('syskey'));
cookie('user_token', $token, ['expire' => $expiretime, 'httponly' => true]);
session('oauth_state', null);
return redirect("/");
}else{
return $this->alert('error', $res ? $res['msg'] : '登录数据获取失败');
}
}
public function logout()
{
//session(null);
cookie('user_token', null);
return redirect(request()->header('referer') ?? '/');
}
public function verifycode()
{
return captcha();
}
public function adminlogin(){
$username = input('post.username',null,'trim');
$password = input('post.password',null,'trim');
$captcha = input('post.captcha',null,'trim');
if(empty($username) || empty($password)){
return msg('error', '用户名或密码不能为空');
}
if(!captcha_check($captcha)){
return msg('error', '验证码错误');
}
if($username == config_get('admin_username') && password_verify($password, config_get('admin_password'))){
$session = md5($username.config_get('admin_password'));
$expiretime = time()+2562000;
$token = authcode("{$username}\t{$session}\t{$expiretime}", 'ENCODE', config_get('syskey'));
cookie('admin_token', $token, ['expire' => $expiretime, 'httponly' => true]);
config_set('admin_lastlogin', date('Y-m-d H:i:s'));
return msg();
}else{
return msg('error', '用户名或密码错误');
}
}
public function adminlogout()
{
cookie('admin_token', null);
return redirect('/admin/login.html');
}
public function qqlogin_api(){
$do = input('get.do');
$type = input('get.type');
$info = $this->getQqloginInfo($type);
if(!$info){
return json(['saveOK'=>1, 'msg'=>'该登录类型不存在']);
}
$login = new \app\lib\QQLogin();
if($do == 'getqrpic'){
$array = $login->getqrpic($info[0]);
}
elseif($do == 'qrlogin'){
$array = $login->qrlogin($info[0], $info[1], input('get.qrsig'));
if($array['saveOK'] == 0){
$cookie = ['uin' => $array['uin'], 'cookie' => $array['cookie'], 'nickname' => $array['nickname']];
session('qq_cookie_'.$type, $cookie);
}
}
return json($array);
}
public function qqlogin(){
if(!checkRefererHost()){
return redirect('/');
}
$redirect = input('get.redirect');
$type = input('get.type');
if(!$redirect || !$type){
return $this->alert('error', '缺少参数', '/');
}
$info = $this->getQqloginInfo($type);
if(!$info){
return $this->alert('error', '该登录类型不存在', '/');
}
if (substr($redirect,0,1)!='/') {
return $this->alert('error', '回调地址错误', '/');
}
View::assign('logintype', base64_encode($type));
View::assign('loginname', base64_encode($info[2]));
View::assign('redirect', base64_encode($redirect));
return view();
}
private function getQqloginInfo($type){
switch($type){
case 'qzone':
return ['5','https://qzs.qq.com/qzone/v5/loginsucc.html?para=izone', 'QQ空间'];
break;
case 'qun':
return ['73','https://qun.qq.com/', 'QQ群管理'];
break;
case 'qqid':
return ['1','https://id.qq.com/index.html' ,'我的QQ中心'];
break;
default:
return null;
break;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace app\controller;
use app\BaseController;
use think\facade\View;
class Base extends BaseController
{
protected $clientip;
protected function initialize()
{
$this->clientip = real_ip(config_get('ip_type')??0);
parent::initialize();
}
protected function alert($code, $msg = '', $url = null, $wait = 3)
{
if ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : (string)$this->app->route->buildUrl($url);
}
if(empty($msg)) $msg = '未知错误';
View::assign([
'code' => $code,
'msg' => $msg,
'url' => $url,
'wait' => $wait,
]);
return View::fetch(app()->getRootPath().'view/dispatch_jump.html');
}
}
+226
View File
@@ -0,0 +1,226 @@
<?php
namespace app\controller;
use think\facade\Db;
use think\facade\View;
use think\facade\Validate;
class Index extends Base
{
const CACHE_TIME = 0;
public function index()
{
$category = Db::name('category')->cache('categorys', self::CACHE_TIME)->field('id,title,icon')->where('enable', 1)->order('weight','desc')->select();
$link = Db::name('link')->cache('links', self::CACHE_TIME)->field('id,name,url')->where('enable', 1)->order('weight','desc')->select();
$tool = Db::name('plugin')->cache('plugins', self::CACHE_TIME)->field('id,title,alias,keyword,request_count,category_id,level')->where('enable', 1)->order('weight','desc')->select();
$list = [];
foreach($category as $item){
$list2 = [];
foreach($tool as $row){
if(!plugin_userlevel($row['level'])) continue;
if($row['category_id'] == $item['id']){
$row['url'] = get_plugin_url($row['alias']);
$row['out'] = substr($row['alias'],0,1) == '/' || substr($row['alias'],0,7) == 'http://' || substr($row['alias'],0,8) == 'https://';
$list2[] = $row;
}
}
$list[] = ['id'=>$item['id'], 'title'=>$item['title'], 'icon'=>$item['icon'], 'items'=>$list2];
}
View::assign('category', $category);
View::assign('tool', $list);
View::assign('link', $link);
return view();
}
public function stars()
{
if(request()->isAjax()){
if(!request()->islogin){
return msg('error', '登录后才能收藏工具');
}
$uid = request()->user['id'];
$do = input('post.do');
if($do == 'clear'){
Db::name('user')->where('id', $uid)->update(['stars'=>'']);
return msg();
}
$id = input('post.id/d');
if(empty($do) || empty($id)) return msg('error', 'param error');
$plugin = Db::name('plugin')->where('id', $id)->where('enable',1)->find();
if(!$plugin) return msg('error', '工具不存在');
$stars = explode(',',request()->user['stars']);
if ($do == 'add' && !in_array($id, $stars)) {
array_push($stars, $id);
} elseif ($do == 'del') {
if (($key = array_search($id, $stars)) !== false) {
unset($stars[$key]);
}
}
$stars = implode(',', array_unique(array_values($stars)));
Db::name('user')->where('id', $uid)->update(['stars'=>$stars]);
return msg('ok', $do == 'add' ? '添加收藏成功!' : '取消收藏成功!');
}
if(!request()->islogin){
return $this->alert('info', '请先登录', '/login');
}
$category = Db::name('category')->cache('categorys', self::CACHE_TIME)->field('id,title,icon')->where('enable', 1)->order('weight','desc')->select();
$list = [];
$stars = request()->user['stars'];
if(strlen($stars)>0){
$stars = explode(',',$stars);
$tool = Db::name('plugin')->cache('plugins', self::CACHE_TIME)->field('id,title,alias,keyword,request_count,category_id')->where('enable', 1)->order('weight','desc')->select();
$list = [];
foreach($category as $item){
foreach($tool as $row){
if(!plugin_userlevel($row['level'])) continue;
if($row['category_id'] == $item['id'] && in_array($row['id'],$stars)){
$row['url'] = get_plugin_url($row['alias']);
$row['out'] = substr($row['alias'],0,1) == '/' || substr($row['alias'],0,7) == 'http://' || substr($row['alias'],0,8) == 'https://';
$list[] = $row;
}
}
}
}
View::assign('category', $category);
View::assign('tool', $list);
return view();
}
public function history()
{
if(request()->isAjax()){
$do = input('post.do');
if($do == 'clear'){
cookie('tools', null);
return msg();
}
}
$category = Db::name('category')->cache('categorys', self::CACHE_TIME)->field('id,title,icon')->where('enable', 1)->order('weight','desc')->select();
$list = [];
$history = cookie('tools');
if(strlen($history)>0){
$history = array_reverse(array_unique(explode(',',$history)));
$tool = Db::name('plugin')->cache('plugins', self::CACHE_TIME)->field('id,title,alias,keyword,request_count,category_id')->where('enable', 1)->order('weight','desc')->select();
$newtool = [];
foreach($tool as $row){
$newtool[$row['id']] = $row;
}
$list = [];
foreach($history as $id){
$row = $newtool[$id];
if(!$row) continue;
if(!plugin_userlevel($row['level'])) continue;
$row['url'] = get_plugin_url($row['alias']);
$row['out'] = substr($row['alias'],0,1) == '/' || substr($row['alias'],0,7) == 'http://' || substr($row['alias'],0,8) == 'https://';
$list[] = $row;
}
}
View::assign('category', $category);
View::assign('tool', $list);
return view();
}
public function comment(){
if(request()->isAjax()){
$do = input('param.do');
if($do == 'add'){
if(!request()->islogin){
return msg('error', '登录后才能提交留言');
}
$email = input('post.email', null, 'trim,strip_tags,htmlspecialchars');
$content = input('post.content', null, 'trim,strip_tags,htmlspecialchars');
$data = [
'uid' => request()->user['id'],
'email' => $email,
'content' => $content,
'enable' => 0,
'create_time' => date("Y-m-d H:i:s"),
'update_time' => date("Y-m-d H:i:s")
];
$validate = Validate::rule([
'email|电子邮箱' => 'require|email',
'content|留言内容' => 'require',
]);
if (!$validate->check($data)) {
return msg('error', $validate->getError());
}
$captcha_result = verify_captcha4_slide();
if($captcha_result !== true){
return msg('error', $captcha_result);
}
$last = Db::name('comment')->where('uid', request()->user['id'])->order('id','desc')->find();
if($last && time() - strtotime($last['create_time']) < 600){
return msg('error', '你发表留言的速度太快了,过段时间再来吧');
}
Db::name('comment')->insert($data);
return msg();
}else{
$page = input('get.page/d');
$limit = 5;
$uid = request()->islogin ? request()->user['id'] : 0;
$select = Db::name('comment')->alias('A')->leftJoin('user B', 'A.uid=B.id')->field('A.id,A.uid,content,reply,A.enable,A.create_time,A.update_time,B.username,B.avatar_url')->where('A.enable', 1)->whereOr('A.uid', $uid);
$total = $select->count();
$comment = $select->order('id','desc')->page($page, $limit)->select();
$items = [];
foreach($comment as $item){
if(!$item['avatar_url']) $item['avatar_url'] = '/static/images/user.png';
$item['time'] = dgmdate($item['create_time']);
$items[] = $item;
}
return msg('ok', 'success', ['total'=>$total, 'page'=>$page, 'pagenum'=>ceil($total/$limit), 'items'=>$items]);
}
}
return view();
}
public function captcha(){
$GtSdk = new \app\lib\GeetestLib(config_get('captcha_id'), config_get('captcha_key'));
$data = array(
'user_id' => request()->islogin?request()->user['id']:'public',
'client_type' => "web",
'ip_address' => $this->clientip
);
$result = $GtSdk->pre_process($data);
session('gtserver', $result['success']);
return json($result);
}
public function statistics(){
$id = input('post.id/d');
if(!$id) return msg('error', 'param error');
$plugin = Db::name('plugin')->where('id', $id)->where('enable', 1)->find();
if(!$plugin) return msg('error', '工具不存在');
Db::name('plugin')->where('id', $id)->inc('request_count')->update();
$history = cookie('tools');
if(!$history) $history = [];
else $history = array_unique(explode(',',$history));
if(in_array($id, $history)){
$key = array_search($id,$history);
unset($history[$key]);
}
$history[] = $id;
if(count($history) > 20){
$history = array_splice($history, 0, 20);
}
cookie('tools', implode(',', $history));
return msg();
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
namespace app\controller;
use app\lib\EnvOperation;
use app\lib\ExecSQL;
use PDO;
use think\Exception;
use think\facade\Cache;
use think\facade\Request;
use think\facade\Validate;
use think\facade\View;
use think\helper\Str;
class Install extends Base
{
public function __destruct()
{
Cache::clear();
reset_opcache();
}
public function initialize()
{
// 检测是否已安装
if (file_exists(app()->getRootPath() . 'install.lock')) {
exit('你已安装成功,需要重新安装请删除 install.lock 文件');
}
Cache::clear();
reset_opcache();
}
public function index()
{
// 检查安装环境
$requirements = [
'PHP >= 7.4' => PHP_VERSION >= 7.4,
'PDO_MySQL' => extension_loaded("pdo_mysql"),
'CURL' => extension_loaded("curl"),
'ZipArchive' => class_exists("ZipArchive"),
'runtime写入权限' => is_writable(app()->getRuntimePath()),
];
reset_opcache();
$step = Request::param('step');
View::assign([
'step' => $step,
'requirements' => $requirements,
]);
return view('../view/install/index.html');
}
public function database()
{
$params = Request::param();
$rules = [
'hostname' => 'require',
'hostport' => 'require|integer',
'username' => 'require',
'password' => 'require',
'database' => 'require',
];
$validate = Validate::rule($rules);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$dsn = 'mysql:host=' . $params['hostname'] . ';dbname=' . $params['database'] . ';port=' . $params['hostport'] . ';charset=utf8';
try {
new PDO($dsn, $params['username'], $params['password']);
} catch (\Exception $e) {
return msg('error', $e->getMessage());
}
try {
$envFile = file_get_contents(app()->getRootPath() . '.env.example');
$envOperation = new EnvOperation($envFile);
foreach (array_keys($rules) as $value) {
$envOperation->set(mb_strtoupper($value), $params[$value]);
}
$envOperation->save();
} catch (\Exception $e) {
return msg('error', $e->getMessage());
}
return msg();
}
public function init_data()
{
try {
$filename = app()->getRootPath() . 'install.sql';
if (!is_file($filename)) {
throw new Exception('数据库 install.sql 文件不存在');
}
$install_sql = file($filename);
//写入数据库
$execSQL = new ExecSQL();
$install_sql = $execSQL->purify($install_sql);
foreach ($install_sql as $sql) {
$execSQL->exec($sql);
if (!empty($execSQL->getErrors())) {
throw new Exception($execSQL->getErrors()[0]);
}
}
} catch (\Exception $e) {
return msg('error', $e->getMessage());
}
return msg();
}
public function admin()
{
$params = Request::param();
$rules = [
'username|用户名' => 'require',
'password|密码' => 'require',
];
$validate = Validate::rule($rules);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
config_set("admin_username", $params['username']);
config_set("admin_password", password_hash($params['password'], PASSWORD_DEFAULT));
config_set("syskey", Str::random(16));
file_put_contents(app()->getRootPath() . 'install.lock', format_date());
return msg();
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
namespace app\controller\admin;
use app\controller\Base;
use think\facade\Request;
use think\facade\Validate;
use think\facade\Db;
class Category extends Base
{
public function list()
{
$params = Request::param();
$validate = Validate::rule([
'page' => 'integer',
'limit' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$page = isset($params['page']) ? intval($params['page']) : 1;
$limit = isset($params['limit']) ? intval($params['limit']) : 50;
$select = Db::name('category');
if(!empty($params['title'])){
$select->where('title', 'like', '%' . $params['title'] . '%');
}
$total = $select->count();
$items = $select->order('weight','desc')->page($page, $limit)->select();
return msg("ok", "success", ['total'=>$total, 'items'=>$items]);
}
public function get()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$item = Db::name('category')->where('id', $params['id'])->findOrEmpty();
return msg('ok', 'success', $item);
}
public function add()
{
$params = Request::param();
$validate = Validate::rule([
'title|分类标题' => 'require|unique:category',
'icon|小图标' => 'require',
'weight|权重' => 'require|integer',
'enable' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('category')->cache('categorys')->insert([
'title' => trim($params['title']),
'icon' => trim($params['icon']),
'weight' => $params['weight'],
'enable' => $params['enable'],
'create_time' => date("Y-m-d H:i:s"),
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function edit()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'icon|小图标' => 'require',
'title|分类标题' => 'require|unique:category',
'weight|权重' => 'require|integer',
'enable' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('category')->cache('categorys')->where('id', $params['id'])->update([
'title' => trim($params['title']),
'icon' => trim($params['icon']),
'weight' => $params['weight'],
'enable' => $params['enable'],
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function enable()
{
$id = input('post.id/d');
$enable = input('post.enable/d');
Db::name('category')->cache('categorys')->where('id', $id)->update([
'enable' => $enable,
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function delete()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('category')->cache('categorys')->where('id', $params['id'])->delete();
return msg();
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
namespace app\controller\admin;
use app\controller\Base;
use think\facade\Request;
use think\facade\Validate;
use think\facade\Db;
class Comment extends Base
{
public function list()
{
$params = Request::param();
$validate = Validate::rule([
'page' => 'integer',
'limit' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$page = intval($params['page']);
$limit = intval($params['limit']);
$select = Db::name('comment');
if(!empty($params['uid'])){
$select->where('uid', $params['uid']);
}
if(!empty($params['content'])){
$select->where('content', 'like', '%' . $params['content'] . '%');
}
$total = $select->count();
$items = $select->order('id','desc')->page($page, $limit)->select();
return msg("ok", "success", ['total'=>$total, 'items'=>$items]);
}
public function get()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$item = Db::name('comment')->where('id', $params['id'])->findOrEmpty();
return msg('ok', 'success', $item);
}
public function edit()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'content|留言内容' => 'require',
'enable' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('comment')->where('id', $params['id'])->update([
'content' => $params['content'],
'reply' => $params['reply'],
'enable' => $params['enable'],
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function enable()
{
$id = input('post.id/d');
$enable = input('post.enable/d');
Db::name('comment')->where('id', $id)->update([
'enable' => $enable,
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function delete()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('comment')->where('id', $params['id'])->delete();
return msg();
}
public function uploadlog()
{
$params = Request::param();
$validate = Validate::rule([
'page' => 'integer',
'limit' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$page = intval($params['page']);
$limit = intval($params['limit']);
$select = Db::name('uploadlog');
if(!empty($params['uid'])){
$select->where('uid', $params['uid']);
}
if(!empty($params['ip'])){
$select->where('ip', $params['ip']);
}
if(!empty($params['fileurl'])){
$select->where('fileurl', $params['fileurl']);
}
$total = $select->count();
$items = $select->order('id','desc')->page($page, $limit)->select();
return msg("ok", "success", ['total'=>$total, 'items'=>$items]);
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
namespace app\controller\admin;
use app\controller\Base;
use think\facade\Request;
use think\facade\Validate;
use think\facade\Db;
class Link extends Base
{
public function list()
{
$params = Request::param();
$validate = Validate::rule([
'page' => 'integer',
'limit' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$page = intval($params['page']);
$limit = intval($params['limit']);
$select = Db::name('link');
if(!empty($params['keyword'])){
$select->where('name|url', 'like', '%' . $params['keyword'] . '%');
}
$total = $select->count();
$items = $select->order('weight','desc')->page($page, $limit)->select();
return msg("ok", "success", ['total'=>$total, 'items'=>$items]);
}
public function get()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$item = Db::name('link')->where('id', $params['id'])->findOrEmpty();
return msg('ok', 'success', $item);
}
public function add()
{
$params = Request::param();
$validate = Validate::rule([
'name|名称' => 'require|chsAlphaNum|unique:link',
'url|地址' => 'require',
'weight|权重' => 'require|integer',
'enable' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('link')->cache('links')->insert([
'name' => trim($params['name']),
'url' => trim($params['url']),
'weight' => $params['weight'],
'enable' => $params['enable'],
'create_time' => date("Y-m-d H:i:s"),
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function edit()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'name|名称' => 'require|chsAlphaNum|unique:link',
'url|地址' => 'require',
'weight|权重' => 'require|integer',
'enable' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('link')->cache('links')->where('id', $params['id'])->update([
'name' => trim($params['name']),
'url' => trim($params['url']),
'weight' => $params['weight'],
'enable' => $params['enable'],
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function enable()
{
$id = input('post.id/d');
$enable = input('post.enable/d');
Db::name('link')->cache('links')->where('id', $id)->update([
'enable' => $enable,
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function delete()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('link')->cache('links')->where('id', $params['id'])->delete();
return msg();
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
namespace app\controller\admin;
use app\BaseController;
class Menu extends BaseController
{
public function get(){
$homeInfo = [
'title' => '主页',
'href' => 'page/dashboard.html',
];
$logoInfo = [
'title' => '工具网后台',
'image' => 'images/logo.png',
'href' => './',
];
$menuInfo = [
[
"title" => "常规管理",
"icon" => "fa fa-address-book",
"href" => "",
"target" => "_self",
"child" => [
[
"title" => "主页",
"icon" => "fa fa-home",
"target" => "_self",
"href" => "page/dashboard.html",
],
[
"title" => "分类管理",
"href" => "page/category.html",
"icon" => "fa fa-bookmark-o",
"target" => "_self",
],
[
"title" => "插件管理",
"href" => "",
"icon" => "fa fa-puzzle-piece",
"target" => "_self",
"child" => [
[
"title" => "插件列表",
"href" => "page/plugin.html",
"icon" => "fa fa-list",
"target" => "_self"
],
[
"title" => "安装新插件",
"href" => "page/plugin/install.html",
"icon" => "fa fa-cloud-upload",
"target" => "_self"
],
]
],
[
"title" => "用户管理",
"href" => "page/user.html",
"icon" => "fa fa-user",
"target" => "_self",
],
[
"title" => "留言管理",
"href" => "page/comment.html",
"icon" => "fa fa-comments",
"target" => "_self",
],
[
"title" => "上传记录",
"href" => "page/uploadlog.html",
"icon" => "fa fa-cloud-upload",
"target" => "_self",
],
[
"title" => "友链管理",
"href" => "page/link.html",
"icon" => "fa fa-link",
"target" => "_self",
],
[
"title" => "系统配置",
"href" => "",
"icon" => "fa fa-cogs",
"target" => "_self",
"child" => [
[
"title" => "基本信息配置",
"href" => "page/system.html",
"icon" => "fa fa-cog",
"target" => "_self"
],
[
"title" => "管理员配置",
"href" => "page/account.html",
"icon" => "fa fa-user",
"target" => "_self"
],
]
],
/*[
"title" => "在线升级",
"href" => "page/update.html",
"icon" => "fa fa-cloud-upload",
"target" => "_self",
],*/
],
]
];
$systemInit = [
'homeInfo' => $homeInfo,
'logoInfo' => $logoInfo,
'menuInfo' => $menuInfo,
];
return json($systemInit);
}
}
+224
View File
@@ -0,0 +1,224 @@
<?php
namespace app\controller\admin;
use app\BaseController;
use think\facade\Db;
use think\facade\Request;
use think\facade\Session;
use think\facade\Validate;
class Plugin extends BaseController
{
public function list()
{
$params = Request::param();
$validate = Validate::rule([
'page' => 'integer',
'limit' => 'integer',
'category_id' => 'integer',
'class' => 'is_legal_plugin_class',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$page = isset($params['page']) ? intval($params['page']) : 1;
$limit = isset($params['limit']) ? intval($params['limit']) : 50;
$orderby = ['id' => 'desc'];
$select = Db::name('plugin');
if(!empty($params['title'])){
$select->where('title', 'like', '%' . $params['title'] . '%');
}
if(!empty($params['alias'])){
$select->where('alias', $params['alias']);
}
if(!empty($params['class'])){
$select->where('class', $params['class']);
}
if(!empty($params['enable'])){
$select->where('enable', '1');
}
if(!empty($params['category_id'])){
$select->where('category_id', $params['category_id']);
$orderby = ['weight' => 'desc', 'id' => 'desc'];
}
$total = $select->count();
$items = $select->order($orderby)->page($page, $limit)->select();
return msg("ok", "success", ['total'=>$total, 'items'=>$items]);
}
public function get()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$item = Db::name('plugin')->where('id', $params['id'])->findOrEmpty();
return msg('ok', 'success', $item);
}
public function add()
{
$params = Request::param();
$validate = Validate::rule([
'title|插件标题' => 'require|unique:plugin',
'alias|路由别名' => 'require|unique:plugin',
'class|插件类名' => 'require|unique:plugin|is_legal_plugin_class',
'category_id|分类' => 'require|integer',
'level|用户等级限制' => 'integer',
'enable' => 'integer',
'weight|权重' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('plugin')->cache('plugins')->insert([
'title' => trim($params['title']),
'alias' => trim($params['alias']),
'class' => trim($params['class']),
'keyword' => trim($params['keyword']),
'weight' => $params['weight'],
'enable' => $params['enable'],
'category_id' => $params['category_id'],
'level' => $params['level'],
'login' => $params['login'],
'desc' => $params['desc'],
'create_time' => date("Y-m-d H:i:s"),
'update_time' => date("Y-m-d H:i:s")
]);
return msg();
}
public function edit()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'title|插件标题' => 'require|unique:plugin',
'alias|路由别名' => 'unique:plugin',
'class|插件类名' => 'unique:plugin|is_legal_plugin_class',
'category_id|分类' => 'integer',
'level|用户等级限制' => 'integer',
'enable' => 'integer',
'weight|权重' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
if(isset($params['level']) && isset($params['enable'])){
Db::name('plugin')->cache('plugins')->where('id', $params['id'])->update([
'title' => trim($params['title']),
'alias' => trim($params['alias']),
'class' => trim($params['class']),
'keyword' => trim($params['keyword']),
'weight' => $params['weight'],
'enable' => $params['enable'],
'category_id' => $params['category_id'],
'level' => $params['level'],
'login' => $params['login'],
'desc' => $params['desc'],
'update_time' => date("Y-m-d H:i:s")
]);
}else{
Db::name('plugin')->cache('plugins')->where('id', $params['id'])->update([
'title' => trim($params['title']),
'alias' => trim($params['alias']),
'class' => trim($params['class']),
'category_id' => $params['category_id'],
'desc' => $params['desc'],
'update_time' => date("Y-m-d H:i:s")
]);
}
return msg();
}
public function enable(){
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'category_id|分类' => 'integer',
'enable' => 'integer'
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('plugin')->cache('plugins')->where('id', $params['id'])->update($params);
return msg();
}
public function delete()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$plugin = Db::name('plugin')->where('id', $params['id'])->find();
if(!$plugin) return msg('ok', 'success');
Db::startTrans();
try {
$classPath = plugin_path_get() . '/'.$plugin['class'].'/Install.php';
if (file_exists($classPath)) {
require $classPath;
$class = 'plugin\\'.$plugin['class'].'\\Install';
if (class_exists($class)) {
$uninstall = new $class();
$uninstall->UnInstall();
}
}
Db::name('plugin')->cache('plugins')->where('id', $params['id'])->delete();
if (!empty($plugin['class'])) {
del_tree(plugin_path_get($plugin['class']));
}
// 提交事务
Db::commit();
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
return msg('error', $e->getMessage(), $e);
}
return msg('ok', 'success');
}
public function upload()
{
$params = Request::file();
$validate = Validate::rule([
'file' => 'require|file',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$uploadedFile = Request::file('file');
$plugin = new \app\lib\Plugin();
$zipFilepath = $plugin->getZipFilepath();
$uploadedFile->move(dirname($zipFilepath), basename($zipFilepath));
return $plugin->install();
}
}
+127
View File
@@ -0,0 +1,127 @@
<?php
namespace app\controller\admin;
use app\BaseController;
use think\facade\Db;
use think\facade\Request;
use think\facade\Validate;
use think\facade\Cache;
class System extends BaseController
{
public function analysis(){
$data['count1'] = Db::name('plugin')->count();
$data['count2'] = Db::name('comment')->count();
$data['count3'] = Db::name('user')->count();
$data['count4'] = Db::name('user')->whereDay('create_time', date("Y-m-d"))->count();
return msg('ok', 'success', $data);
}
public function info()
{
$tmp = 'version()';
$mysqlVersion = Db::query("select version()")[0][$tmp];
$data = [
'framework_version' => app()::VERSION,
'php_version' => PHP_VERSION,
'mysql_version' => $mysqlVersion,
'software' => $_SERVER['SERVER_SOFTWARE'],
'os' => php_uname(),
'date' => date("Y-m-d H:i:s"),
'checkupdate' => '//auth.cccyun.cc/app/tool.php?version='.config('app.version').'&ver='.config('app.ver')
];
return msg('ok', 'success', $data);
}
public function all()
{
$all = Db::name('config')->select();
return msg('ok', 'success', $all);
}
public function get()
{
$key = Request::param('key');
$value = config_get($key);
return msg('ok', 'success', $value);
}
public function set()
{
$params = Request::param();
foreach ($params as $v) {
if (empty($v['key'])) {
continue;
}
config_set($v['key'], $v['value']);
}
cache('configs', NULL);
$all = Db::name('config')->select();
return msg('ok', 'success', $all);
}
public function setpwd()
{
$params = Request::param();
if(isset($params['username']))$params['username']=trim($params['username']);
if(isset($params['oldpwd']))$params['oldpwd']=trim($params['oldpwd']);
if(isset($params['newpwd']))$params['newpwd']=trim($params['newpwd']);
if(isset($params['newpwd2']))$params['newpwd2']=trim($params['newpwd2']);
$validate = Validate::rule([
'username|用户名' => 'require|chsAlphaNum',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
config_set('admin_username', $params['username']);
if(!empty($params['oldpwd']) && !empty($params['newpwd']) && !empty($params['newpwd2'])){
$oldpwd = config_get('admin_password');
if($oldpwd && !password_verify($params['oldpwd'], $oldpwd)){
return msg('error', '旧密码不正确');
}
if($params['newpwd'] != $params['newpwd2']){
return msg('error', '两次新密码输入不一致');
}
config_set('admin_password', password_hash($params['newpwd'], PASSWORD_DEFAULT));
}
cache('configs', NULL);
cookie('admin_token', null);
return msg();
}
public function templates()
{
$glob = glob(app()->getRootPath() . config("view.view_dir_name") . '/index/*');
$arr = [];
foreach ($glob as $v) {
if (is_dir($v)) {
array_push($arr, basename($v));
}
}
return msg('ok', 'success', $arr);
}
public function clear(){
Cache::clear();
reset_opcache();
return msg();
}
public function iptype(){
$result = [
['name'=>'0_X_FORWARDED_FOR', 'ip'=>real_ip(0), 'city'=>get_ip_city(real_ip(0))],
['name'=>'1_X_REAL_IP', 'ip'=>real_ip(1), 'city'=>get_ip_city(real_ip(1))],
['name'=>'2_REMOTE_ADDR', 'ip'=>real_ip(2), 'city'=>get_ip_city(real_ip(2))]
];
return msg('ok', 'success', $result);
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
namespace app\controller\admin;
use app\controller\Base;
use app\lib\ExecSQL;
use think\facade\Request;
class Update extends Base
{
private $RELEASE_API = '';
public function initialize()
{
reset_opcache();
$this->RELEASE_API = base64_decode('aHR0cHM6Ly90b29sLWNsb3VkLmFvYW9zdGFyLmNvbS9vcGVuL3JlbGVhc2U=');
}
private function get_last_release()
{
$res = get_curl($this->RELEASE_API, 0, Request::domain());
$json = json_decode($res);
if (empty($json) || empty($json->data)) {
if (!empty($json->message)) {
throw new \Exception($json->message);
}
throw new \Exception('"连接云中心失败,请检查网络连通性是否正常"');
}
return $json;
}
public function check()
{
try {
$release = $this->get_last_release();
$release->data->current_version = get_version();
} catch (\Exception $e) {
return msg('error', $e->getMessage());
}
return msg('ok', 'success', $release->data);
}
public function update()
{
try {
$release = $this->get_last_release();
} catch (\Exception $e) {
return msg('error', $e->getMessage());
}
$get = get_curl($release->data->download_url);
if (empty($get) || str_starts_with($get, 'CURL Error:')) {
return msg('error', '下载更新包失败,请检查网络连通性是否正常');
}
$tmpFilename = app()->getRuntimePath() . '/tmp/' . uniqid() . '.zip';
if (!file_exists(dirname($tmpFilename))) {
mkdir(dirname($tmpFilename), 0777, true);
}
if (!file_put_contents($tmpFilename, $get)) {
return msg('error', '保存文件失败,请检查是否有写入权限');
}
$rootPath = app()->getRootPath();
if (!unzip($tmpFilename, $rootPath)) {
return msg('error', '解压压缩包失败');
}
return msg('ok', '资源包解压成功', $release->data);
}
public function updateDatabase()
{
$glob = glob(app()->getRuntimePath() . '/update/sql/*.sql');
if (empty($glob)) {
return msg('ok', '未发现数据库更新文件');
}
foreach ($glob as $value) {
$result[$value] = [];
$basename = basename($value);
$lines = file($value);
$execSQL = new ExecSQL();
$lines = $execSQL->purify($lines);
$number = $execSQL->exec($lines);
$result[$value] = array_merge($result[$value], $execSQL->getErrors());
if ($number > 0) {
$result[$value][] = "影响的记录数 $number";
}
$result[$value][] = '创建数据库升级记录成功';
end:
unlink($value);
$result[$value] = "[$basename]" . implode("\n", $result[$value]);
}
return msg('ok', "数据库执行结果:\n" . implode("\n", $result));
}
public function updateScript()
{
$glob = glob(app()->getRuntimePath() . '/update/script/*.php');
if (empty($glob)) {
return msg('ok', '未发现更新脚本');
}
foreach ($glob as $value) {
$result[$value] = [];
$basename = basename($value);
try {
require $value;
$class = 'UpdateScript';
if (!class_exists($class)) {
throw new \Exception("更新脚本不存在[$class]类");
}
$instance = new $class();
$boot = 'main';
if (!method_exists($instance, $boot)) {
throw new \Exception("更新脚本不存在[$boot]方法");
}
$instance->main();
$result[$value] = array_merge($result[$value], $instance->getResult());
} catch (\Exception $e) {
$result[$value][] = $e->getMessage();
}
$result[$value][] = '创建更新脚本升级记录成功';
end:
unlink($value);
$result[$value] = "[$basename]" . implode("\n", $result[$value]);
}
return msg('ok', "更新脚本执行结果:\n" . implode("\n", $result));
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
namespace app\controller\admin;
use app\controller\Base;
use think\facade\Request;
use think\facade\Validate;
use think\facade\Db;
class User extends Base
{
public function list()
{
$params = Request::param();
$validate = Validate::rule([
'page' => 'integer',
'limit' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$page = intval($params['page']);
$limit = intval($params['limit']);
$select = Db::name('user');
if(!empty($params['id'])){
$select->where('id|openid', $params['id']);
}
if(!empty($params['username'])){
$select->where('username', 'like', '%' . $params['username'] . '%');
}
$total = $select->count();
$items = $select->order('id','desc')->page($page, $limit)->select();
return msg("ok", "success", ['total'=>$total, 'items'=>$items]);
}
public function get()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
$item = Db::name('user')->where('id', $params['id'])->findOrEmpty();
return msg('ok', 'success', $item);
}
public function enable(){
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'enable' => 'integer'
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('user')->where('id', $params['id'])->update($params);
return msg();
}
public function edit()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require',
'level' => 'integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('user')->where('id', $params['id'])->update([
'level' => $params['level']
]);
return msg();
}
public function delete()
{
$params = Request::param();
$validate = Validate::rule([
'id' => 'require|integer',
]);
if (!$validate->check($params)) {
return msg('error', $validate->getError());
}
Db::name('user')->where('id', $params['id'])->delete();
return msg('ok', 'success');
}
public function slogin(){
$id = input('get.id/d');
$item = Db::name('user')->where('id', $id)->find();
if(!$item){
return $this->alert('error', '用户不存在');
}
$session = md5($id.$item['password']);
$expiretime = time()+30744000;
$token = authcode("{$id}\t{$session}\t{$expiretime}", 'ENCODE', config_get('syskey'));
cookie('user_token', $token, ['expire' => $expiretime, 'httponly' => true]);
return redirect("/");
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
// 事件定义文件
return [
'bind' => [
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
],
];
+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);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];
+31
View File
@@ -0,0 +1,31 @@
<?php
declare (strict_types=1);
namespace app\middleware;
class AuthAdmin
{
public function handle($request, \Closure $next)
{
$islogin = false;
$cookie = cookie('admin_token');
if($cookie){
$token=authcode($cookie, 'DECODE', config_get('syskey'));
if($token){
list($user, $sid, $expiretime) = explode("\t", $token);
$session=md5(config_get('admin_username').config_get('admin_password'));
if($session==$sid && $expiretime>time()) {
$islogin = true;
}
}
}
if (!$islogin) {
if ($request->isAjax() || !$request->isGet()) {
return msg('error', '请登录')->code(401);
}
return redirect((string)url('/admin/login.html'));
}
return $next($request);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\Db;
class AuthUser
{
public function handle($request, \Closure $next)
{
$islogin = false;
$cookie = cookie('user_token');
$user = null;
if($cookie){
$token=authcode($cookie, 'DECODE', config_get('syskey'));
if($token){
list($uid, $sid, $expiretime) = explode("\t", $token);
$user = Db::name('user')->where('id', $uid)->find();
if($user && $user['enable']==1){
$session=md5($user['id'].$user['password']);
if($session==$sid && $expiretime>time()) {
if(!$user['avatar_url']) $user['avatar_url'] = '/static/images/user.png';
$islogin = true;
}
}elseif($user && $user['enable']==0 && !session('user_block')){
session('user_block', '1');
}
}
}
$request->islogin = $islogin;
$request->user = $user;
/*if (!$islogin) {
if ($request->isAjax() || !$request->isGet()) {
return msg('error','请登录');
}
return redirect((string)url('/login'));
}*/
return $next($request);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare (strict_types = 1);
namespace app\middleware;
use think\facade\Db;
use think\facade\Config;
class LoadConfig
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
if (!file_exists(app()->getRootPath().'.env')){
return redirect((string)url('/install'));
}
$res = Db::name('config')->cache('configs',0)->column('value','key');
Config::set($res, 'sys');
return $next($request);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\View;
class RefererCheck
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
if(!checkRefererHost()){
return response('Access Denied', 403);
}
return $next($request);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\View;
class ViewOutput
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
View::assign('islogin', $request->islogin);
View::assign('user', $request->user);
View::assign('cdn_cdnjs', config_get('cdn_cdnjs', '//cdn.staticfile.org/'));
View::assign('cdn_npm', config_get('cdn_npm', 'https://unpkg.com/'));
View::config(['view_path' => template_path_get()]);
return $next($request)->header([
'Cache-Control' => 'no-store, no-cache, must-revalidate',
'Pragma' => 'no-cache',
]);
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
use app\ExceptionHandle;
use app\Request;
// 容器Provider定义文件
return [
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
];
+10
View File
@@ -0,0 +1,10 @@
<?php
use app\AppService;
// 系统服务定义文件
// 服务在完成全局初始化之后执行
return [
AppService::class,
\app\service\ValidateService::class
];
+45
View File
@@ -0,0 +1,45 @@
<?php
declare (strict_types=1);
namespace app\service;
use think\facade\Validate;
class ValidateService extends \think\Service
{
/**
* 注册服务
*
* @return mixed
*/
public function register()
{
//
}
/**
* 执行服务
*
* @return mixed
*/
public function boot()
{
//
Validate::maker(function ($validate) {
$validate->extend('is_json', function ($value) {
if (!is_string($value)) {
$value = json_encode($value);
}
json_decode($value);
return json_last_error() == JSON_ERROR_NONE;
});
$validate->extend('is_legal_plugin_class', function ($value) {
if (!is_string($value)){
return false;
}
$arr = explode('\\', $value);
return count($arr) === 2;
});
});
}
}