Browse Source

refactor: new telegram DC model

dev
Dnomd343 3 years ago
parent
commit
1c01130432
  1. 15
      .env.example
  2. 15
      functions/Curl.php
  3. 8
      functions/RedisCache.php
  4. 6
      main.php
  5. 189
      models/tgDC.php
  6. 3
      route.php

15
.env.example

@ -1,3 +1,6 @@
# Time zone
TIME_ZONE=Asia/Hong_Kong
# Bot Settings # Bot Settings
BOT_NAME= BOT_NAME=
BOT_TOKEN= BOT_TOKEN=
@ -10,3 +13,15 @@ REDIS_PREFIX=tgbot
# ChinaZ API # ChinaZ API
ICP_KEY= 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

15
functions/Curl.php

@ -0,0 +1,15 @@
<?php
class Curl {
public function get($url, $timeOut = 30) { // curl模拟Get 默认30s超时
$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;
}
}
?>

8
functions/RedisCache.php

@ -1,6 +1,6 @@
<?php <?php
class redisCache { class RedisCache {
private $redisSetting = array(); // Redis接口参数 private $redisSetting = array(); // Redis接口参数
public function __construct($prefix) { // 初始化 public function __construct($prefix) { // 初始化
@ -13,7 +13,7 @@ class redisCache {
); );
} }
public function getData($key) { // 查询Redis缓存不存在返回NULL public function getData($key) { // 查询Redis缓存 不存在返回NULL
$redis = new Redis(); $redis = new Redis();
$redis->connect($this->redisSetting['host'], $this->redisSetting['port']); $redis->connect($this->redisSetting['host'], $this->redisSetting['port']);
if ($this->redisSetting['passwd'] !== '') { if ($this->redisSetting['passwd'] !== '') {
@ -24,7 +24,7 @@ class redisCache {
return $redisValue; return $redisValue;
} }
public function setData($key, $data, $cacheTTL = 600) { // 写入信息到Redis缓存 默认10min过期 public function setData($key, $data, $cacheTTL = 0) { // 写入Redis缓存 默认不过期
$redis = new Redis(); $redis = new Redis();
$redis->connect($this->redisSetting['host'], $this->redisSetting['port']); $redis->connect($this->redisSetting['host'], $this->redisSetting['port']);
if ($this->redisSetting['passwd'] !== '') { if ($this->redisSetting['passwd'] !== '') {
@ -32,7 +32,9 @@ class redisCache {
} }
$redisKey = $this->redisSetting['prefix'] . $key; $redisKey = $this->redisSetting['prefix'] . $key;
$status = $redis->set($redisKey, $data); // 写入数据库 $status = $redis->set($redisKey, $data); // 写入数据库
if ($cacheTTL > 0) {
$redis->pexpire($redisKey, $cacheTTL * 1000); // 设置过期时间 单位 ms = s * 1000 $redis->pexpire($redisKey, $cacheTTL * 1000); // 设置过期时间 单位 ms = s * 1000
}
return $status; return $status;
} }
} }

6
main.php

