From 1c01130432de27e7ca8bdb8cdbbcf7346b048748 Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Thu, 19 Aug 2021 22:48:02 +0800 Subject: [PATCH 1/7] refactor: new telegram DC model --- .env.example | 15 +++ functions/Curl.php | 15 +++ functions/RedisCache.php | 10 +- main.php | 6 +- models/tgDC.php | 191 +++++++++++++++++---------------------- route.php | 3 +- 6 files changed, 126 insertions(+), 114 deletions(-) create mode 100644 functions/Curl.php diff --git a/.env.example b/.env.example index e62f315..06bb188 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# Time zone +TIME_ZONE=Asia/Hong_Kong + # Bot Settings BOT_NAME= BOT_TOKEN= @@ -10,3 +13,15 @@ REDIS_PREFIX=tgbot # ChinaZ API ICP_KEY= +ICP_API=https://apidatav2.chinaz.com/single/icp + +# IP Info +ECHOIP_API=https://api.343.re/ip/ + +# KMS Check +KMS_API=https://kms.343.re/ + +# 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..d566c61 --- /dev/null +++ b/functions/Curl.php @@ -0,0 +1,15 @@ + \ No newline at end of file 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/main.php b/main.php index 3e424e7..1084835 100644 --- a/main.php +++ b/main.php @@ -2,17 +2,18 @@ require_once 'env.php'; require_once 'route.php'; +require_once 'functions/Curl.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 +28,7 @@ if ($isCallback) { // 回调请求模式 $messageFrom = $webhook['message']['from']; } $chat = $message['chat']; + $tgEnv = array( 'isGroup' => ($chat['type'] === 'group') ? true : false, // 是否为群组 'isCallback' => $isCallback, // 是否为回调请求 diff --git a/models/tgDC.php b/models/tgDC.php index 0a21c32..25154fb 100644 --- a/models/tgDC.php +++ b/models/tgDC.php @@ -1,7 +1,7 @@ '新加坡' ); default: - return array(); + return array(); // 错误输入 } } - private function curl($url, $timeOut = 5) { // curl模拟 默认5s超时 - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeOut); - $content = curl_exec($curl); - curl_close($curl); - return $content; - } - - private function checkAccount($account) { // 检查用户名是否合法 - preg_match('/^[a-zA-Z0-9_]+$/', $account, $match); - if (count($match) === 0 or strlen($account) < 5) { // 用户名由至少5位 0-9/a-z/A-Z/_ 组成 - return false; - } - if (substr($account, 0, 1) === '_' || substr($account, -1) === '_') { // 不能以_开头结尾 - return false; - } - return true; - } - private function getUserInfo($account) { // 获取Telegram用户信息 - $info['account'] = $account; - $info['name'] = null; $info['dc'] = null; - - $html = $this->curl('https://t.me/' . $account); // 获取原始HTML数据 + $info['name'] = null; + $info['account'] = $account; + $html = (new Curl)->get('https://t.me/' . $account); // 获取原始HTML数据 $html = preg_replace('/[\t\n\r]+/', '', $html); // 去除干扰 - if (!is_string($html) || $html == '') { return $info; } // 用户名无效 + if (!is_string($html) || $html == '') { + return $info + [ 'time' => time() ]; // 用户名无效 + } $avatarRegex = '//'; $nameRegex = '/(.+?)<\/span>/'; preg_match($avatarRegex, $html, $avatarMatch); // 匹配目标头像 preg_match($nameRegex, $html, $nameMatch); // 匹配目标名称 - if ($nameMatch[1]) { + if (isset($nameMatch[1])) { $info['name'] = $nameMatch[1]; // 获取用户名 } - if ($avatarMatch[1]) { - $avatarUrl = $avatarMatch[1]; // 获取头像链接 - } - if ($avatarUrl) { // 头像存在 - $dcRegex = '/https:\/\/cdn(.+)\.telesco\.pe\//'; - preg_match_all($dcRegex, $avatarUrl, $dcMatch); // 根据cdn?.telesco.pe获取DC - if ($dcMatch[1]) { - $info['dc'] = 'DC' . $dcMatch[1][0]; + if (isset($avatarMatch[1])) { // 头像可见 + $dcRegex = '/https:\/\/cdn([1-5])\.telesco\.pe\//'; + preg_match($dcRegex, $avatarMatch[1], $dcMatch); // 根据cdn?.telesco.pe获取DC + if (isset($dcMatch[1])) { + $info['dc'] = 'DC' . $dcMatch[1]; // DC匹配成功 } } - if ($info['dc']) { - $info += $this->getDcDetail($info['dc']); // 匹配DC参数 + if ($info['dc'] != null) { + $info += $this->getDcDetail($info['dc']); // 载入DC详细信息 } + $info['time'] = time(); // 记录查询时间戳 return $info; } - private function getUserInfoCache($account) { // 获取用户信息 带缓存 - $redis = new redisCache('tgdc'); + public function getInfo($account, $isCache = true) { // 获取用户信息 默认带缓存 + $redis = new RedisCache('tgdc'); $info = $redis->getData($account); // 查询缓存数据 - if (!$info) { // 缓存未命中 + if (!$isCache || !$info) { // 不缓存 或 缓存未命中 $info = $this->getUserInfo($account); // 发起查询 - if (!$info['name'] && !$info['dc']) { // 用户名与头像均无 - $cacheTTL = 300; // 缓存5min - } else if ($info['name'] && !$info['dc']) { // 存在用户名但未设置头像 - $cacheTTL = 20; // 缓存20s - } else { - $cacheTTL = 3600; // 其余情况缓存1h - } - $redis->setData($account, json_encode($info), $cacheTTL); // 缓存数据 + $redis->setData($account, json_encode($info)); // 缓存数据 永久 } else { // 缓存命中 $info = json_decode($info, true); // 使用缓存数据 } return $info; } +} - public function getInfo($account) { // 查询入口 - if (substr($account, 0, 1) === '@') { // 用户名可带有@ - $account = substr($account, 1); +class tgDCEntry { // DC查询入口 + private function checkAccount($account) { // 检查用户合法性 + preg_match('/^[a-zA-Z0-9_]+$/', $account, $match); + if (count($match) === 0 or strlen($account) < 5) { // 用户名由至少5位 0-9/a-z/A-Z/_ 组成 + return false; } - if (!$this->checkAccount($account)) { // 用户名不合法 - return array( - 'status' => 'error', - 'message' => '用户名无效' - ); + if (substr($account, 0, 1) === '_' || substr($account, -1) === '_') { // 不能以_开头结尾 + return false; } - $info = $this->getUserInfoCache($account); + return true; + } + + private function showHelp() { // 显示帮助信息 + $message = tgApi::sendMarkdown('*Usage:* `/dc username`'); + $message = json_decode($message, true); + return $message['result']['message_id']; // 返回消息ID + } + + private function genMessage($info) { // 生成返回信息 if (!$info['name'] && !$info['dc']) { // 用户名与头像均无 - return array( - 'status' => 'error', - 'message' => '@' . $account . ' 无法识别' - ); + return '@' . $info['account'] . ' 无法识别'; } else if ($info['name'] && !$info['dc']) { // 存在用户名但未设置头像 - return array( - 'status' => 'error', - 'message' => '@' . $account . ' 未设置头像或不可见' - ); + return '@' . $info['account'] . ' 未设置头像或不可见'; } - return array( - 'status' => 'ok', - 'data' => json_encode($info) // 返回查询结果 - ); + $msg = '@' . $info['account'] . ' (' . $info['name'] . ')' . PHP_EOL; + $msg .= '_' . $info['as'] . '_ '; + $msg .= '`(``' . $info['ip'] . '``)`' . PHP_EOL; + $msg .= '*' . $info['dc'] . '* - ' . $info['addr'] . PHP_EOL; + return $msg; // 返回正常查询结果 } -} -class tgDCEntry { - private function getInfo($account) { - $content = (new tgDC)->getInfo($account); // 发起查询 - if ($content['status'] === 'ok') { - $info = json_decode($content['data'], true); - $msg = '@' . $info['account'] . ' (' . $info['name'] . ')' . PHP_EOL; - $msg .= '' . $info['as'] . ' '; - $msg .= '(' . $info['ip'] . ')' . PHP_EOL; - $msg .= '' . $info['dc'] . ' - ' . $info['addr'] . PHP_EOL; - return array( - 'parse_mode' => 'HTML', // HTML格式输出 - 'text' => $msg - ); + private function sendInfo($account) { // 查询并发送用户信息 + if (!$this->checkAccount($account)) { // 用户名不合法 + tgApi::sendText('用户名无效'); + return; + } + $info = (new tgDC)->getInfo($account); // 带缓存查询 + $message = tgApi::sendMarkdown($this->genMessage($info)); // 发送预查询信息 + if (!$info['name'] && !$info['dc']) { + $cacheTime = 300; // 未设置用户名或用户不存在 缓存5min + } else if ($info['name'] && !$info['dc']) { + $cacheTime = 20; // 用户头像不可见 缓存20s } else { - return array( - 'text' => $content['message'] // 返回错误信息 - ); + $cacheTime = 86400; // 用户正常 缓存24h + } + if ($cacheTime < time() - $info['time']) { // 数据过期 + $messageId = json_decode($message, true)['result']['message_id']; + $infoRenew = (new tgDC)->getInfo($account, false); // 不带缓存 重新查询 + unset($info['time']); + unset($infoRenew['time']); + if ($info !== $infoRenew) { // 数据出现变化 + tgApi::editMessage(array( + 'parse_mode' => 'Markdown', + 'message_id' => $messageId, + 'text' => $this->genMessage($infoRenew) // 更新信息 + )); + } } } public function query($rawParam) { // tgDC查询入口 - $helpMsg = array( // 使用说明 - 'parse_mode' => 'Markdown', - 'text' => '*Usage:* `/dc username`' - ); - if ($rawParam === 'help') { // 查询使用说明 - tgApi::sendMessage($helpMsg); - return; - } - if ($rawParam !== '') { // 查询指定用户数据 - tgApi::sendMessage($this->getInfo($rawParam)); - return; - } global $tgEnv; - if (!$tgEnv['isGroup']) { // 群组不发送帮助信息 - $message = json_decode(tgApi::sendMessage($helpMsg), true); // 发送使用说明 + if ($rawParam === 'help') { $this->showHelp(); } // 显示使用说明 + if ($rawParam == '') { + $rawParam = $tgEnv['userAccount']; // 空指令时查询对方信息 + if (!$tgEnv['isGroup']) { + $messageId = $this->showHelp(); // 非群组发送使用说明 + } + } + if (substr($rawParam, 0, 1) === '@') { + $rawParam = substr($rawParam, 1); // 去除用户名前@ } - tgApi::sendMessage($this->getInfo($tgEnv['userAccount'])); // 查询对方用户名 - if ($tgEnv['isGroup']) { return; } - fastcgi_finish_request(); // 断开连接 + $this->sendInfo($rawParam); // 查询并发送用户信息 + if (!isset($messageId)) { return; } sleep(10); // 延迟10s tgApi::deleteMessage(array( // 删除使用说明 - 'message_id' => $message['result']['message_id'] + 'message_id' => $messageId )); } } diff --git a/route.php b/route.php index 7ab187c..5a20ff0 100644 --- a/route.php +++ b/route.php @@ -32,7 +32,7 @@ function cmdRoute($cmd) { // 命令功能模块路由 case '/punycode': return (new punycodeEntry); } - return null; + return null; // 命令不存在 } function route($message) { // 请求路由 @@ -52,6 +52,7 @@ function route($message) { // 请求路由 $cmd = substr($cmd, 0, strlen($cmd) - strlen($botAccount) - 1); // 分离@机器人 } } + $rawParam = trim($rawParam); $entry = cmdRoute($cmd); // 获取功能模块入口 if (!$entry) { return; } // 命令不存在 if ($tgEnv['isCallback']) { From 3e2c1a1ca8acaeb90ca984ee85f6bc16bdf3ff84 Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Thu, 19 Aug 2021 23:16:46 +0800 Subject: [PATCH 2/7] refactor: new CFOP pic model --- models/cfopPic.php | 123 +++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 70 deletions(-) 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'] + )); } } From 5a72e26a7be2c51a46c4bc6f6880da3ad2b720cf Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Fri, 20 Aug 2021 22:30:13 +0800 Subject: [PATCH 3/7] refactor: new IP info model --- .env.example | 10 +- functions/Curl.php | 3 + functions/DNS.php | 72 ++++++++ functions/{ExtractDomain.php => Domain.php} | 7 + main.php | 3 +- models/ipInfo.php | 176 ++++++++------------ models/tgDC.php | 6 +- 7 files changed, 161 insertions(+), 116 deletions(-) create mode 100644 functions/DNS.php rename functions/{ExtractDomain.php => Domain.php} (89%) diff --git a/.env.example b/.env.example index 06bb188..ad9a482 100644 --- a/.env.example +++ b/.env.example @@ -11,16 +11,16 @@ REDIS_PORT=6379 REDIS_PASSWD= REDIS_PREFIX=tgbot -# ChinaZ API -ICP_KEY= -ICP_API=https://apidatav2.chinaz.com/single/icp - # IP Info -ECHOIP_API=https://api.343.re/ip/ +ECHOIP_HOST=ip.343.re # KMS Check KMS_API=https://kms.343.re/ +# ChinaZ API +ICP_KEY= +ICP_API=https://apidatav2.chinaz.com/single/icp + # CFOP Pic CFOP_GAN=BQACAgUAAxkBAAIBtGEOLnr4Q6D4Z_80bgfXq5xsZMeWAAKtAwACWy55VOU-SGKqc7aMIAQ CFOP_MFG=BQACAgUAAxkBAAIB3WEOVHKeYrrGhFo-GffB0W-tQRKlAALQAwACWy55VGny8ArGMkfoIAQ diff --git a/functions/Curl.php b/functions/Curl.php index d566c61..c6411f8 100644 --- a/functions/Curl.php +++ b/functions/Curl.php @@ -1,11 +1,14 @@ ua); $content = curl_exec($curl); curl_close($curl); return $content; diff --git a/functions/DNS.php b/functions/DNS.php new file mode 100644 index 0000000..7aa71f7 --- /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; + } + + private 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); + } + + private 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/ExtractDomain.php b/functions/Domain.php similarity index 89% rename from functions/ExtractDomain.php rename to functions/Domain.php index cbcacae..be4398e 100644 --- a/functions/ExtractDomain.php +++ b/functions/Domain.php @@ -1,5 +1,12 @@ 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/tgDC.php b/models/tgDC.php index 25154fb..c58469f 100644 --- a/models/tgDC.php +++ b/models/tgDC.php @@ -83,7 +83,7 @@ class tgDCEntry { // DC查询入口 return true; } - private function showHelp() { // 显示帮助信息 + private function sendHelp() { // 显示帮助信息 $message = tgApi::sendMarkdown('*Usage:* `/dc username`'); $message = json_decode($message, true); return $message['result']['message_id']; // 返回消息ID @@ -133,11 +133,11 @@ class tgDCEntry { // DC查询入口 public function query($rawParam) { // tgDC查询入口 global $tgEnv; - if ($rawParam === 'help') { $this->showHelp(); } // 显示使用说明 + if ($rawParam === 'help') { $this->sendHelp(); } // 显示使用说明 if ($rawParam == '') { $rawParam = $tgEnv['userAccount']; // 空指令时查询对方信息 if (!$tgEnv['isGroup']) { - $messageId = $this->showHelp(); // 非群组发送使用说明 + $messageId = $this->sendHelp(); // 非群组发送使用说明 } } if (substr($rawParam, 0, 1) === '@') { From 8da689fbc8580a09fe0d10b6cc3407b479dbca4e Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Sat, 21 Aug 2021 03:23:19 +0800 Subject: [PATCH 4/7] refactor: new NTP check model --- functions/Curl.php | 27 +++++- functions/DNS.php | 4 +- functions/Domain.php | 11 +++ models/ntpCheck.php | 206 ++++++++++++++++++++++++++----------------- 4 files changed, 162 insertions(+), 86 deletions(-) diff --git a/functions/Curl.php b/functions/Curl.php index c6411f8..8793280 100644 --- a/functions/Curl.php +++ b/functions/Curl.php @@ -4,11 +4,30 @@ class Curl { public $ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67'; public function get($url, $timeOut = 30) { // curl模拟Get 默认30s超时 + return $this->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(); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeOut); - curl_setopt($curl, CURLOPT_USERAGENT, $this->ua); + foreach ($configs as $config) { + curl_setopt($curl, $config[0], $config[1]); + } $content = curl_exec($curl); curl_close($curl); return $content; diff --git a/functions/DNS.php b/functions/DNS.php index 7aa71f7..1e49bf0 100644 --- a/functions/DNS.php +++ b/functions/DNS.php @@ -40,7 +40,7 @@ class DNS { return $ipAddr; } - private function ip2long6($ipv6) { // 压缩IPv6地址为long + public function ip2long6($ipv6) { // 压缩IPv6地址为long $ip_n = inet_pton($ipv6); $bits = 15; while ($bits >= 0) { @@ -51,7 +51,7 @@ class DNS { return gmp_strval(gmp_init($ipv6long, 2), 10); } - private function long2ip6($ipv6long) { // 解压long为IPv6地址 + public function long2ip6($ipv6long) { // 解压long为IPv6地址 $bin = gmp_strval(gmp_init($ipv6long, 10), 2); if (strlen($bin) < 128) { $pad = 128 - strlen($bin); diff --git a/functions/Domain.php b/functions/Domain.php index be4398e..d688142 100644 --- a/functions/Domain.php +++ b/functions/Domain.php @@ -5,6 +5,17 @@ class 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); } + + public 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)) { // IP地址 + return true; + } + return false; + } } class extractDomain { diff --git a/models/ntpCheck.php b/models/ntpCheck.php index 306f2c4..1359e81 100644 --- a/models/ntpCheck.php +++ b/models/ntpCheck.php @@ -1,28 +1,28 @@ ntpDB); - $res = $db->query('SELECT * FROM `ntp_list` WHERE id=' . $list_id . ';'); + $res = $db->query('SELECT * FROM `ntp_list` WHERE id=' . $listId . ';'); return $res->fetchArray(SQLITE3_ASSOC)['name']; } - public function getNtpList() { // 获取所有NTP服务器地址 + public function getList() { // 获取所有NTP服务器地址 $db = new SqliteDB($this->ntpDB); $res = $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 +31,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]+