diff --git a/.env.example b/.env.example index e62f315..4f99eaf 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# Time zone +TIME_ZONE=Asia/Hong_Kong + # Bot Settings BOT_NAME= BOT_TOKEN= @@ -8,5 +11,17 @@ REDIS_PORT=6379 REDIS_PASSWD= REDIS_PREFIX=tgbot +# IP Info +ECHOIP_HOST=ip.343.re + +# KMS Server +KMS_HOST=kms.343.re +KMS_VLMCS=/usr/bin/vlmcs + # ChinaZ API ICP_KEY= + +# CFOP Pic +CFOP_GAN=BQACAgUAAxkBAAIBtGEOLnr4Q6D4Z_80bgfXq5xsZMeWAAKtAwACWy55VOU-SGKqc7aMIAQ +CFOP_MFG=BQACAgUAAxkBAAIB3WEOVHKeYrrGhFo-GffB0W-tQRKlAALQAwACWy55VGny8ArGMkfoIAQ +CFOP_YX=BQACAgUAAxkBAAIB32EOVISFQbgmir2abj6QkgqaSX1WAALRAwACWy55VMEuU9lCYTYWIAQ diff --git a/functions/Curl.php b/functions/Curl.php new file mode 100644 index 0000000..d7904b0 --- /dev/null +++ b/functions/Curl.php @@ -0,0 +1,37 @@ +run(array( + [CURLOPT_URL, $url], + [CURLOPT_RETURNTRANSFER, 1], + [CURLOPT_CONNECTTIMEOUT, $timeOut], + [CURLOPT_USERAGENT, $this->ua] + )); + } + + public function post($url, $data, $timeOut = 30) { // curl模拟Post 默认30s超时 + return $this->run(array( + [CURLOPT_URL, $url], + [CURLOPT_RETURNTRANSFER, 1], + [CURLOPT_CONNECTTIMEOUT, $timeOut], + [CURLOPT_USERAGENT, $this->ua], + [CURLOPT_POST, 1], + [CURLOPT_POSTFIELDS, $data] + )); + } + + private function run($configs) { // 发起curl请求 + $curl = curl_init(); + foreach ($configs as $config) { + curl_setopt($curl, $config[0], $config[1]); + } + $content = curl_exec($curl); + curl_close($curl); + return $content; + } +} + +?> \ No newline at end of file diff --git a/functions/DNS.php b/functions/DNS.php new file mode 100644 index 0000000..98f8b72 --- /dev/null +++ b/functions/DNS.php @@ -0,0 +1,72 @@ +ip2long6($record['ipv6']); + } + sort($ipAddr); // 解析结果排序 + foreach ($ipAddr as &$ip) { + $ip = $this->long2ip6($ip); + } + return $ipAddr; + } + + public function resolveIP($domain) { // DNS解析IP记录 A/AAAA + $ipAddr = array(); + $ipv4 = $this->resolveA($domain); + foreach ($ipv4 as $ip) { + $ipAddr[] = $ip; + } + $ipv6 = $this->resolveAAAA($domain); + foreach ($ipv6 as $ip) { + $ipAddr[] = $ip; + } + return $ipAddr; + } + + public function ip2long6($ipv6) { // 压缩IPv6地址为long + $ip_n = inet_pton($ipv6); + $bits = 15; + while ($bits >= 0) { + $bin = sprintf("%08b", (ord($ip_n[$bits]))); + $ipv6long = $bin.$ipv6long; + $bits--; + } + return gmp_strval(gmp_init($ipv6long, 2), 10); + } + + public function long2ip6($ipv6long) { // 解压long为IPv6地址 + $bin = gmp_strval(gmp_init($ipv6long, 10), 2); + if (strlen($bin) < 128) { + $pad = 128 - strlen($bin); + for ($i = 1; $i <= $pad; $i++) { + $bin = '0' . $bin; + } + } + $bits = 0; + while ($bits <= 7) { + $bin_part = substr($bin, ($bits * 16), 16); + $ipv6 .= dechex(bindec($bin_part)) . ':'; + $bits++; + } + return inet_ntop(inet_pton(substr($ipv6, 0, -1))); + } +} + +?> diff --git a/functions/DNSSEC.php b/functions/DNSSEC.php new file mode 100644 index 0000000..de49dc6 --- /dev/null +++ b/functions/DNSSEC.php @@ -0,0 +1,47 @@ + diff --git a/functions/ExtractDomain.php b/functions/Domain.php similarity index 50% rename from functions/ExtractDomain.php rename to functions/Domain.php index cbcacae..8bc4adc 100644 --- a/functions/ExtractDomain.php +++ b/functions/Domain.php @@ -1,9 +1,25 @@ tldDB); $res = $db->query('SELECT record FROM `list`;'); while ($row = $res->fetchArray(SQLITE3_ASSOC)) { @@ -12,28 +28,21 @@ class extractDomain { return $tlds; // Unicode字符使用Punycode编码 } - private function isDomain($domain) { // 检测是否为域名 - preg_match('/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/', $domain, $match); - return (count($match) != 0); - } - - private function getDomain($url) { // 从URL获取域名 + private function extractDomain($url) { // 从URL获取域名 $url = preg_replace('/^[\w]+:\/\//', '', $url); // 去除协议字段 $url = explode('?', $url)[0]; // 去除请求参数内容 $domain = explode('/', $url)[0]; // 分离域名 return (new Punycode)->encode($domain); } - private function getTld($domain) { // 搜索域名TLD - $tlds = $this->getAllTlds(); // 获取TLD列表 + private function getTld($domain) { // 匹配域名TLD + $tlds = $this->getTldList(); // 获取TLD列表 foreach ($tlds as $tld) { if (substr($domain, -strlen($tld)) === $tld) { // 匹配测试 $target[] = $tld; } } - if (count($target) === 0) { - return ''; // 匹配不到TLD - }; + if (!isset($target)) { return null; } // 匹配不到TLD $type = 0; foreach ($target as $tld) { // 遍历可能的结果 $num = substr_count($tld, '.'); @@ -52,15 +61,13 @@ class extractDomain { } public function analyse($url) { // 分析域名信息 - $domain = $this->getDomain($url); + $domain = $this->extractDomain($url); if (!$this->isDomain($domain)) { // 域名不合格 return array(); } $tld = $this->getTld($domain); - if ($tld == '') { // 匹配不到TLD - return array( - 'domain' => $domain - ); + if ($tld === null) { // 匹配不到TLD + return [ 'domain' => $domain ]; } return array( 'domain' => $domain, @@ -68,6 +75,26 @@ class extractDomain { 'site' => $this->getSite($domain, $tld) ); } + + public function getSubTld($tld) { + $db = new SqliteDB($this->tldDB); + $res = $db->query('SELECT record FROM `list` WHERE tld="' . $tld . '";'); + while ($row = $res->fetchArray(SQLITE3_ASSOC)) { + if ($row['record'] !== $tld) { + $subTld[] = $row['record']; + } + } + return $subTld; + } + + public function icpTldInfo($tld) { // 查询TLD的ICP信息 + $db = new SqliteDB($this->tldDB); + $info = $db->query('SELECT * FROM `icp` WHERE tld="' . $tld . '";'); + $info = $info->fetchArray(SQLITE3_ASSOC); + if ($info == '') { return null; } // TLD不存在于ICP列表中 + $info['site'] = json_decode(base64_decode($info['site']), true); + return $info; + } } ?> diff --git a/functions/RedisCache.php b/functions/RedisCache.php index 8c1e29c..5c7a34a 100644 --- a/functions/RedisCache.php +++ b/functions/RedisCache.php @@ -1,6 +1,6 @@ connect($this->redisSetting['host'], $this->redisSetting['port']); if ($this->redisSetting['passwd'] !== '') { @@ -24,7 +24,7 @@ class redisCache { return $redisValue; } - public function setData($key, $data, $cacheTTL = 600) { // 写入信息到Redis缓存 默认10min过期 + public function setData($key, $data, $cacheTTL = 0) { // 写入Redis缓存 默认不过期 $redis = new Redis(); $redis->connect($this->redisSetting['host'], $this->redisSetting['port']); if ($this->redisSetting['passwd'] !== '') { @@ -32,7 +32,9 @@ class redisCache { } $redisKey = $this->redisSetting['prefix'] . $key; $status = $redis->set($redisKey, $data); // 写入数据库 - $redis->pexpire($redisKey, $cacheTTL * 1000); // 设置过期时间 单位 ms = s * 1000 + if ($cacheTTL > 0) { + $redis->pexpire($redisKey, $cacheTTL * 1000); // 设置过期时间 单位 ms = s * 1000 + } return $status; } } diff --git a/functions/SqliteDB.php b/functions/SqliteDB.php index 88aa2d5..731b41d 100644 --- a/functions/SqliteDB.php +++ b/functions/SqliteDB.php @@ -2,10 +2,10 @@ class SqliteDB extends SQLite3 { // Sqlite3数据库 public function __construct($filename) { - $this->open($filename); + $this->open($filename); // 打开数据库连接 } public function __destruct() { - $this->close(); + $this->close(); // 关闭数据库连接 } } diff --git a/main.php b/main.php index 3e424e7..5cf0cab 100644 --- a/main.php +++ b/main.php @@ -2,17 +2,20 @@ require_once 'env.php'; require_once 'route.php'; +require_once 'functions/DNS.php'; +require_once 'functions/Curl.php'; +require_once 'functions/Domain.php'; +require_once 'functions/DNSSEC.php'; require_once 'functions/Punycode.php'; require_once 'functions/SqliteDB.php'; require_once 'functions/RedisCache.php'; require_once 'functions/TgInterface.php'; -require_once 'functions/ExtractDomain.php'; - -fastcgi_finish_request(); // 断开请求连接 +fastcgi_finish_request(); // 断开连接 $env = loadEnv('.env'); // 载入环境变量 $apiToken = $env['BOT_TOKEN']; $botAccount = $env['BOT_NAME']; // 机器人用户名 +ini_set('date.timezone', $env['TIME_ZONE']); // 设置时区 $apiPath = 'https://api.telegram.org/bot' . $apiToken; // Telegram API接口 $webhook = json_decode(file_get_contents("php://input"), TRUE); // Webhook接受信息 @@ -27,6 +30,7 @@ if ($isCallback) { // 回调请求模式 $messageFrom = $webhook['message']['from']; } $chat = $message['chat']; + $tgEnv = array( 'isGroup' => ($chat['type'] === 'group') ? true : false, // 是否为群组 'isCallback' => $isCallback, // 是否为回调请求 diff --git a/models/cfopPic.php b/models/cfopPic.php index 0642895..4bd0686 100644 --- a/models/cfopPic.php +++ b/models/cfopPic.php @@ -1,91 +1,74 @@ '网页下载', + 'url' => 'https://res.dnomd343.top/Share/cfop/' + ]), + array([ + 'text' => '获取全部', + 'callback_data' => '/cfop all' + ]), + array( + array( + 'text' => 'GAN', + 'callback_data' => '/cfop gan' + ), + array( + 'text' => '魔方格', + 'callback_data' => '/cfop mfg' + ), + array( + 'text' => '裕鑫', + 'callback_data' => '/cfop yx' + ) + ) + ); + tgApi::sendMessage(array( 'text' => 'CFOP魔方公式合集', 'reply_markup' => json_encode(array( - 'inline_keyboard' => array( - array([ - 'text' => '网页下载', - 'url' => 'https://res.dnomd343.top/Share/cfop/' - ]), - array([ - 'text' => '获取全部', - 'callback_data' => '/cfop all' - ]), - array( - array( - 'text' => 'GAN', - 'callback_data' => '/cfop gan' - ), - array( - 'text' => '魔方格', - 'callback_data' => '/cfop mfg' - ), - array( - 'text' => '裕鑫', - 'callback_data' => '/cfop yx' - ) - ) - ) + 'inline_keyboard' => $buttons )) - ); + )); } - private function getPicId($type) { // 返回图片文件ID + private function sendCfopPic($type) { // 发送图片 global $env; - switch ($type) { - case 'gan': - return $env['CFOP_GAN']; - case 'mfg': - return $env['CFOP_MFG']; - case 'yx': - return $env['CFOP_YX']; - } - } - - private function getPic($type) { // 获取图片 - switch ($type) { - case 'gan': - case 'mfg': - case 'yx': - return array( - 'document' => $this->getPicId($type) - ); - case '': - return $this->getCfopMsg(); - default: - return array( - 'text' => '未知公式' - ); - } - } - - private function sendCfopPic($params) { // 发送图片或信息 - if ($params['document']) { - tgApi::sendDocument($params); + if ($type === 'gan') { + $picId = $env['CFOP_GAN']; + } else if ($type === 'mfg') { + $picId = $env['CFOP_MFG']; + } else if ($type === 'yx') { + $picId = $env['CFOP_YX']; + } else if ($type === '' || $type === 'help') { + $this->sendMenu(); } else { - tgApi::sendMessage($params); + tgApi::sendText('未知公式'); + } + if (isset($picId)) { + tgApi::sendDocument(array( + 'document' => $picId + )); } } public function query($rawParam) { // CFOP图片查询入口 - $this->sendCfopPic($this->getPic($rawParam)); + $this->sendCfopPic($rawParam); } public function callback($rawParam) { // CFOP图片回调入口 - if ($rawParam === 'all') { - global $tgEnv; - tgApi::deleteMessage(array( // 删除源消息 - 'message_id' => $tgEnv['messageId'] - )); - $this->sendCfopPic($this->getPic('gan')); - $this->sendCfopPic($this->getPic('mfg')); - $this->sendCfopPic($this->getPic('yx')); + if ($rawParam !== 'all') { + $this->sendCfopPic($rawParam); return; } - $this->sendCfopPic($this->getPic($rawParam)); + $this->sendCfopPic('gan'); + $this->sendCfopPic('mfg'); + $this->sendCfopPic('yx'); + tgApi::deleteMessage(array( // 删除源消息 + 'message_id' => $GLOBALS['tgEnv']['messageId'] + )); } } diff --git a/models/icpQuery.php b/models/icpQuery.php index 9e5814c..60f2fcd 100644 --- a/models/icpQuery.php +++ b/models/icpQuery.php @@ -1,161 +1,56 @@ apiPath = $env['ICP_API'] . '?key=' . $env['ICP_KEY'] . '&domain='; + $this->apiPath = 'https://apidatav2.chinaz.com/single/icp?key=' . $GLOBALS['env']['ICP_KEY'] . '&domain='; } private function getIcpInfo($domain) { // ICP备案查询 - $domain = urlencode((new Punycode)->decode($domain)); - $info = json_decode(file_get_contents($this->apiPath . $domain), true); - if ($info['StateCode'] === 1) { // 存在ICP备案 - $info = array( - 'status' => 'ok', + $domain = urlencode((new Punycode)->decode($domain)); // API接口需要URL编码原始域名 + $info = json_decode(file_get_contents($this->apiPath . $domain), true); // 请求API接口 + if ($info['StateCode'] === 1) { + return $info['Result'] + array( // 存在ICP备案 + 'time' => time(), 'hasIcp' => true - ) + $info['Result']; - } else if ($info['StateCode'] === 0) { // 无ICP备案 - if ($info['Reason'] !== '暂无备案信息') { - return array( - 'status' => 'error', - 'message' => $info['Reason'] - ); - } - $info = array( - 'status' => 'ok', - 'hasIcp' => false, - 'icpMsg' => $info['Reason'] ); - } else { - $info = array( - 'status' => 'error', // 服务错误 - 'message' => 'Server error' + } else if ($info['StateCode'] === 0) { + if ($info['Reason'] !== '暂无备案信息') { return null; } // 服务错误 + return array( // 无ICP备案 + 'time' => time(), + 'hasIcp' => false ); + } else { + return null; // 服务错误 } - return $info; } public function isCache($domain) { // 查询域名是否存在缓存 - $redis = new redisCache('icp'); + $redis = new RedisCache('icp'); $info = $redis->getData($domain); // 查询缓存数据 return ($info) ? true : false; } - public function icpInfo($domain) { // ICP查询入口 带缓存 - $redis = new redisCache('icp'); + public function icpInfo($domain, $isCache = true) { // ICP查询入口 默认带缓存 + $redis = new RedisCache('icp'); $info = $redis->getData($domain); // 查询缓存数据 - if (!$info) { // 缓存未命中 - $info = $this->getIcpInfo($domain); // 执行查询 - if ($info['status'] !== 'ok') { // 查询错误 - return $info; - } - unset($info['status']); - $redis->setData($domain, json_encode($info), 90 * 86400); // 缓存90day + if (!$isCache || !$info) { // 不缓存 或 缓存未命中 + $info = $this->getIcpInfo($domain); // 发起查询 + if ($info === null) { return null; } // 服务错误 + $redis->setData($domain, json_encode($info)); // 缓存ICP信息 永久 } else { // 缓存命中 $info = json_decode($info, true); // 使用缓存数据 } - return array( - 'status' => 'ok' - ) + $info; + return $info; } } -class icpQueryEntry { - private function getIcpTlds() { // 获取所有可ICP备案的顶级域 - $db = new SqliteDB('./db/tldInfo.db'); - $punycode = new Punycode(); - $res = $db->query('SELECT tld FROM `icp`;'); - while ($row = $res->fetchArray(SQLITE3_ASSOC)) { - $tlds[] = $punycode->encode($row['tld']); // 转为Punycode编码 - } - return $tlds; // Unicode字符使用Punycode编码 - } - - private function isIcpEnable($tld) { // 检查TLD是否允许ICP备案 - $icpTlds = $this->getIcpTlds(); - foreach ($icpTlds as $icpTld) { // 遍历所有可ICP域名 - if ($icpTld === $tld) { - return true; // 允许备案 - } - } - return false; // 无法备案 - } - - private function check($str) { // 检查输入参数 - $content = (new extractDomain)->analyse($str); - if (!isset($content['domain'])) { // 格式错误 - return array( - 'status' => 'error', - 'message' => 'Illegal Request' - ); - } - if (!isset($content['tld'])) { // 未知TLD - return array( - 'status' => 'error', - 'message' => 'Unknow TLD' - ); - } - if (!$this->isIcpEnable($content['tld'])) { - return array( - 'status' => 'error', - 'message' => '`' . $content['tld'] . '` is not allowed in ICP' - ); - } - return array( - 'status' => 'ok', - 'domain' => $content['site'] // 返回主域名 - ); - } - - public function query($rawParam) { // ICP备案查询入口 - if ($rawParam == '' || $rawParam === 'help') { // 显示使用说明 - tgApi::sendMessage(array( - 'parse_mode' => 'Markdown', - 'text' => '*Usage:* `/icp domain`', - )); - return; - } - $content = $this->check($rawParam); - if ($content['status'] !== 'ok') { // 请求参数错误 - tgApi::sendMarkdown($content['message']); - return; - } - $isCache = true; - $domain = $content['domain']; +class icpQueryEntry { // ICP信息查询入口 + private function genMessage($domain, $info) { // 生成ICP数据消息 + if ($info === null) { return 'Server error'; } // 服务错误 $msg = '`' . (new Punycode)->decode($domain) . '`' . PHP_EOL; - if (!(new icpQuery)->isCache($domain)) { // 域名信息未缓存 - $message = tgApi::sendMarkdown($msg . 'ICP备案信息查询中...'); - $message = json_decode($message, true); - $isCache = false; - } - $info = (new icpQuery)->icpInfo($domain); // 发起查询 - if ($info['status'] !== 'ok') { - if ($isCache) { // 没有缓冲信息 直接发送 - tgApi::sendText($info['message']); // 查询出错 - } else { - tgApi::editMessage(array( - 'text' => $info['message'], - 'message_id' => $message['result']['message_id'] - )); - } - return; - } - if (!$info['hasIcp']) { // 没有ICP备案 - $content = array( - 'parse_mode' => 'Markdown', - 'text' => $msg . $info['icpMsg'] - ); - if ($isCache) { // 没有缓冲信息 直接发送 - tgApi::sendMessage($content); - } else { - tgApi::editMessage(array( - 'message_id' => $message['result']['message_id'] - ) + $content); - } - return; - } + if (!$info['hasIcp']) { return $msg . '暂无备案信息'; } // 无ICP备案 $msg .= '*类型:*' . $info['CompanyType'] . PHP_EOL; if ($info['Owner'] != '') { // 负责人为空不显示 $msg .= '*负责人:*' . $info['Owner'] . PHP_EOL; @@ -172,16 +67,53 @@ class icpQueryEntry { $msg .= '*网站名:*' . $info['SiteName'] . PHP_EOL; $msg .= '*审核时间:*' . $info['VerifyTime'] . PHP_EOL; $msg .= '*许可证号:*' . $info['SiteLicense'] . PHP_EOL; - if ($isCache) { // 没有缓冲信息 直接发送 - tgApi::sendMarkdown($msg); // 返回查询数据 - } else { - tgApi::editMessage(array( // 返回查询数据 修改原消息 + return $msg; + } + + private function sendIcpInfo($domain) { // 查询并发送ICP备案信息 + if (!(new icpQuery)->isCache($domain)) { // 未缓存 + $headerMsg = '`' . (new Punycode)->decode($domain) . '`' . PHP_EOL; + $message = tgApi::sendMarkdown($headerMsg . 'ICP备案信息查询中...'); // 发送缓冲信息 + $messageId = json_decode($message, true)['result']['message_id']; + $info = (new icpQuery)->icpInfo($domain); // 发起查询 + tgApi::editMessage(array( 'parse_mode' => 'Markdown', - 'text' => $msg, - 'message_id' => $message['result']['message_id'] + 'message_id' => $messageId, // 替换原消息 + 'text' => $this->genMessage($domain, $info) )); + } else { // 已缓存 + $info = (new icpQuery)->icpInfo($domain); // 获取缓存信息 + $message = tgApi::sendMarkdown($this->genMessage($domain, $info)); // 先输出 + if (time() - $info['time'] < 90 * 86400) { return; } // 缓存有效期90day + $infoRenew = (new icpQuery)->icpInfo($domain, false); // 不带缓存查询 + $messageId = json_decode($message, true)['result']['message_id']; + unset($info['time']); + unset($infoRenew['time']); + if ($info !== $infoRenew) { // 数据出现变化 + tgApi::editMessage(array( + 'parse_mode' => 'Markdown', + 'message_id' => $messageId, + 'text' => $this->genMessage($domain, $infoRenew) // 更新信息 + )); + } + } + } + + public function query($rawParam) { // ICP备案查询入口 + if ($rawParam == '' || $rawParam === 'help') { + tgApi::sendMarkdown('*Usage:* `/icp domain`'); // 显示使用说明 + return; + } + $content = (new Domain)->analyse($rawParam); + if (!isset($content['domain'])) { // 格式错误 + tgApi::sendText('Illegal Request'); + } else if (!isset($content['tld'])) { // 未知TLD + tgApi::sendText('Unknow TLD'); + } else if ((new Domain)->icpTldInfo($content['tld']) === null) { // TLD无法ICP备案 + tgApi::sendMarkdown('`' . $content['tld'] . '` is not allowed in ICP'); + } else { + $this->sendIcpInfo($content['site']); // 查询并输出域名备案信息 } - } } diff --git a/models/ipInfo.php b/models/ipInfo.php index 8d73e01..e41d724 100644 --- a/models/ipInfo.php +++ b/models/ipInfo.php @@ -1,11 +1,10 @@ apiPath = $env['ECHOIP_API']; + $this->apiPath = 'https://' . $GLOBALS['env']['ECHOIP_HOST'] . '/info/'; } private function changeCoor($num) { // 转为时分秒格式 @@ -27,83 +26,32 @@ class ipInfo { return $str; } - public function getInfo($ip) { - if (!filter_var($ip, FILTER_VALIDATE_IP)) { // IP地址不合法 - return array( - 'status' => 'error', - 'message' => 'Illegal IP Address' - ); - } - $redis = new redisCache('ip'); + private function getIpInfo($ip) { // 向上游查询IP信息 + $content = (new Curl)->get($this->apiPath . $ip); + $info = json_decode($content, true); + if ($info['status'] !== 'T') { return null; } + unset($info['status']); + return $info + array( + 'locCoor' => $this->coorFormat($info['loc']) // 经纬度格式 + ); + } + + public function getInfo($ip) { // 查询IP信息 错误返回null + $redis = new RedisCache('ip'); $info = $redis->getData($ip); // 查询缓存数据 if (!$info) { // 缓存未命中 - $info = json_decode(file_get_contents($this->apiPath . $ip), true); - if ($info['status'] !== 'T') { // 上游接口错误 - return array( - 'status' => 'error', - 'message' => 'Server Error' - ); - } - unset($info['status']); - if ($info['loc'] != NULL) { - $info['locCoor'] = $this->coorFormat($info['loc']); // 转换为经纬度格式 - } - $redis->setData($ip, json_encode($info), 1800); // 缓存30min + $info = $this->getIpInfo($ip); + if ($info === null) { return null; } // 服务错误 返回null + $redis->setData($ip, json_encode($info), 43200); // 缓存12h } else { // 缓存命中 $info = json_decode($info, true); // 使用缓存数据 } - return array( - 'status' => 'ok', - 'data' => json_encode($info) // 返回查询结果 - ); + return $info; // 查询成功 返回结果 } } -class ipInfoEntry { - private function isDomain($domain) { // 检测是否为域名 - preg_match('/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/', $domain, $match); - return (count($match) != 0); - } - - private function dnsResolveIPv4($domain) { // DNS解析A记录 - $ipAddr = array(); - $rs = dns_get_record(strtolower($domain), DNS_A); - foreach ($rs as $record) { - $ipAddr[] = $record['ip']; - } - return $ipAddr; - } - - private function dnsResolveIPv6($domain) { // DNS解析AAAA记录 - $ipAddr = array(); - $rs = dns_get_record(strtolower($domain), DNS_AAAA); - foreach ($rs as $record) { - $ipAddr[] = $record['ipv6']; - } - return $ipAddr; - } - - private function dnsResolve($domain) { // DNS解析IP记录 - $ipAddr = array(); - $ipv4 = $this->dnsResolveIPv4($domain); - foreach ($ipv4 as $ip) { - $ipAddr[] = $ip; - } - $ipv6 = $this->dnsResolveIPv6($domain); - foreach ($ipv6 as $ip) { - $ipAddr[] = $ip; - } - return $ipAddr; - } - - private function getInfo($ip) { - $content = (new ipInfo)->getInfo($ip); // 发起查询 - if ($content['status'] !== 'ok') { - return array( - 'text' => $content['message'] // 返回错误信息 - ); - } - $info = json_decode($content['data'], true); +class ipInfoEntry { // IP信息查询入口 + private function genMessage($info) { // 生成返回信息 $msg = 'IP: ' . $info['ip'] . '' . PHP_EOL; if ($info['as'] != NULL) { $msg .= 'AS: '; @@ -122,43 +70,37 @@ class ipInfoEntry { if ($info['scope'] != NULL) { $msg .= 'Scope: ' . $info['scope'] . '' . PHP_EOL; } if ($info['detail'] != NULL) { $msg .= 'Detail: ' . $info['detail'] . PHP_EOL; } return array( + 'text' => $msg, 'parse_mode' => 'HTML', // HTML格式输出 'disable_web_page_preview' => 'true', // 不显示页面预览 - 'text' => $msg, - 'reply_markup' => json_encode(array( // 显示按钮 - 'inline_keyboard' => array([[ - 'text' => 'View on echoIP', - 'url' => 'https://ip.dnomd343.top/?ip=' . $ip - ]]) - )) + 'reply_markup' => $this->genButton('View on echoIP', $info['ip']) // 显示按钮 ); } - public function query($rawParam) { // ipInfo查询入口 - if ($rawParam == '' || $rawParam === 'help') { // 显示使用说明 - tgApi::sendMessage(array( - 'parse_mode' => 'Markdown', - 'text' => '*Usage:* `/ip IPv4/IPv6/Domain`', - 'reply_markup' => json_encode(array( // 显示echoIP按钮 - 'inline_keyboard' => array([[ - 'text' => 'Get my IP address', - 'url' => 'https://ip.dnomd343.top/' - ]]) - )) - )); - return; - } - if (filter_var($rawParam, FILTER_VALIDATE_IP)) { // 参数为IP地址 - tgApi::sendMessage($this->getInfo($rawParam)); // 发起查询 - return; - } - if (!$this->isDomain($rawParam)) { - tgApi::sendText('Illegal Request'); // 非法请求 + private function genButton($text, $ip = '') { // 生成ehcoIP页面链接按钮 默认为主页 + $url = 'https://' . $GLOBALS['env']['ECHOIP_HOST'] . '/'; + if ($ip !== '') { $url .= '?ip=' . $ip; } + return json_encode(array( + 'inline_keyboard' => array([[ // echoIP按钮 + 'text' => $text, + 'url' => $url + ]]) + )); + } + + private function sendInfo($ip) { // 查询并发送IP信息 + $info = (new ipInfo)->getInfo($ip); + if ($info === null) { + tgApi::sendText('Server error'); // 上游查询错误 return; } - $ips = $this->dnsResolve($rawParam); + tgApi::sendMessage($this->genMessage($info)); + } + + private function sendDomainInfo($domain) { // 查询并发送域名解析结果 + $ips = (new DNS)->resolveIP($domain); if (count($ips) == 0) { // 解析不到IP记录 - tgApi::sendMarkdown('Nothing found of `' . $rawParam . '`'); + tgApi::sendMarkdown('Nothing found of `' . $domain . '`'); return; } foreach ($ips as $ip) { @@ -170,27 +112,47 @@ class ipInfoEntry { if (count($ips) >= 2) { $buttons[] = array([ // 两个及以上的IP 添加显示全部的按钮 'text' => 'Get all detail', - 'callback_data' => '/ip ' . $rawParam + 'callback_data' => '/ip ' . $domain ]); } tgApi::sendMessage(array( 'parse_mode' => 'Markdown', - 'text' => 'DNS resolve of `' . $rawParam . '`', - 'reply_markup' => json_encode(array( // 显示IP列表按钮 + 'text' => 'DNS resolve of `' . $domain . '`', + 'reply_markup' => json_encode(array( // IP列表按钮 'inline_keyboard' => $buttons )) )); } + private function sendHelp() { // 发送使用说明 + $helpMessage = array( + 'parse_mode' => 'Markdown', + 'text' => '*Usage:* `/ip IPv4/IPv6/Domain`', + 'reply_markup' => $this->genButton('Get my IP address') // echoIP主页链接 + ); + tgApi::sendMessage($helpMessage); + } + + public function query($rawParam) { // ipInfo查询入口 + if ($rawParam == '' || $rawParam === 'help') { + $this->sendHelp(); // 显示使用说明 + } else if (filter_var($rawParam, FILTER_VALIDATE_IP)) { // 参数为IP地址 + $this->sendInfo($rawParam); // 查询并发送IP信息 + } else if ((new Domain)->isDomain($rawParam)) { // 参数为域名 + $this->sendDomainInfo($rawParam); // 查询并发送域名信息 + } else { + tgApi::sendText('Illegal Request'); // 非法请求 + } + } + public function callback($rawParam) { // ipInfo回调入口 if (filter_var($rawParam, FILTER_VALIDATE_IP)) { // 参数为IP地址 $this->query($rawParam); } else { // 参数为域名 - global $tgEnv; tgApi::deleteMessage(array( - 'message_id' => $tgEnv['messageId'] + 'message_id' => $GLOBALS['tgEnv']['messageId'] )); - $ips = $this->dnsResolve($rawParam); + $ips = (new DNS)->resolveIP($rawParam); foreach ($ips as $ip) { $this->query($ip); // 逐个输出 } diff --git a/models/kmsCheck.php b/models/kmsCheck.php index 560755b..bedfbe0 100644 --- a/models/kmsCheck.php +++ b/models/kmsCheck.php @@ -1,16 +1,16 @@ kmsDB); - $res = $db->query('SELECT * FROM `' . $type . '_version` WHERE version_id=' . $version_id . ';'); - return $res->fetchArray(SQLITE3_ASSOC)['version_name']; +class kmsKeys { // KMS密钥获取 + private $db, $kmsDB = './db/kmsKeys.db'; // KMS密钥数据库 + + private function getVersionName($type, $versionId) { // 获取对应版本的名称 + $res = $this->db->query('SELECT * FROM `' . $type . '_version` WHERE version_id=' . $versionId . ';'); + return $res->fetchArray(SQLITE3_ASSOC)['version_name']; } - private function getKmsKeys($type) { // 获取所有版本的KMS密钥 - $db = new SqliteDB($this->kmsDB); - $res = $db->query('SELECT * FROM `' . $type . '`;'); + private function getKmsKeys($type) { // 获取指定类型的密钥信息 win/win-server + $this->db = new SqliteDB($this->kmsDB); + $res = $this->db->query('SELECT * FROM `' . $type . '`;'); while ($row = $res->fetchArray(SQLITE3_ASSOC)) { $index = $row['version']; unset($row['version']); @@ -19,110 +19,101 @@ class kmsKeys { return $data; } - public function getKeys($type) { // 获取指定类型KMS密钥 + public function getKeys($type) { // 获取指定类型的各版本及KMS密钥 switch ($type) { - case '': + case '': // win和win-server系列 return $this->getKmsKeys('win') + $this->getKmsKeys('win-server'); - case 'win': + case 'win': // win系列 return $this->getKmsKeys('win'); - case 'win-server': + case 'win-server': // win-server系列 return $this->getKmsKeys('win-server'); default: - return array(); + return array(); // 未知类型 返回空数组 } } } -class kmsCheck { - private $api; +class kmsCheck { // KMS服务器检查 + private function getKmsStatus($host, $port) { // 获取KMS服务器状态 + if(filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $host = '[' . $host . ']'; // IPv6地址需用中括号包围 + } + $cmd = $GLOBALS['env']['KMS_VLMCS'] . ' '; + $cmd .= $host . ':' . $port; // 加入目标服务器信息 + $raw = shell_exec($cmd . ' -G temp'); // 执行vlmcs测试 + preg_match_all('/Sending activation request \(KMS V6\)/', $raw, $match); + return (count($match[0]) === 6) ? true : false; // 返回KMS服务器状态 + } - public function __construct() { - global $env; - $this->api = $env['KMS_API']; + public function isCache($server) { // 检查KMS服务器是否已缓存 + $redis = new RedisCache('kms'); + $status = $redis->getData($server); + return (!$status) ? false : true; } - public function kmsStatus($host, $port) { + public function kmsStatus($host, $port) { // 检测KMS服务器状态 + $redis = new RedisCache('kms'); $server = $host . ':' . $port; - $redis = new redisCache('kms'); - $info = $redis->getData($server); // 查询缓存数据 - if (!$info) { // 缓存未命中 - $url = $this->api . 'check?host=' . $host . '&port=' . $port; - $info = json_decode(file_get_contents($url), true); // 请求上游接口 - $info['server'] = $server; - switch ($info['status']) { - case 'ok': - $info['online'] = true; - break; - case 'error': - $info['online'] = false; - break; - default: - return array( - 'status' => 'error', - 'message' => 'Server error' - ); + $status = $redis->getData($server); + if (!$status) { // 缓存未命中 + if ($this->getKmsStatus($host, $port)) { // 测试服务器状态 + $status = [ 'status' => 'online' ]; // 服务正常 + } else { + $status = [ 'status' => 'offline' ]; // 服务掉线 } - $info['status'] = 'ok'; - unset($info['message']); - $redis->setData($server, json_encode($info), 300); // 缓存5min + $redis->setData($server, json_encode($status), 300); // 缓存5min } else { // 缓存命中 - $info = json_decode($info, true); // 使用缓存数据 + $status = json_decode($status, true); // 使用缓存数据 } - return $info; + return $status; } } -class kmsCheckEntry { - private function isHost($host) { // 判断host是否合法 - preg_match('/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/', $host, $match); - if (count($match) !== 0) { // 域名 - if (!is_numeric(substr($host, -1))) { return true; } // 域名最后一位不为数字 - } - if (filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { // IPv4地址 - return true; - } - return false; - } - - private function formatCheck($server) { +class kmsCheckEntry { // KMS功能入口 + private function formatCheck($server) { // 输入参数格式检查 $temp = explode(':', $server); if (count($temp) === 1) { // 不带:的请求 - if ($this->isHost($temp[0])) { - $host = $server; - $port = 1688; - } else { - return array( - 'status' => 'error', - 'message' => 'Illegal host' - ); - } + if (!(new Domain)->isHost($temp[0])) { return null; } // 错误请求 + return array( + 'host' => $temp[0], + 'port' => 1688 + ); } else if (count($temp) === 2) { // 带一个:的请求 - if ($this->isHost($temp[0])) { - $host = $temp[0]; - } else { - return array( - 'status' => 'error', - 'message' => 'Illegal host' - ); - } - $port = $temp[1]; - if ($port < 0 || $port > 65535) { - return array( - 'status' => 'error', - 'message' => 'Illegal port' - ); - } - } else { // 带多个:的请求 + if (!(new Domain)->isHost($temp[0])) { return null; } // 错误请求 + if ($temp[1] < 0 || $temp[1] > 65535) { return null; } // 错误请求 return array( - 'status' => 'error', - 'message' => 'Illegal request' + 'host' => $temp[0], + 'port' => $temp[1] ); + } else { // 带多个:的请求 + return null; // 错误请求 } - return array( - 'status' => 'ok', - 'host' => $host, - 'port' => $port - ); + } + + private function genMessage($server, $status) { // 生成KMS状态消息 + $msg = '`' . $server . '`' . PHP_EOL; + if ($status['status'] === 'online') { + return $msg . 'KMS服务*正常运行*'; // 服务正常 + } else { + return $msg . 'KMS服务*无法使用*'; // 服务异常 + } + } + + private function sendKmsStatus($host, $port) { // 检查并发送KMS服务器状态 + $server = $host . ':' . $port; + if ((new kmsCheck)->isCache($server)) { // 状态已缓存 + $status = (new kmsCheck)->kmsStatus($host, $port); + tgApi::sendMarkdown($this->genMessage($server, $status)); // 发送服务器状态 + return; + } + $message = tgApi::sendMarkdown('`' . $server . '`' . PHP_EOL . 'KMS服务检测中...'); + $messageId = json_decode($message, true)['result']['message_id']; // 未缓存 发送缓冲消息 + $status = (new kmsCheck)->kmsStatus($host, $port); // 发起查询 + tgApi::editMessage(array( // 返回查询结果 + 'parse_mode' => 'Markdown', + 'message_id' => $messageId, + 'text' => $this->genMessage($server, $status) + )); } private function simpStr($str) { // 简化版本名称 @@ -130,17 +121,41 @@ class kmsCheckEntry { return implode('', $match[0]); } - private function getKmsVersions($type) { // 获取win或win-server的版本列表 + private function genKmsKeys($targetVersion) { // 显示指定版本的KMS密钥列表 + $content = explode('|', $targetVersion); + $type = $content[0]; // 获取类型 win/win-server + $targetVersion = $content[1]; // 获取目标版本 + $kmsKeys = (new kmsKeys)->getKeys($type); // 获取该类型的所有版本 + foreach ($kmsKeys as $version => $kmsKey) { // 比对压缩以后的名称 + if ($this->simpStr($version) === $targetVersion) { break; } // 匹配成功 + } + $msg = '*' . $version . ' KMS Keys*' . PHP_EOL . PHP_EOL; + foreach ($kmsKey as $row) { + $msg .= $row['name'] . ':`' . $row['key'] . '`' . PHP_EOL . PHP_EOL; + } + return array( + 'text' => $msg, + 'parse_mode' => 'Markdown', + 'reply_markup' => json_encode(array( // 返回按钮 + 'inline_keyboard' => array([[ + 'text' => '<< Go back <<', + 'callback_data' => '/kms ' . $type + ]]) + )) + ); + } + + private function genKmsVersions($type) { // 获取版本列表 win/win-server $kmsKeys = (new kmsKeys)->getKeys($type); foreach ($kmsKeys as $version => $kmsKey) { $buttons[] = array([ // 生成按钮列表 'text' => $version, - 'callback_data' => '/kms ' . $this->simpStr($version) + 'callback_data' => '/kms ' . $type . '|' . $this->simpStr($version) ]); } $buttons[] = array([ 'text' => '<< Go back <<', - 'callback_data' => '/kms keys' + 'callback_data' => '/kms menu' // 加入返回按钮 ]); return array( 'text' => 'Which version did you need?', @@ -150,122 +165,91 @@ class kmsCheckEntry { ); } - private function getKmsKeys($targetVersion) { // 显示指定版本的KMS密钥列表 - $kmsKeys = (new kmsKeys)->getKeys(''); - foreach ($kmsKeys as $version => $kmsKey) { // 比对压缩以后的名称 - if ($this->simpStr($version) === $targetVersion) { break; } // 匹配成功 - } - $msg = '*' . $version . ' KMS Keys*' . PHP_EOL . PHP_EOL; - foreach ($kmsKey as $row) { - $msg .= $row['name'] . ':`' . $row['key'] . '`' . PHP_EOL . PHP_EOL; - } + private function genActiveCmd() { // 生成KMS激活命令 + $kmsHost = $GLOBALS['env']['KMS_HOST']; + $actiCmd = '```' . PHP_EOL . 'slmgr /upk' . PHP_EOL . 'slmgr /ipk {KMS_KEY}' . PHP_EOL; + $actiCmd .= 'slmgr /skms ' . $kmsHost . PHP_EOL . 'slmgr /ato' . PHP_EOL . 'slmgr /dlv'; + $actiCmd .= PHP_EOL . '```'; return array( + 'text' => $actiCmd, 'parse_mode' => 'Markdown', - 'text' => $msg + 'reply_markup' => json_encode(array( // 返回按钮 + 'inline_keyboard' => array([[ + 'text' => '<< Go back <<', + 'callback_data' => '/kms menu' + ]]) + )) ); } - private function checkKms($host, $port) { // 检查KMS服务器状态 - $content = (new kmsCheck)->kmsStatus($host, $port); - if ($content['status'] === 'ok') { - if ($content['online'] === true) { - return array( - 'parse_mode' => 'Markdown', - 'text' => '`' . $content['server'] . '`' . PHP_EOL . 'KMS服务*正常运行*' - ); - } else { - return array( - 'parse_mode' => 'Markdown', - 'text' => '`' . $content['server'] . '`' . PHP_EOL . 'KMS服务*无法使用*' - ); - } - } else { - return array( - 'text' => $content['message'] - ); - } - } - - public function query($rawParam) { // kmsCheck查询入口 - if ($rawParam == '' || $rawParam === 'help') { // 显示使用说明 - tgApi::sendMessage(array( - 'parse_mode' => 'Markdown', - 'text' => '*Usage:* `/kms IP/Domain[:port]`', - 'reply_markup' => json_encode(array( // 获取KMS密钥按钮 - 'inline_keyboard' => array([[ - 'text' => 'Get KMS Keys', - 'callback_data' => '/kms keys' - ]]) - )) - )); - return; - } - $check = $this->formatCheck($rawParam); - if ($check['status'] === 'error') { // 输入格式有误 - tgApi::sendText($check['message']); - return; - } - $message = tgApi::sendMarkdown('`' . $rawParam . '`' . PHP_EOL . 'KMS服务检测中...'); - $message = json_decode($message, true); - fastcgi_finish_request(); // 断开连接 - tgApi::editMessage(array( - 'message_id' => $message['result']['message_id'], - ) + $this->checkKms($check['host'], $check['port'])); // 发起查询并返回结果 - } - - public function callback($rawParam) { // kmsCheck回调入口 - global $tgEnv; - $selectMsg = array( + private function genSelectMsg() { // 生成KMS版本选择消息 + return array( 'text' => 'Which one did you need?', 'reply_markup' => json_encode(array( - 'inline_keyboard' => array( + 'inline_keyboard' => array( // 功能选择按钮 array([ 'text' => 'Windows', - 'callback_data' => '/kms win' + 'callback_data' => '/kms win' // Windows密钥 ]), array([ 'text' => 'Windows Server', - 'callback_data' => '/kms win-server' + 'callback_data' => '/kms win-server' // Windows Server密钥 ]), array([ 'text' => 'Activation Command', - 'callback_data' => '/kms cmd' + 'callback_data' => '/kms cmd' // 激活命令 ]) ) )) ); - $actiCmd = '```' . PHP_EOL . 'slmgr /upk' . PHP_EOL . 'slmgr /ipk {KMS_KEY}' . PHP_EOL; - $actiCmd .= 'slmgr /skms {KMS_HOST}' . PHP_EOL . 'slmgr /ato' . PHP_EOL . 'slmgr /dlv'; - $actiCmd .= PHP_EOL . '```'; + } + + private function sendHelp() { // 发送使用说明 + $helpMessage = array( + 'parse_mode' => 'Markdown', + 'text' => '*Usage:* `/kms IP/Domain[:port]`', + 'reply_markup' => json_encode(array( // 加入 获取KMS密钥 按钮 + 'inline_keyboard' => array([[ + 'text' => 'Get KMS Keys', + 'callback_data' => '/kms menu' + ]]) + )) + ); + tgApi::sendMessage($helpMessage); + } + + public function query($rawParam) { // KMS测试查询入口 + if ($rawParam == '' || $rawParam === 'help') { // 显示使用说明 + $this->sendHelp(); + return; + } + $server = $this->formatCheck($rawParam); + if ($server === null) { + tgApi::sendText('Illegal request'); // 输入格式错误 + return; + } + $this->sendKmsStatus($server['host'], $server['port']); // 检查并发送KMS服务器状态 + } + + public function callback($rawParam) { // KMS测试回调入口 + $messageId = $GLOBALS['tgEnv']['messageId']; switch ($rawParam) { - case 'keys': - tgApi::editMessage(array( - 'message_id' => $tgEnv['messageId'], - ) + $selectMsg); - return; - case 'cmd': - tgApi::editMessage(array( - 'message_id' => $tgEnv['messageId'], - 'parse_mode' => 'Markdown', - 'text' => $actiCmd, - 'reply_markup' => json_encode(array( - 'inline_keyboard' => array([[ - 'text' => '<< Go back <<', - 'callback_data' => '/kms keys' - ]]) - )) - )); - return; - case 'win': - case 'win-server': - tgApi::editMessage(array( - 'message_id' => $tgEnv['messageId'], - ) + $this->getKmsVersions($rawParam)); - return; + case 'menu': // 选择菜单 + $message = $this->genSelectMsg(); + break; + case 'cmd': // KMS激活命令 + $message = $this->genActiveCmd(); + break; + case 'win': // Windows激活密钥 + case 'win-server': // Windows Server激活密钥 + $message = $this->genKmsVersions($rawParam); + break; + default: + $message = $this->genKmsKeys($rawParam); // 显示密钥列表 } tgApi::editMessage(array( - 'message_id' => $tgEnv['messageId'] - ) + $this->getKmsKeys($rawParam)); + 'message_id' => $messageId // 修改源消息内容 + ) + $message); } } diff --git a/models/ntpCheck.php b/models/ntpCheck.php index 306f2c4..c48f994 100644 --- a/models/ntpCheck.php +++ b/models/ntpCheck.php @@ -1,28 +1,27 @@ ntpDB); - $res = $db->query('SELECT * FROM `ntp_list` WHERE id=' . $list_id . ';'); + private function getListName($listId) { // 获取对应组的名称 + $res = $this->db->query('SELECT * FROM `ntp_list` WHERE id=' . $listId . ';'); return $res->fetchArray(SQLITE3_ASSOC)['name']; } - public function getNtpList() { // 获取所有NTP服务器地址 - $db = new SqliteDB($this->ntpDB); - $res = $db->query('SELECT * FROM `ntp_host`;'); + public function getList() { // 获取所有NTP服务器地址 + $this->db = new SqliteDB($this->ntpDB); + $res = $this->db->query('SELECT * FROM `ntp_host`;'); while ($row = $res->fetchArray(SQLITE3_ASSOC)) { $index = $row['list_id']; unset($row['list_id']); - $data[$this->getListName($index)][] = $row; + $list[$this->getListName($index)][] = $row; } - return $data; + return $list; } } -class ntpCheck { - private function formatOffset($str) { // 格式化Offset +class ntpCheck { // NTP服务器检查 + private function formatOffset($str) { // 格式化偏移时间 $num = number_format($str, 6) * 1000; // s -> ms $str = sprintf("%1\$.3f", $num); // 补零到小数点后3位 if ($num > 0) { @@ -31,23 +30,61 @@ class ntpCheck { return $str . 'ms'; } - private function curlPost($url, $data) { // curl模拟post操作 40s超时 - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 40); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $data); - $content = curl_exec($curl); - curl_close($curl); - return $content; + private function sortByIp($servers) { // 排序算法 + $temp = array(); + foreach ($servers as $val){ + $temp[] = $val['Server']; + } + sort($temp); + $temp = array_flip($temp); + $sort = array(); + foreach ($servers as $val) { + $temp_1 = $val['Server']; + $temp_2 = $temp[$temp_1]; + $sort[$temp_2] = $val; + } + asort($sort); + return $sort; + } + + private function sortServer($servers) { // 按顺序排列服务器 + foreach ($servers as $server) { + if(filter_var($server['Server'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $ipv4[] = $server; // 提取IPv4服务器 + } + if(filter_var($server['Server'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $ipv6[] = $server; // 提取IPv6服务器 + } + } + if (isset($ipv4)) { // 存在IPv4服务器 + foreach ($ipv4 as $index => $ip) { + $ipv4[$index]['Server'] = ip2long($ip['Server']); // IPv4预处理 + } + $ipv4 = $this->sortByIp($ipv4); // 排序IPv4服务器 + foreach ($ipv4 as $index => $ip) { + $ip['Server'] = long2ip($ip['Server']); // IPv4恢复 + $result[] = $ip; + } + } + if (isset($ipv6)) { // 存在IPv6服务器 + foreach ($ipv6 as $index => $ip) { + $ipv6[$index]['Server'] = (new DNS)->ip2long6($ip['Server']); // IPv6预处理 + } + $ipv6 = $this->sortByIp($ipv6); // 排序IPv6服务器 + foreach ($ipv6 as $index => $ip) { + $ip['Server'] = (new DNS)->long2ip6($ip['Server']); // IPv6恢复 + $result[] = $ip; + } + } + return (!isset($result)) ? array() : $result; // 无结果 返回空数组 } private function getNtpStatus($host) { // 获取NTP服务器状态 - $html = $this->curlPost('https://servertest.online/ntp', array( + $html = (new Curl)->post('https://servertest.online/ntp', array( 'a' => $host, 'c' => 'Query+both' )); + if ($html == '') { return null; } // 服务错误 preg_match('/<\/form>[\s\S]+