@ -2,17 +2,18 @@
require_once 'env.php'; require_once 'env.php';
require_once 'route.php'; require_once 'route.php';
require_once 'functions/Curl.php';
require_once 'functions/Punycode.php'; require_once 'functions/Punycode.php';
require_once 'functions/SqliteDB.php'; require_once 'functions/SqliteDB.php';
require_once 'functions/RedisCache.php'; require_once 'functions/RedisCache.php';
require_once 'functions/TgInterface.php'; require_once 'functions/TgInterface.php';
require_once 'functions/ExtractDomain.php'; require_once 'functions/ExtractDomain.php';
fastcgi_finish_request(); // 断开请求连接 fastcgi_finish_request(); // 断开连接
$env = loadEnv('.env'); // 载入环境变量 $env = loadEnv('.env'); // 载入环境变量
$apiToken = $env['BOT_TOKEN']; $apiToken = $env['BOT_TOKEN'];
$botAccount = $env['BOT_NAME']; // 机器人用户名 $botAccount = $env['BOT_NAME']; // 机器人用户名
ini_set('date.timezone', $env['TIME_ZONE']); // 设置时区
$apiPath = 'https://api.telegram.org/bot' . $apiToken; // Telegram API接口 $apiPath = 'https://api.telegram.org/bot' . $apiToken; // Telegram API接口
$webhook = json_decode(file_get_contents("php://input"), TRUE); // Webhook接受信息 $webhook = json_decode(file_get_contents("php://input"), TRUE); // Webhook接受信息
@ -27,6 +28,7 @@ if ($isCallback) { // 回调请求模式
$messageFrom = $webhook['message']['from']; $messageFrom = $webhook['message']['from'];
} }
$chat = $message['chat']; $chat = $message['chat'];
$tgEnv = array( $tgEnv = array(
'isGroup' => ($chat['type'] === 'group') ? true : false, // 是否为群组 'isGroup' => ($chat['type'] === 'group') ? true : false, // 是否为群组
'isCallback' => $isCallback, // 是否为回调请求 'isCallback' => $isCallback, // 是否为回调请求

189
models/tgDC.php

@ -1,7 +1,7 @@
<?php <?php
class tgDC { class tgDC { // 查询用户DC
private function getDcDetail($dc) { // 获取DC信息 private function getDcDetail($dc) { // 返回DC详细信息
switch ($dc) { switch ($dc) {
case 'DC1': case 'DC1':
return array( return array(
@ -24,153 +24,130 @@ class tgDC {
'addr' => '新加坡' 'addr' => '新加坡'
); );
default: 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用户信息 private function getUserInfo($account) { // 获取Telegram用户信息
$info['account'] = $account;
$info['name'] = null;
$info['dc'] = null; $info['dc'] = null;
$info['name'] = null;
$html = $this->curl('https://t.me/' . $account); // 获取原始HTML数据 $info['account'] = $account;
$html = (new Curl)->get('https://t.me/' . $account); // 获取原始HTML数据
$html = preg_replace('/[\t\n\r]+/', '', $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 = '/<img class="tgme_page_photo_image" src="([^<>]+)">/'; $avatarRegex = '/<img class="tgme_page_photo_image" src="([^<>]+)">/';
$nameRegex = '/<span dir="auto">(.+?)<\/span>/'; $nameRegex = '/<span dir="auto">(.+?)<\/span>/';
preg_match($avatarRegex, $html, $avatarMatch); // 匹配目标头像 preg_match($avatarRegex, $html, $avatarMatch); // 匹配目标头像
preg_match($nameRegex, $html, $nameMatch); // 匹配目标名称 preg_match($nameRegex, $html, $nameMatch); // 匹配目标名称
if ($nameMatch[1]) { if (isset($nameMatch[1])) {
$info['name'] = $nameMatch[1]; // 获取用户名 $info['name'] = $nameMatch[1]; // 获取用户名
} }
if ($avatarMatch[1]) { if (isset($avatarMatch[1])) { // 头像可见
$avatarUrl = $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 ($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 ($info['dc'] != null) {
$info += $this->getDcDetail($info['dc']); // 载入DC详细信息
} }
if ($info['dc']) { $info['time'] = time(); // 记录查询时间戳
$info += $this->getDcDetail($info['dc']); // 匹配DC参数
}
return $info; return $info;
} }
private function getUserInfoCache($account) { // 获取用户信息 带缓存 public function getInfo($account, $isCache = true) { // 获取用户信息 默认带缓存
$redis = new redisCache('tgdc'); $redis = new RedisCache('tgdc');
$info = $redis->getData($account); // 查询缓存数据 $info = $redis->getData($account); // 查询缓存数据
if (!$info) { // 缓存未命中 if (!$isCache || !$info) { // 不缓存 或 缓存未命中
$info = $this->getUserInfo($account); // 发起查询 $info = $this->getUserInfo($account); // 发起查询
if (!$info['name'] && !$info['dc']) { // 用户名与头像均无 $redis->setData($account, json_encode($info)); // 缓存数据 永久
$cacheTTL = 300; // 缓存5min
} else if ($info['name'] && !$info['dc']) { // 存在用户名但未设置头像
$cacheTTL = 20; // 缓存20s
} else {
$cacheTTL = 3600; // 其余情况缓存1h
}
$redis->setData($account, json_encode($info), $cacheTTL); // 缓存数据
} else { // 缓存命中 } else { // 缓存命中
$info = json_decode($info, true); // 使用缓存数据 $info = json_decode($info, true); // 使用缓存数据
} }
return $info; return $info;
} }
}
public function getInfo($account) { // 查询入口 class tgDCEntry { // DC查询入口
if (substr($account, 0, 1) === '@') { // 用户名可带有@ private function checkAccount($account) { // 检查用户合法性
$account = substr($account, 1); 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)) { // 用户名不合法 if (substr($account, 0, 1) === '_' || substr($account, -1) === '_') { // 不能以_开头结尾
return array( return false;
'status' => 'error', }
'message' => '用户名无效' return true;
); }
private function showHelp() { // 显示帮助信息
$message = tgApi::sendMarkdown('*Usage:* `/dc username`');
$message = json_decode($message, true);
return $message['result']['message_id']; // 返回消息ID
} }
$info = $this->getUserInfoCache($account);
private function genMessage($info) { // 生成返回信息
if (!$info['name'] && !$info['dc']) { // 用户名与头像均无 if (!$info['name'] && !$info['dc']) { // 用户名与头像均无
return array( return '@' . $info['account'] . ' 无法识别';
'status' => 'error',
'message' => '@' . $account . ' 无法识别'
);
} else if ($info['name'] && !$info['dc']) { // 存在用户名但未设置头像 } else if ($info['name'] && !$info['dc']) { // 存在用户名但未设置头像
return array( return '@' . $info['account'] . ' 未设置头像或不可见';
'status' => 'error',
'message' => '@' . $account . ' 未设置头像或不可见'
);
} }
return array( $msg = '@' . $info['account'] . ' (' . $info['name'] . ')' . PHP_EOL;
'status' => 'ok', $msg .= '_' . $info['as'] . '_ ';
'data' => json_encode($info) // 返回查询结果 $msg .= '`(``' . $info['ip'] . '``)`' . PHP_EOL;
); $msg .= '*' . $info['dc'] . '* - ' . $info['addr'] . PHP_EOL;
return $msg; // 返回正常查询结果
} }
}
class tgDCEntry { private function sendInfo($account) { // 查询并发送用户信息
private function getInfo($account) { if (!$this->checkAccount($account)) { // 用户名不合法
$content = (new tgDC)->getInfo($account); // 发起查询 tgApi::sendText('用户名无效');
if ($content['status'] === 'ok') { return;
$info = json_decode($content['data'], true); }
$msg = '@' . $info['account'] . ' (' . $info['name'] . ')' . PHP_EOL; $info = (new tgDC)->getInfo($account); // 带缓存查询
$msg .= '<i>' . $info['as'] . '</i> '; $message = tgApi::sendMarkdown($this->genMessage($info)); // 发送预查询信息
$msg .= '<code>(</code><code>' . $info['ip'] . '</code><code>)</code>' . PHP_EOL; if (!$info['name'] && !$info['dc']) {
$msg .= '<b>' . $info['dc'] . '</b> - ' . $info['addr'] . PHP_EOL; $cacheTime = 300; // 未设置用户名或用户不存在 缓存5min
return array( } else if ($info['name'] && !$info['dc']) {
'parse_mode' => 'HTML', // HTML格式输出 $cacheTime = 20; // 用户头像不可见 缓存20s
'text' => $msg
);
} else { } else {
return array( $cacheTime = 86400; // 用户正常 缓存24h
'text' => $content['message'] // 返回错误信息 }
); 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查询入口 public function query($rawParam) { // tgDC查询入口
$helpMsg = array( // 使用说明 global $tgEnv;
'parse_mode' => 'Markdown', if ($rawParam === 'help') { $this->showHelp(); } // 显示使用说明
'text' => '*Usage:* `/dc username`' if ($rawParam == '') {
); $rawParam = $tgEnv['userAccount']; // 空指令时查询对方信息
if ($rawParam === 'help') { // 查询使用说明 if (!$tgEnv['isGroup']) {
tgApi::sendMessage($helpMsg); $messageId = $this->showHelp(); // 非群组发送使用说明
return;
} }
if ($rawParam !== '') { // 查询指定用户数据
tgApi::sendMessage($this->getInfo($rawParam));
return;
} }
global $tgEnv; if (substr($rawParam, 0, 1) === '@') {
if (!$tgEnv['isGroup']) { // 群组不发送帮助信息 $rawParam = substr($rawParam, 1); // 去除用户名前@
$message = json_decode(tgApi::sendMessage($helpMsg), true); // 发送使用说明
} }
tgApi::sendMessage($this->getInfo($tgEnv['userAccount'])); // 查询对方用户名 $this->sendInfo($rawParam); // 查询并发送用户信息
if ($tgEnv['isGroup']) { return; } if (!isset($messageId)) { return; }
fastcgi_finish_request(); // 断开连接
sleep(10); // 延迟10s sleep(10); // 延迟10s
tgApi::deleteMessage(array( // 删除使用说明 tgApi::deleteMessage(array( // 删除使用说明
'message_id' => $message['result']['message_id'] 'message_id' => $messageId
)); ));
} }
} }

3
route.php

@ -32,7 +32,7 @@ function cmdRoute($cmd) { // 命令功能模块路由
case '/punycode': case '/punycode':
return (new punycodeEntry); return (new punycodeEntry);
} }
return null; return null; // 命令不存在
} }
function route($message) { // 请求路由 function route($message) { // 请求路由
@ -52,6 +52,7 @@ function route($message) { // 请求路由
$cmd = substr($cmd, 0, strlen($cmd) - strlen($botAccount) - 1); // 分离@机器人 $cmd = substr($cmd, 0, strlen($cmd) - strlen($botAccount) - 1); // 分离@机器人
} }
} }
$rawParam = trim($rawParam);
$entry = cmdRoute($cmd); // 获取功能模块入口 $entry = cmdRoute($cmd); // 获取功能模块入口
if (!$entry) { return; } // 命令不存在 if (!$entry) { return; } // 命令不存在
if ($tgEnv['isCallback']) { if ($tgEnv['isCallback']) {

Loading…
Cancel
Save