- {if $oNode->duck_class}
-
+ {if $oNode->country_code}
+
{/if}
{if in_array($oNode->pid, $aServerLiveIds)}
diff --git a/src/Command/Cron.php b/src/Command/Cron.php
index 595b8b495..68e02af91 100755
--- a/src/Command/Cron.php
+++ b/src/Command/Cron.php
@@ -84,6 +84,10 @@ EOL;
$jobs->smtpClearDayCount(); // 重置stmp的day_count
+ $jobs->vcodeReport(); // 每日验证码注册率报表
+ $jobs->mailMustReport(); // 每日mailMust报表 - 需在重置之前
+ $jobs->mailMust_reset_month(); // 每天8点,check重置mailMust的month_count
+
// 每日清除前一天残留TgStep
$jobs->clearTgStep();
@@ -170,6 +174,7 @@ EOL;
if ($minute === 0) {
$jobs->hour_width_report(); // 检测小时耗流
// $jobs->mergeBandWidth(); || 逻辑改变,不用了
+ $jobs->mailMust_reset_day(); // 每小时0分,check重置mailMust的day_count
}
// Run email queue
diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php
index a2903c159..a6f0c4742 100755
--- a/src/Controllers/AuthController.php
+++ b/src/Controllers/AuthController.php
@@ -13,7 +13,7 @@ use App\Services\Auth;
use App\Services\Cache;
use App\Services\Captcha;
use App\Services\Filter;
-use App\Services\Mail;
+use App\Services\MailMust;
use App\Services\MFA;
use App\Services\RateLimit;
use App\Services\Reward;
@@ -174,6 +174,7 @@ final class AuthController extends BaseController
*/
public function sendVerify(ServerRequest $request, Response $response, $next): ResponseInterface
{
+
if (Config::obtain('reg_email_verify')) {
$email = strtolower(trim($this->antiXss->xss_clean($request->getParam('email'))));
@@ -205,7 +206,7 @@ final class AuthController extends BaseController
$redis->setex('email_verify:' . $email_code, Config::obtain('email_verify_code_ttl'), $email);
try {
- Mail::send(
+ MailMust::sendVcode(
$email,
$_ENV['appName'] . ' - 验证邮件',
'verify_code.tpl',
diff --git a/src/Models/Config.php b/src/Models/Config.php
index d9fe3d30a..cf02fa58f 100755
--- a/src/Models/Config.php
+++ b/src/Models/Config.php
@@ -32,6 +32,17 @@ final class Config extends Model
default => (string) $config->value,
};
}
+
+ public static function _hit($item)
+ {
+ $config = (new Config())->where('item', $item)->first();
+
+ if (!$config) {
+ return false;
+ }
+
+ return $config->value;
+ }
public static function getClass($class): array
{
diff --git a/src/Models/MailMust.php b/src/Models/MailMust.php
new file mode 100755
index 000000000..6b7ae6a16
--- /dev/null
+++ b/src/Models/MailMust.php
@@ -0,0 +1,34 @@
+increment('inday_count');
+ $this->increment('inmonth_count');
+
+ $this->refresh();
+
+ if ($this->inday_count >= $this->inday_max) {
+ $this->status = -1;
+ $this->save();
+ }
+
+ if ($this->inmonth_count >= $this->inmonth_max) {
+ $this->status = -2;
+ $this->save();
+ }
+
+ }
+}
diff --git a/src/Models/Node.php b/src/Models/Node.php
index 2887a2082..b4bf080fc 100755
--- a/src/Models/Node.php
+++ b/src/Models/Node.php
@@ -61,14 +61,14 @@ final class Node extends Model
return $sMemoFront;
}
- public function getDuckClassAttribute(): string
- {
- $sLocation = $this->duck_group ?: ($this->duck_code ?? '');
-
- $sBeforeDash = strstr($sLocation, '-', true) ?: $sLocation;
-
- return preg_replace('/\d+/', '', $sBeforeDash);
- }
+// public function getDuckClassAttribute(): string
+// {
+// $sLocation = $this->duck_group ?: ($this->duck_code ?? '');
+//
+// $sBeforeDash = strstr($sLocation, '-', true) ?: $sLocation;
+//
+// return preg_replace('/\d+/', '', $sBeforeDash);
+// }
/**
* 节点状态颜色
diff --git a/src/Services/Cron.php b/src/Services/Cron.php
index 8ec9d0223..7b0c7fb88 100755
--- a/src/Services/Cron.php
+++ b/src/Services/Cron.php
@@ -53,11 +53,249 @@ use const PHP_EOL;
use App\Models\Smtp as ModelSmtp;
//use Illuminate\Support\Facades\DB; // 线上可能要开
use Carbon\Carbon;
-
+use App\Models\MailMust as ModelMailMust;
+use App\Models\MailLog as ModelMailLog;
+use App\Services\TomTool\Telegram\Slave as TeleSlave;
final class Cron
{
+ // 必达邮件报表 - 必须在重置之前调用
+ public static function mailMustReport()
+ {
+ $cMailMust = ModelMailMust::where("status", "!=", 0)->get();
+
+ if ($cMailMust->isEmpty()) {
+ return false;
+ }
+
+ $sMsg = "\n====== ".$_ENV["site_code"]."必达邮件报表 ======";
+ foreach ($cMailMust as $oMailMust) {
+
+ $sStatusCn = match ($oMailMust->status) {
+ 1 => " ♻️ 工作中",
+ -1 => " ‼️ 日流量用光",
+ -2 => " ‼️ 月流量用光",
+ default => "",
+ };
+
+ $iSendCountPrev = $oMailMust->inday_count - $oMailMust->inday_count_prev;
+ $iLeft = $oMailMust->inmonth_max - $oMailMust->inmonth_count;
+ $iPerLeft = round(($iLeft / $oMailMust->inmonth_max * 100), 2);
+
+ // *start 距离天计算
+ $iResetDay = $oMailMust->inmonth_reset_day;
+
+ $oNow = new DateTime(); // 当前时间
+ $oResetDate = new DateTime(); // 初始化重置日期对象
+ $oResetDate->setDate((int)$oNow->format('Y'), (int)$oNow->format('m'), $iResetDay);
+ $oResetDate->setTime(0, 0, 0); // 规范化到当天凌晨
+
+ // 如果当前日期已经过了(或正好是)本月的重置日
+ if ($oNow >= $oResetDate) {
+ $oResetDate->modify('+1 month'); // 目标锁定下个月 10 号
+ }
+
+ // 计算差值
+ $oInterval = $oNow->diff($oResetDate);
+ $iDaysRem = (int)$oInterval->format('%a'); // 剩余天数
+ // *end 距离天计算
+
+ $sMsg .= "\n---- {$oMailMust->client}{$sStatusCn} ----";
+ $sMsg .= "\n昨日发送数:{$iSendCountPrev}";
+ $sMsg .= "\n月余量:{$iPerLeft}%({$iLeft})";
+ $sMsg .= "\n距重置:{$iDaysRem}天";
+ $sMsg .= "\n[日限额:{$oMailMust->inday_max}、月限额:{$oMailMust->inmonth_max}]";
+ }
+
+ TeleSlave::notify()->enableDetail(false)->send($sMsg);
+
+ echo "必达邮件报表发送完成";
+ }
+
+ // 注册验证邮件报表
+ public static function vcodeReport()
+ {
+
+ $sPrevTime = strtotime("-1 day");
+// $sPrevTime = strtotime("-1 day"); // test
+ $sPrevDate = date("Y-m-d", $sPrevTime);
+
+ $cMailLog = ModelMailLog::where("scene", "vcode")->whereDate("date", $sPrevDate)->get();
+
+ if ($cMailLog->isEmpty()) {
+ return false;
+ }
+
+ $aUserEmails = User::whereDate("reg_date", $sPrevDate)->pluck("email")->toArray();
+ $aUserEmails = array_flip($aUserEmails);
+
+ $aMailUser = [];
+ $iTotalSendCount = 0; // 发送数量-重复
+ $cCom = [];
+ foreach ($cMailLog as $oMailLog) {
+ $iTotalSendCount++;
+ $aComTmp = explode('@', $oMailLog->to);
+ $sCom = end($aComTmp);
+
+ //// sart mailUser统计
+ if (!isset($aMailUser[$oMailLog->to])) { // 初始mailUser
+ $aMailUser[$oMailLog->to] = [
+ "bRegDone" => false,
+ "iSendCount" => 0,
+ "sClient" => '',
+ "sCom" => $sCom
+ ];
+ }
+
+ $aMailUser[$oMailLog->to]["iSendCount"]++;
+ $aMailUser[$oMailLog->to]["sClient"] = $oMailLog->client ?? '';
+
+ if (isset($aUserEmails[$oMailLog->to])) {
+ $aMailUser[$oMailLog->to]["bRegDone"] = true;
+ }
+ //// end mailUser统计
+
+ // start com统计
+ if (!isset($cCom[$sCom])) { // 初始com
+ $cCom[$sCom] = [
+ "sName" => $sCom,
+ "iSendCount" => 0,
+ "iRegDoneCount" => 0,
+ "iRegLossCount" => 0
+ ];
+ }
+ $cCom[$sCom]["iSendCount"]++;
+ // end com统计
+
+ }
+
+ $aComInfo = $cCom; // cominfo
+ $iRegDoneCount = 0; // 注册成功
+ $iRegLossCount = 0; // 注册失败-彻底(发码而未注册上)
+ $iMailUserCount = count($aMailUser); // 发送数量-非重复
+ $aFoolUser = []; // 发n次邮箱却没注册上的用户
+ $iFoolLimit = 4; // 参数
+ $iRegDoneSendCount = 0;
+ $iRegLossSendCount = 0;
+ foreach ($aMailUser as $sUserMail => $aCounts) {
+
+ if (!isset($aComInfo[$aCounts["sCom"]])) { // 按说不需要,稳健一点
+ $aComInfo[$aCounts["sCom"]] = [
+ "sName" => $sCom,
+ "iSendCount" => 0,
+ "iRegDoneCount" => 0,
+ "iRegLossCount" => 0
+ ];
+ }
+
+ if ($aCounts["bRegDone"] == true) {
+ $iRegDoneCount++;
+ $iRegDoneSendCount += $aCounts["iSendCount"];
+ $aComInfo[$aCounts["sCom"]]["iRegDoneCount"]++;
+ } else {
+ $iRegLossCount++;
+ $iRegLossSendCount += $aCounts["iSendCount"];
+ $aComInfo[$aCounts["sCom"]]["iRegLossCount"]++;
+ }
+
+ if ($aCounts["bRegDone"] == false && $aCounts["iSendCount"] > $iFoolLimit) {
+ $aFoolUser[$sUserMail] = $aCounts;
+ }
+
+ }
+
+
+ $iPerTotalRedDone = $iTotalSendCount > 0 ? round(($iRegDoneCount / $iTotalSendCount) * 100, 2) : 0;
+ $iAvgTotalSend = $iMailUserCount > 0 ? round($iTotalSendCount / $iMailUserCount, 2) : 0; // 平均每个用户发送了多少封邮件
+ $iAvgRegDone = $iRegDoneCount > 0 ? round($iRegDoneSendCount / $iRegDoneCount, 2) : 0; // 注册上的均发
+ $iPerRegDone = $iMailUserCount > 0 ? round(($iRegDoneCount / $iMailUserCount) * 100, 2) : 0; // 去重注册上率
+ $iAvgRegLoss = $iRegLossCount > 0 ? round($iRegLossSendCount / $iRegLossCount, 2) : 0; // 没注册上的均发
+ $iPerRegLoss = $iMailUserCount > 0 ? round(($iRegLossCount / $iMailUserCount) * 100, 2) : 0; // 去重没注册上率
+
+ $sMsg = "\n====== ".$_ENV["site_code"]."发码表报 - 注册 ======";
+ $sMsg .= "\n总发送:{$iTotalSendCount}";
+ $sMsg .= "\n总均发:{$iAvgTotalSend}";
+ $sMsg .= "\n总注册上率:{$iPerTotalRedDone}%({$iRegDoneCount}个)";
+ $sMsg .= "\n去重发送:{$iMailUserCount}";
+ $sMsg .= "\n去重注册上率:{$iPerRegDone}% [数量:{$iRegDoneCount}、均发:{$iAvgRegDone}]";
+ $sMsg .= "\n去重没注册上率:{$iPerRegLoss}% [数量:{$iRegLossCount}、均发:{$iAvgRegLoss}]";
+
+ TeleSlave::notify()->enableDetail(false)->send($sMsg);
+
+ usort($aComInfo, function ($a, $b) {
+ $iPerA = $a["iRegDoneCount"] / $a["iSendCount"];
+ $iPerB = $b["iRegDoneCount"] / $b["iSendCount"];
+ // 升序:如果 a 小于 b 返回负数
+ return $iPerB <=> $iPerA;
+ });
+
+ $sMsg = "\n====== ".$_ENV["site_code"]."发码表报 - 邮箱 ======";
+ foreach ($aComInfo as $sCom => $aCom) {
+ $iPerComReg = ($aCom["iRegDoneCount"] / $aCom["iSendCount"]) * 100;
+ $sMsg .= "\n".$aCom["sName"]." [成功率:{$iPerComReg}%、数量:".$aCom["iSendCount"]."]";
+ }
+
+ TeleSlave::notify()->enableDetail(false)->send($sMsg);
+
+ echo "ok";
+ }
+
+ // 重置mailMust day
+ public static function mailMust_reset_day()
+ {
+ $iCurrentHour = (int) date('H');
+// $iCurrentHour = 8; // test
+
+ $cModels = ModelMailMust::where('inday_reset_hour', $iCurrentHour)->get();
+
+ if ($cModels->isEmpty()) {
+ return;
+ }
+
+ foreach ($cModels as $oModelMailMust) {
+
+ $oModelMailMust->inday_count_prev += $oModelMailMust->inday_count;
+ $oModelMailMust->inday_count = 0;
+
+ if ($oModelMailMust->status === -1) {
+ $oModelMailMust->status = 1;
+ }
+
+ $oModelMailMust->save();
+ }
+
+ echo "\nmailMust_reset_day - ok";
+ }
+
+ // 重置maulMust month
+ public static function mailMust_reset_month()
+ {
+ $j = (int) date("j");
+// $j = 1; //test
+
+ $cModelMailMust = ModelMailMust::where('inmonth_reset_day', $j)->get();
+
+ if ($cModelMailMust->isEmpty()) {
+ return;
+ }
+
+ foreach ($cModelMailMust as $oModelMailMust) {
+
+ $oModelMailMust->inmonth_count = 0;
+ $oModelMailMust->inday_count_prev = 0;
+
+ if ($oModelMailMust->status === -2) {
+ $oModelMailMust->status = 1;
+ }
+
+ $oModelMailMust->save();
+ }
+
+ echo "\nmailMust_reset_month - ok";
+ }
+
+
//// 商品自动化
public static function productAuto(): void
{
@@ -268,7 +506,7 @@ final class Cron
}
$aOrder = Order::whereBetween("update_time", [$sPrevTimeStart, $sPrevTimeEnd])
- ->where("status", "activated")
+ ->whereIn("status", ["activated", "pending"])
->get()
->toArray();
@@ -2070,15 +2308,15 @@ final class Cron
$text_html .= '
昨日总收入笔数:' . count($paylists) . '
昨日总收入金额:' . $paylists->sum('total');
echo 'Sending daily finance email to admin user' . PHP_EOL;
- try {
- Notification::notifyAdmin(
- '财务日报',
- $text_html,
- 'finance.tpl'
- );
- } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
- echo $e->getMessage() . PHP_EOL;
- }
+// try {
+// Notification::notifyAdmin(
+// '财务日报',
+// $text_html,
+// 'finance.tpl'
+// );
+// } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
+// echo $e->getMessage() . PHP_EOL;
+// }
echo Tools::toDateTime(time()) . ' 成功发送财务日报' . PHP_EOL;
}
@@ -2094,15 +2332,15 @@ final class Cron
$text_html = '
上周总收入笔数:' . count($paylists) . '
上周总收入金额:' . $paylists->sum('total');
echo 'Sending weekly finance email to admin user' . PHP_EOL;
- try {
- Notification::notifyAdmin(
- '财务周报',
- $text_html,
- 'finance.tpl'
- );
- } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
- echo $e->getMessage() . PHP_EOL;
- }
+// try {
+// Notification::notifyAdmin(
+// '财务周报',
+// $text_html,
+// 'finance.tpl'
+// );
+// } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
+// echo $e->getMessage() . PHP_EOL;
+// }
echo Tools::toDateTime(time()) . ' 成功发送财务周报' . PHP_EOL;
}
@@ -2118,15 +2356,15 @@ final class Cron
$text_html = '
上月总收入笔数:' . count($paylists) . '
上月总收入金额:' . $paylists->sum('total');
echo 'Sending monthly finance email to admin user' . PHP_EOL;
- try {
- Notification::notifyAdmin(
- '财务月报',
- $text_html,
- 'finance.tpl'
- );
- } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
- echo $e->getMessage() . PHP_EOL;
- }
+// try {
+// Notification::notifyAdmin(
+// '财务月报',
+// $text_html,
+// 'finance.tpl'
+// );
+// } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
+// echo $e->getMessage() . PHP_EOL;
+// }
echo Tools::toDateTime(time()) . ' 成功发送财务月报' . PHP_EOL;
}
diff --git a/src/Services/Mail.php b/src/Services/Mail.php
index 771bd4c4f..4e3c3a647 100755
--- a/src/Services/Mail.php
+++ b/src/Services/Mail.php
@@ -55,9 +55,12 @@ final class Mail
return $smarty->fetch($template);
}
+
/**
- * @throws Exception
- * @throws ClientExceptionInterface
+ to = 对方email
+ subject = 标题
+ tmplate = 使用哪个模版
+ array = 模版内容说是? - 使用smarty模版渲染arr的key是变量名
*/
public static function send($to, $subject, $template, $array = []): void
{
diff --git a/src/Services/Mail/Resend.php b/src/Services/Mail/Resend.php
new file mode 100755
index 000000000..9e2d6fdd0
--- /dev/null
+++ b/src/Services/Mail/Resend.php
@@ -0,0 +1,79 @@
+oModelMailMust = ModelMailMust::where('client', 'resend')->first();
+ $this->oMailSdk = ResendSdk::client($this->oModelMailMust->apikey);
+ }
+
+ public function send($to, $subject, $body): void
+ {
+
+ $oResult = $this->oMailSdk->emails->send([
+ 'from' => $this->oModelMailMust->auther,
+ 'to' => [$to],
+ 'subject' => $subject,
+ 'html' => $body,
+ ]);
+
+ $sResultId = is_array($oResult) ? ($oResult['id'] ?? null) : ($oResult->id ?? null);
+
+ if (!$sResultId) {
+ throw new Exception("Resend 服务器未返回有效 ID");
+ }
+
+ $this->oModelMailMust->_incr();
+
+ }
+
+ public function getClientCode()
+ {
+ return 'resend';
+ }
+
+// public function send($to, $subject, $body): void
+// {
+// try {
+//
+// $oResult = $this->oMailSdk->emails->send([
+// 'from' => $this->oModelMailMust->auther,
+// 'to' => [$to],
+// 'subject' => $subject,
+// 'html' => $body,
+// ]);
+//
+// $sResultId = is_array($oResult) ? ($oResult['id'] ?? null) : ($oResult->id ?? null);
+//
+// if (!$sResultId) {
+// throw new Exception("Resend 服务器未返回有效 ID");
+// }
+//
+// $this->oModelMailMust->_incr();
+//
+// } catch (\Exception $e) {
+//
+// $s = "有邮件发送失败(logid:{$oMailLog->id}): " . $e->getMessage();
+//
+// TeleSlave::warn()->send($s);
+//
+// }
+//
+// }
+}
diff --git a/src/Services/MailMust.php b/src/Services/MailMust.php
new file mode 100755
index 000000000..daea9140f
--- /dev/null
+++ b/src/Services/MailMust.php
@@ -0,0 +1,72 @@
+orderBy("sort")->first();
+
+ if (!$oMailMust) {
+ return new NullMail();
+ }
+
+ return match ($oMailMust->client) {
+ 'resend' => new Resend(),
+ default => new NullMail(),
+ };
+
+ }
+
+ public static function send($to, $subject, $template, $array = [], $scene = ''): void
+ {
+
+ //
+ $sGroupUse = Config::_hit("mailMustMap_clientGroup:".$scene);
+ if ($sGroupUse == "must") {
+ $oClient = self::getClient();
+ } else {
+ $oClient = Mail::getClient();
+ }
+
+ $oMailLog = new ModelMailLog();
+ $oMailLog->to = $to;
+ $oMailLog->title = $subject;
+ $oMailLog->scene = $scene;
+ $oMailLog->client = $oClient->getClientCode();
+ $oMailLog->date = date("Y-m-d H:i:s");
+ $oMailLog->save();
+
+ $body = Mail::genHtml($template, $array);
+
+ try {
+ $oClient->send($to, $subject, $body);
+ } catch (\Exception $e) {
+ TeleSlave::warn()->send("有邮件发送失败({$oMailLog->id}):".$e->getMessage());
+ throw $e;
+ }
+
+ }
+
+ public static function sendVcode($to, $subject, $template, $array = [], $scene = 'vcode')
+ {
+ self::send($to, $subject, $template, $array, $scene);
+ }
+
+
+
+}
diff --git a/src/Services/Tg/Hook/Mod/Config.php b/src/Services/Tg/Hook/Mod/Config.php
new file mode 100644
index 000000000..68391d913
--- /dev/null
+++ b/src/Services/Tg/Hook/Mod/Config.php
@@ -0,0 +1,50 @@
+oDog = new Dog(new ModelConfig());
+ $this->oDog->_setDogName("config"); // 必须,me用,正常就是类名,只不过后期没准改名,所以就这里写死
+ $this->oDog->_setPrimaryKey("item"); // 可选,默认id
+// $this->oDog->_setStrField(["content"]); // 可选,指定长文本字段
+ }
+
+ public function __call($sName, $aArg)
+ {
+ return $this->oDog->$sName($aArg[0]);
+ }
+
+ public function t1()
+ {
+ $r = ModelConfig::_hit("mailMustMap_clientGroup:vcode");
+ tomd($r, 1);
+ }
+
+// public function show($aParam)
+// {
+// $sDate = $aParam["aArg"][1] ?? date("Y-m-d");
+//
+// $aMailLog = ModelMailLog::whereDate("date", $sDate)->get()->toArray();
+//
+// return $this->toJson($aMailLog);
+// }
+//
+// public function count()
+// {
+// $aCount = ModelMailLog::selectRaw('date, count(*) as total')
+// ->groupBy('date')
+// ->get();
+//
+// return $this->toJson($aCount);
+// }
+
+}
diff --git a/src/Services/Tg/Hook/Mod/Test.php b/src/Services/Tg/Hook/Mod/Test.php
index 3a103f3cf..680820437 100644
--- a/src/Services/Tg/Hook/Mod/Test.php
+++ b/src/Services/Tg/Hook/Mod/Test.php
@@ -12,6 +12,8 @@ use App\Services\Cron;
use App\Command\Cron as CommandCron;
use App\Services\TomTool\Http;
use App\Models\PlanQueue;
+use Resend;
+use App\Services\TomTool\Telegram\Slave as TeleSlave;
final class Test extends Base
{
@@ -42,10 +44,34 @@ final class Test extends Base
// abort(404);
}
+ public function job($aParam)
+ {
+ return $this->cron($aParam);
+ }
+
public function t1()
{
- echo 123;
+
+ $oTeleSlave = TeleSlave::warn("xxxx")->send("123123");
+
+
exit;
+
+ $iPayClass = $_ENV["resend_apikey"];
+
+ echo $iPayClass;
+
+ exit;
+ $resend = Resend::client('re_bopdT4FJ_Na1R1sHVaHGAWuWwXZ6VgH5U');
+
+ $resend->emails->send([
+ 'from' => 'Acme ',
+ 'to' => ['mukaitao@proton.me'],
+ 'subject' => 'hello world',
+ 'html' => 'it works!',
+ ]);
+
+ echo 1;exit;
}
public function rootGet()
diff --git a/src/Services/TomTool/Flow.php b/src/Services/TomTool/Flow.php
new file mode 100755
index 000000000..7f2aa38e4
--- /dev/null
+++ b/src/Services/TomTool/Flow.php
@@ -0,0 +1,425 @@
+fail("kkkkkkk");
+
+ 进阶:
+ Flow::make("name")->noDone()->msg("哟哟哟哟哟哟")->sendTg()->fetch();
+
+ 业务:
+ step()
+ msg()
+ data()
+ aData()
+ jData()
+ sData()
+ code()
+ put()
+ merge()
+ toDone()
+ noDone()
+ step("start")
+
+ action:
+ reset()
+ done(?$msg)
+ fail(?$msg, ?$aParentResult)
+ sendTg() 伪action
+ fetch() or get()
+ fetchAll() or geAll()
+
+ set:
+ enableTg();
+ parent($aParentResult);
+
+ with:
+ withTraceId();
+ withTimestamp();
+ withStep();
+
+ 判断:
+ ::isDone($aResult);
+ ::isFail($aResult);
+
+ */
+
+class Flow
+{
+ protected array $aStep = [];
+ protected array $aParent = [];
+ protected bool $isToTg = false;
+ protected bool $isToTgOne = false;
+ protected array $aResult = [
+ 'isDone' => false,
+ ];
+ protected string $sName;
+ private bool $isChain = false;
+
+ public function __construct(string $name = 'none')
+ {
+ $this->sName = $name;
+ $this->aStep[] = "{$this->sName}::start";
+ }
+
+ // new
+ public static function make(string $name = 'none'): self
+ {
+ return new self($name);
+ }
+
+ // new
+ public static function start($sName = 'none')
+ {
+ $oInstance = new self($sName);
+ $oInstance->isChain = true;
+ return $oInstance;
+ }
+
+ // option
+ public function enableTg(bool $flag = true): self
+ {
+ $this->isToTg = $flag;
+ return $this;
+ }
+
+ // option
+ public function enableTgOne(bool $flag = true): self
+ {
+ $this->enableTg(true);
+ $this->isToTgOne = $flag;
+ return $this;
+ }
+
+ // option
+ public function parent(array $aParentResult): self
+ {
+ $this->aParent = $aParentResult;
+ return $this;
+ }
+
+ // data
+ public function step(string $step): self
+ {
+ $this->aStep[] = "{$this->sName}::{$step}";
+ return $this;
+ }
+
+ // data
+ public function msg($msg = "msg未设置"): self
+ {
+ if (is_array($msg)) {
+ $msg = json_encode($msg);
+ }
+ $this->put('sMsg', $msg);
+ return $this;
+ }
+
+ // data
+ public function aData(array $data): self
+ {
+ $this->put('aData', $data);
+ return $this;
+ }
+
+ // data
+ public function aDataPut($k, $v)
+ {
+ $this->aResult["aData"][$k] = $v;
+ return $this;
+ }
+
+ // data
+ public function sData(string $data): self
+ {
+ $this->put('sData', $data);
+ return $this;
+ }
+
+ // data
+ public function code(string $code): self
+ {
+ $this->put('sCode', $code);
+ return $this;
+ }
+
+ // data
+ public function put(string $key, mixed $value, $sDataKey = ''): self
+ {
+ if ($sDataKey) {
+ $this->aResult[$sDataKey][$key] = $value;
+ } else {
+ $this->aResult[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ // data
+ public function merge(array $data): self
+ {
+ foreach ($data as $k => $v) {
+ $this->put($k, $v);
+ }
+ return $this;
+ }
+
+ // data
+ public function setDone(bool $b = true): self
+ {
+ $this->aResult['isDone'] = $b;
+ return $this;
+ }
+
+ // data
+ public function setFail(string $step = ''): self
+ {
+ if ($step) {
+ $this->step($step);
+ }
+ $this->aResult['isDone'] = false;
+ return $this;
+ }
+
+ // with
+ public function withTraceId(string $id = null): self
+ {
+ $this->aResult['sTraceId'] = $id ?? uniqid('trace_', true);
+ return $this;
+ }
+
+ // with
+ public function withTimestamp(): self
+ {
+ $this->aResult['sTimestamp'] = (new \DateTime())->format(\DateTime::ATOM);
+ return $this;
+ }
+
+ // with
+ public function withStep(bool $flag = true): self
+ {
+ if ($flag) {
+ $this->withParentStep();
+ $this->aResult['aStep'] = $this->aStep;
+ } else {
+ unset($this->aResult['aStep']);
+ }
+ return $this;
+ }
+
+ // action
+ public function reset(): self
+ {
+ $this->aStep = [$this->sName . '::start'];
+ $this->aParent = [];
+ $this->isToTg = false;
+ $this->isToTgOne = false;
+ $this->aResult = ['isDone' => false];
+ return $this;
+ }
+
+ // action
+ public function done(mixed $msg = null)
+ {
+ $this->setDone();
+ if ($msg) {
+ $this->msg($msg);
+ }
+ return $this->get();
+ }
+
+ // action
+ public function fail(mixed $msg = null, array $aParentResult = [])
+ {
+ $this->setFail('fail');
+ if ($aParentResult) {
+ $this->aParent = $aParentResult;
+ }
+ if ($msg) {
+ $this->msg($msg);
+ }
+ return $this->getFull();
+ }
+
+ // action
+// public function sendTg() // 伪装成非set
+// {
+// $this->enableTgOne();
+// return $this;
+// }
+
+ // action
+ public function sendTg($sBotCode = 'base', $sChatCode = 'base')
+ {
+
+ }
+
+ public function getDataA()
+ {
+ return $this->aResult["aData"] ?? [];
+ }
+
+ public function getDataS()
+ {
+ return (string) $this->aResult["sData"] ?? '';
+ }
+
+ public function getDataI()
+ {
+ return (int) $this->aResult["iData"] ?? 0;
+ }
+
+ public function getDataB()
+ {
+ return (bool) $this->aResult["bData"] ?? false;
+ }
+
+ public function getDataX()
+ {
+ return $this->aResult["xData"] ?? '';
+ }
+
+ public function setDataA(array $aData): self
+ {
+ $this->aData($aData);
+ return $this;
+ }
+
+ public function setDataB(bool $bData): self
+ {
+ $this->aResult["bData"] = $bData;
+ return $this;
+ }
+
+ public function setDataS(string $sData): self
+ {
+ $this->aResult["sData"] = $sData;
+ return $this;
+ }
+
+ public function setDataI(int $iData): self
+ {
+ $this->aResult["iData"] = $iData;
+ return $this;
+ }
+
+ public function setDataX($xData): self
+ {
+ $this->aResult["xData"] = $xData;
+ return $this;
+ }
+
+ public function getResult(): array
+ {
+ return $this->aResult;
+ }
+
+ // action
+ public function get()
+ {
+ if ($this->isToTg) {
+ $this->_sendTg();
+ }
+
+ if ($this->isToTgOne) {
+ $this->isToTg = false;
+ $this->isToTgOne = false;
+ }
+
+ if ($this->isChain) {
+ return $this;
+ }
+
+ return $this->aResult;
+ }
+
+ // action
+ public function getFull()
+ {
+ $this->withStep();
+ return $this->get();
+ }
+
+ // action
+ public function toArray()
+ {
+ return $this->aResult;
+ }
+
+ // action
+ public function toJson($bFormat = true)
+ {
+ if ($bFormat === true) {
+ return json_encode($this->aResult, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+ } else {
+ return json_encode($this->aResult);
+ }
+ }
+
+ // helper
+ public function isDone()
+ {
+ return $this->aResult["isDone"] ?? false;
+ }
+
+ // helper
+ public function isFail()
+ {
+ return !$this->isDone();
+ }
+
+ // helper
+ public static function isDoneResult(array $aResult): bool
+ {
+ return $aResult['isDone'] ?? false;
+ }
+
+ // helper
+ public static function isFailResult(array $aResult): bool
+ {
+ return !self::isDoneResult($aResult);
+ }
+
+ // shell
+ public function fetch()
+ {
+ return $this->get();
+ }
+
+ // shell
+ public function fetchAll()
+ {
+ return $this->getFull();
+ }
+
+ private function _sendTg()
+ {
+ TelegramVdb::make()->dataArr($this->aResult)->save();
+ }
+
+ protected function addStepParent(): void
+ {
+ $this->aStep[] = $this->aParent;
+ }
+
+ protected function withParentStep(): void
+ {
+ if ($this->aParent) {
+ $this->addStepParent();
+ }
+ }
+}
diff --git a/src/Services/TomTool/HttpV2.php b/src/Services/TomTool/HttpV2.php
new file mode 100755
index 000000000..4cc18ca1e
--- /dev/null
+++ b/src/Services/TomTool/HttpV2.php
@@ -0,0 +1,203 @@
+send()
+
+ 进阶:
+ HttpV2::make($url, "get")->setDataA($aData)->enableJsonAs()->enableAsync()->optMethod("get")->send();
+
+ action:
+ send()
+
+ data:
+ xData()
+ sData()
+ jData()
+ sData()
+
+ set:
+ optMethod()
+ enableJsonAs()
+ enableJsonSend()
+ enableJsonReturn()
+
+ @note:
+ 默认关闭 SSL 验证(适用于开发环境),生产环境建议开启
+ 异步模式下不会返回响应内容,仅快速触发请求
+
+ */
+
+
+class HttpV2
+{
+ protected string $method;
+ protected $data = [];
+ protected string $url;
+ protected bool $sendJson = false;
+ protected bool $returnJson = false;
+ protected bool $async = false;
+ // 匈牙利命名法,仅新增一个 token 变量
+ protected string $sToken = '';
+
+ public static function make(string $url, string $method = 'post'): self
+ {
+ $instance = new self();
+ $instance->url = $url;
+ $instance->optMethod($method);
+ return $instance;
+ }
+
+ public function optMethod(string $method): self
+ {
+ $this->method = strtolower($method);
+ return $this;
+ }
+
+ /**
+ * 新增:支持携带 Token
+ * 如果不传值,默认从环境变量 token_base 获取
+ */
+ public function withToken(?string $sToken = null): self
+ {
+ $this->sToken = $sToken ?? (string)env('token_base', false);
+ return $this;
+ }
+
+ public function setDataX($xData)
+ {
+ $this->setData($xData);
+ return $this;
+ }
+
+ public function setDataA($aData)
+ {
+ $this->setData($aData);
+ return $this;
+ }
+
+ public function setDataS($sData)
+ {
+ $this->setData($sData);
+ return $this;
+ }
+
+ public function setData($xData)
+ {
+ $this->data = $xData;
+ return $this;
+ }
+
+ public function enableJsonSend(bool $flag = true): self
+ {
+ $this->sendJson = $flag;
+ return $this;
+ }
+
+ public function enableJsonReturn(bool $flag = true): self
+ {
+ $this->returnJson = $flag;
+ return $this;
+ }
+
+ public function enableJsonAs(): self
+ {
+ $this->sendJson = true;
+ $this->returnJson = true;
+ return $this;
+ }
+
+ public function enableAsync(bool $flag = true): self
+ {
+ $this->async = $flag;
+ return $this;
+ }
+
+ public function send(): string
+ {
+ $oFlow = Flow::make("HttpV2::send");
+
+ $ch = curl_init();
+ $headers = [];
+
+ // --- 核心嵌入:如果 sToken 有值且不是 false,塞进 Header ---
+ if ($this->sToken && $this->sToken !== 'false') {
+ $headers[] = 'Authorization: Bearer ' . $this->sToken;
+ }
+
+ // 构建 URL 和请求体 (完全保留原样)
+ if ($this->method === 'get') {
+ $url = $this->url . '?' . http_build_query($this->data);
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HTTPGET, true);
+ } else {
+ curl_setopt($ch, CURLOPT_URL, $this->url);
+ $payload = $this->sendJson ? json_encode($this->data) : http_build_query($this->data);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
+
+ if ($this->method === 'post') {
+ curl_setopt($ch, CURLOPT_POST, true);
+ } else {
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method));
+ }
+
+ if ($this->sendJson) {
+ $headers[] = 'Content-Type: application/json';
+ $headers[] = 'Content-Length: ' . strlen($payload);
+ }
+ }
+
+ if ($this->returnJson) {
+ $headers[] = 'Accept: application/json';
+ }
+
+ if (!empty($headers)) {
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ }
+
+ // SSL 设置 (完全保留原样)
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+
+ // 异步发送 (完全保留原样)
+ if ($this->async) {
+ curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 200);
+ curl_setopt($ch, CURLOPT_NOSIGNAL, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
+ curl_exec($ch);
+ curl_close($ch);
+ return 'sent';
+ }
+
+ // 阻塞执行 (完全保留原样)
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $response = curl_exec($ch);
+
+ if (curl_errno($ch)) {
+ $error = curl_error($ch);
+ curl_close($ch);
+ throw new Exception("cURL Error: {$error}");
+ }
+
+ curl_close($ch);
+
+ return $response;
+ }
+}
diff --git a/src/Services/TomTool/Telegram/Master.php b/src/Services/TomTool/Telegram/Master.php
new file mode 100755
index 000000000..cdb8961d3
--- /dev/null
+++ b/src/Services/TomTool/Telegram/Master.php
@@ -0,0 +1,177 @@
+sBotCode = $sBotCode;
+ $oInstance->sChatCode = $sChatCode;
+ return $oInstance;
+ }
+
+ public function setBotCode(string $sBotCode): self
+ {
+ $this->sBotCode = $sBotCode;
+ return $this;
+ }
+
+ public function setChatCode(string $sChatCode): self
+ {
+ $this->sChatCode = $sChatCode;
+ return $this;
+ }
+
+ public function setBotToken(string $sBotToken): self
+ {
+ $this->sBotToken = $sBotToken;
+ return $this;
+ }
+
+ public function setChatId(int $iChatId): self
+ {
+ $this->iChatId = $iChatId;
+ return $this;
+ }
+
+ public function setMsgS(string $sMsg): self
+ {
+ $this->sMsg = $sMsg;
+ return $this;
+ }
+
+ public function setPhotoUrl(string $sPhotoUrl, int $iPhotoWidth = 0, int $iPhotoHeight = 0): self
+ {
+ $this->sPhotoUrl = $sPhotoUrl;
+ $this->iPhotoWidth = $iPhotoWidth;
+ $this->iPhotoHeight = $iPhotoHeight;
+ return $this;
+ }
+
+ public function setType(string $sType): self
+ {
+ $this->sType = $sType;
+ return $this;
+ }
+
+ public function setFileUrl(string $sFileUrl = '', string $sFileName = ''): self
+ {
+ $this->sFileUrl = $sFileUrl;
+ $this->sFileName = $sFileName;
+ return $this;
+ }
+
+ public function send($sMsg = ''): Flow
+ {
+ if ($sMsg) {
+ $this->sMsg = $sMsg;
+ }
+
+ if (!$this->sMsg) {
+// throw new \Exception("未设置msg");
+ }
+
+ $this->keyBegin();
+
+ if ($this->enableQueue) {
+ $aData = $this->buildDataTom();
+ $oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
+ } else {
+ $oFlow = ServiceTelegramSdk::start()
+ ->setBotToken($this->sBotToken)
+ ->setChatId($this->iChatId)
+ ->setMsgS($this->sMsg)
+ ->setPhotoUrl($this->sPhotoUrl, $this->iPhotoWidth, $this->iPhotoHeight)
+ ->setFileUrl($this->sFileUrl, $this->sFileName)
+ ->setType($this->sType)
+ ->send();
+ }
+
+ return $oFlow;
+ }
+
+ private function keyBegin()
+ {
+ if (!$this->bKeyBegin) {
+ if (!$this->sBotToken) {
+ $this->sBotToken = ModelTelegramKey::botTokenByCode($this->sBotCode);
+ }
+
+ if (!$this->iChatId) {
+ $this->iChatId = ModelTelegramKey::chatIdByCode($this->sChatCode);
+ }
+
+ if (!$this->sBotToken || !$this->iChatId) {
+ throw new \RuntimeException("botToken或chatId没找到,botCode={$this->sBotCode},chatCode={$this->sChatCode}");
+ }
+
+ $this->bKeyBegin = true;
+ }
+ }
+
+ private function buildDataTom(): array
+ {
+ return [
+ "sBotCode" => $this->sBotCode ?? '',
+ "sChatCode" => $this->sChatCode ?? '',
+ "sBotToken" => $this->sBotToken ?? '',
+ "iChatId" => $this->iChatId ?? '',
+ "sPhotoUrl" => $this->sPhotoUrl ?? '',
+ "iPhotoWidth" => $this->iPhotoWidth,
+ "iPhotoHeight" => $this->iPhotoHeight,
+ "sFileUrl" => $this->sFileUrl,
+ "sFileName" => $this->sFileName,
+ "sMsg" => $this->sMsg ?? '',
+ "sType" => $this->sType ?? '',
+ ];
+ }
+
+
+}
diff --git a/src/Services/TomTool/Telegram/Sdk.php b/src/Services/TomTool/Telegram/Sdk.php
new file mode 100755
index 000000000..b022dd6d8
--- /dev/null
+++ b/src/Services/TomTool/Telegram/Sdk.php
@@ -0,0 +1,215 @@
+oApi = new Api($sBotToken);
+ $this->sBotToken = $sBotToken;
+ return $this;
+ }
+
+ public function setChatId($iChatId)
+ {
+ $this->iChatId = $iChatId;
+ return $this;
+ }
+
+ public function setMsgS($sMsg = '')
+ {
+ $this->sMsg = $sMsg;
+ return $this;
+ }
+
+ public function setPhotoUrl($sUrl = '', $iPhotoWidth = 0, $iPhotoHeight = 0)
+ {
+ $this->sPhotoUrl = $sUrl;
+ $this->iPhotoWidth = $iPhotoWidth;
+ $this->iPhotoHeight = $iPhotoHeight;
+ return $this;
+ }
+
+ public function setFileUrl($sFileUrl = '', $sFileName = '')
+ {
+ $this->sFileUrl = $sFileUrl;
+ $this->sFileName = $sFileName;
+ return $this;
+ }
+
+ public function setType($sType)
+ {
+ if ($sType) {
+ $this->sMsgFormatType = $sType;
+ }
+
+ return $this;
+ }
+
+ public function send()
+ {
+ $oFlow = Flow::start("tg_sdk_send");
+
+ $this->msgFormat();
+
+// try {
+
+ if ($this->sPhotoUrl) {
+ $r = $this->oApi->sendPhoto($this->sMsgFormat);
+ } else if ($this->sFileUrl) {
+ $r = $this->oApi->sendDocument($this->sMsgFormat);
+ } else {
+ $r = $this->oApi->sendMessage($this->sMsgFormat);
+ }
+
+ if (!($r && $r->getMessageId())) {
+ return $oFlow->fail($r);
+ }
+// } catch (TelegramSDKException $e) {
+// return $oFlow->fail(['error' => $e->getMessage()]);
+// }
+
+ return $oFlow->done();
+ }
+
+ public function getMsgFormat()
+ {
+ $this->msgFormat();
+
+ return $this->sMsgFormat;
+ }
+
+ public function msgFormat()
+ {
+ if (!in_array($this->sMsgFormatType, self::ALLOWED_FORMATS, true)) {
+ throw new \RuntimeException("不支持的格式类型: {$this->sMsgFormatType}");
+ }
+
+ $method = 'msgFormat_' . $this->sMsgFormatType;
+ $this->sMsgFormat = $this->$method();
+
+ if ($this->sPhotoUrl) {
+
+ $oManager = new ImageManager(new Driver());
+
+ $sTempPath = $_ENV['dir_base'].'public/upload';
+ $sTempPath .= "/tg_output.png";
+
+ $sImg = file_get_contents($this->sPhotoUrl);
+ $oImage = $oManager->read($sImg);
+
+ if ($this->iPhotoWidth && $this->iPhotoHeight) {
+ $oImage = $oImage->cover($this->iPhotoWidth, $this->iPhotoHeight);
+ }
+
+ $oImage->toJpg(80)->save($sTempPath);
+
+ $photo = InputFile::create($sTempPath, date("Y-m-d").'.jpg');
+
+ $this->sMsgFormat["photo"] = $photo;
+
+ $this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
+ unset($this->sMsgFormat["text"]);
+
+ } else if ($this->sFileUrl) {
+
+ if (!$this->sFileName) {
+ $sFileName = date("Y-m-d H:i:s").'.txt';
+ } else {
+ $sFileName = $this->sFileName;
+ }
+
+ $sFile = InputFile::create($this->sFileUrl, $sFileName);
+
+ $this->sMsgFormat["document"] = $sFile;
+ $this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
+ unset($this->sMsgFormat["text"]);
+
+ }
+
+ return $this;
+ }
+
+ private function msgFormat_none()
+ {
+ return [
+ 'chat_id' => $this->iChatId,
+ 'text' => $this->sMsg,
+ 'parse_mode' => '',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ }
+
+ private function msgFormat_html()
+ {
+ return [
+ 'chat_id' => $this->iChatId,
+ 'text' => strip_tags($this->sMsg, [
+ 'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'span', 'tg-spoiler', 'a', 'tg-emoji',
+ 'code', 'pre',
+ ]),
+ 'parse_mode' => 'HTML',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ }
+
+ private function msgFormat_markdown()
+ {
+ return [
+ 'chat_id' => $this->iChatId,
+ 'text' => $this->sMsg,
+ 'parse_mode' => 'Markdown',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ }
+
+ private function msgFormat_markdownV2()
+ {
+ return [
+ 'chat_id' => $this->iChatId,
+ 'text' => $this->sMsg,
+ 'parse_mode' => 'MarkdownV2',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ }
+
+}
diff --git a/src/Services/TomTool/Telegram/Slave.php b/src/Services/TomTool/Telegram/Slave.php
new file mode 100755
index 000000000..75dc21b3c
--- /dev/null
+++ b/src/Services/TomTool/Telegram/Slave.php
@@ -0,0 +1,361 @@
+sBotCode = $sBotCode;
+ $oInstance->sChatCode = $sChatCode;
+ $oInstance->enableSend = filter_var($_ENV["tg_send_enable"] ?? true, FILTER_VALIDATE_BOOLEAN);
+ return $oInstance;
+ }
+
+ // new
+ public static function log(string $sChatCode = 'admin'): self
+ {
+ return self::start("log", $sChatCode);
+ }
+
+ // new
+ public static function warn(string $sChatCode = 'admin'): self
+ {
+ return self::start("warn", $sChatCode);
+ }
+
+ // new
+ public static function fail(string $sChatCode = 'admin'): self
+ {
+ return self::start("fail", $sChatCode);
+ }
+
+ // new
+ public static function notify(string $sChatCode = 'admin'): self
+ {
+ return self::start("fail", $sChatCode);
+ }
+
+
+ // opt
+ public function enableAsync(bool $b = true): self
+ {
+ $this->enableAsync = $b;
+ return $this;
+ }
+
+ // opt
+ public function enableDetail(bool $b = true): self
+ {
+ $this->enableDetail = $b;
+ return $this;
+ }
+
+ // opt
+ public function enableSlaveQueue(bool $b = true): self
+ {
+ $this->enableSlaveQueue = $b;
+ return $this;
+ }
+
+ // opt
+ public function enableMaster(bool $b = true): self
+ {
+ $this->enableMaster = $b;
+ return $this;
+ }
+
+ // opt
+ public function enableMasterQueue(bool $b = true): self
+ {
+ $this->enableMasterQueue = $b;
+ return $this;
+ }
+
+ // set
+ public function setPhotoUrl(string $sPhotoUrl, int $iWidth = 0, int $iHeight = 0): self
+ {
+ $this->sPhotoUrl = $sPhotoUrl;
+ $this->iPhotoWidth = $iWidth;
+ $this->iPhotoHeight = $iHeight;
+ return $this;
+ }
+
+ // set
+ public function setFileUrl(string $sFileUrl, string $sFileName = ''): self
+ {
+ $this->sFileUrl = $sFileUrl;
+ $this->sFileName = $sFileName;
+ return $this;
+ }
+
+ // set
+ public function setType(string $sType): self
+ {
+ $this->sType = $sType;
+ return $this;
+ }
+
+ // set
+ public function setMsgS(string $sMsg = ''): self
+ {
+ if ($this->enableDetail) {
+ $sMsg = "[ ".$this->getEnvSiteCode()." | ".date("d:H:i:s")." ] ".$sMsg;
+ }
+
+ $this->sMsg = $sMsg;
+
+ return $this;
+ }
+
+ // set
+ public function setMsgA(array $aMsg = []): self
+ {
+ $aMsgNew = $aMsg;
+
+ if ($this->enableDetail) {
+ $aMsgNew = [];
+ $aMsgNew["site_code"] = $this->getEnvSiteCode();
+ $aMsgNew["date"] = date("Y-m-d H:i:s");
+ $aMsgNew["data"] = $aMsg;
+ }
+
+ $this->sMsg = json_encode($aMsgNew, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+
+ return $this;
+ }
+
+ // set
+ public function setBotCode(string $sBotCode): self
+ {
+ $this->sBotCode = $sBotCode;
+ return $this;
+ }
+
+ // set
+ public function setChatCode(string $sChatCode): self
+ {
+ $this->sChatCode = $sChatCode;
+ return $this;
+ }
+
+ // set
+ public function setBotToken(string $sBotToken): self
+ {
+ $this->sBotToken = $sBotToken;
+ return $this;
+ }
+
+ // set
+ public function setChatId(int $iChatId): self
+ {
+ $this->iChatId = $iChatId;
+ return $this;
+ }
+
+ // action
+ public function send($xMsg = ''): Flow
+ {
+ if ($this->enableSend == false) {
+ $oFlow = Flow::start();
+ return $oFlow->done("不发送");
+ }
+
+ if ($xMsg) {
+ if (is_array($xMsg)) {
+ $this->setMsgA($xMsg);
+ } else {
+ $this->setMsgS($xMsg);
+ }
+ }
+
+ if (!$this->sMsg) {
+// throw new \Exception("未设置msg");
+ }
+
+ if ($this->enableMaster) {
+ $oFlow = $this->_send_master();
+ } else {
+ $oFlow = $this->_send_local();
+ }
+
+ return $oFlow;
+ }
+
+ // action
+ public function sendMaster($xMsg = ''): Flow
+ {
+ $this->enableMaster(true);
+ return $this->send($xMsg);
+ }
+
+ // action
+ public function sendLocal($xMsg = ''): Flow
+ {
+ $this->enableMaster(false);
+ return $this->send($xMsg);
+ }
+
+ private function _send_master(): Flow
+ {
+ $aData = $this->buildDataTom();
+
+ if ($this->enableSlaveQueue) {
+ $oFlow = ServiceHttpQueue::start("telegram_master")->arg($aData)->save();
+ } else {
+
+ $oFlow = Flow::start("_send_master");
+ $sUrlMaSterBase = $_ENV["url_master_base"] ?? "";
+
+ if (!$sUrlMaSterBase) {
+ throw new \Exception("未设置env的url_master_base");
+ }
+
+ $sUrlMasterPost = "https://".$sUrlMaSterBase."/api/telegram/post";
+
+ try {
+
+ $oHttpV2 = HttpV2::make($sUrlMasterPost);
+
+ if ($this->enableAsync) {
+ $oHttpV2 = $oHttpV2->enableAsync();
+ }
+
+ $r = $oHttpV2->enableJsonAs()->setData($aData)->send();
+
+ if ($this->enableAsync) {
+ return $oFlow->sData("async")->done("async");
+ } else {
+ $a = json_decode($r, true);
+ $isDone = $a["isDone"] ?? false;
+
+ if ($isDone) {
+ return $oFlow->done();
+ } else {
+ return $oFlow->fail($r);
+ }
+
+ }
+
+ } catch (\Throwable $e) {
+ $oFlow = $oFlow->fail($e->getMessage());
+ }
+ }
+
+ return $oFlow;
+ }
+
+ // 直接从本地发到tg,几乎用不上,都走master了
+ private function _send_local(): Flow
+ {
+ if ($this->enableSlaveQueue) {
+ $aData = $this->buildDataTom();
+ $oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
+ } else {
+ $oFlow = ServiceTelegramSdk::start($this->sBotToken, $this->iChatId)->setMsgS($this->sMsg)->send();
+ }
+
+ return $oFlow;
+ }
+
+ private function buildDataTom(): array
+ {
+ return [
+ "sBotCode" => $this->sBotCode ?? '',
+ "sChatCode" => $this->sChatCode ?? '',
+ "sBotToken" => $this->sBotToken ?? '',
+ "iChatId" => $this->iChatId ?? '',
+ "enableMasterQueue" => $this->enableMasterQueue ?? '',
+ "enableMaster" => $this->enableMaster,
+ "sMsg" => $this->sMsg ?? '',
+ "sPhotoUrl" => $this->sPhotoUrl ?? '',
+ "iPhotoWidth" => $this->iPhotoWidth ?? 0,
+ "iPhotoHeight" => $this->iPhotoHeight ?? 0,
+ "sType" => $this->sType ?? '',
+ "sFileUrl" => $this->sFileUrl ?? '',
+ "sFileName" => $this->sFileName ?? '',
+ ];
+ }
+
+ private function getEnvSiteCode(): string
+ {
+ $sEnvSiteCode = $_ENV["site_code"] ?? "未设置env的site_code";
+
+ return $sEnvSiteCode;
+ }
+
+}
diff --git a/src/Services/TomTool/TelegramV2.php b/src/Services/TomTool/TelegramV2.php
new file mode 100755
index 000000000..35bdbe2f6
--- /dev/null
+++ b/src/Services/TomTool/TelegramV2.php
@@ -0,0 +1,121 @@
+setToken($sToken);
+
+ $this->setTo($sTo);
+
+// $this->bot = new Api($this->sToken);
+ }
+
+ public function setMsgType($sMsgType)
+ {
+ $this->sMsgType = $sMsgType;
+ }
+
+ public function setTo($xTo, $sType = "code")
+ {
+ $iChatId = $xTo;
+
+ if ($sType == "code") {
+// $sEnvKey = "tgChat_".$xTo;
+ $iChatId = $_ENV['tgChat'][$iChatId];
+ }
+
+ $this->iTo = $iChatId;
+ }
+
+ public function setToken($xToken, $sType = "code")
+ {
+ $sToken = $xToken;
+
+ if ($sType == "code") {
+// $sEnvKey = "tgToken_".$xToken;
+ $sToken = $_ENV['tgToken'][$sToken];
+ }
+
+ $this->sToken = $sToken;
+
+ $this->bot = new Api($this->sToken);
+ }
+
+ public function send($sMsg, $sMsgType = 'str')
+ {
+ if ($sMsgType == "arr") {
+ $sMsg = json_encode($sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ }
+
+ if ($this->sMsgType === false) {
+ $sendMessage = [
+ 'chat_id' => $this->iTo,
+ 'text' => $sMsg,
+ 'parse_mode' => '',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ } else if ($this->sMsgType === "html") {
+ $sendMessage = [
+ 'chat_id' => $this->iTo,
+ 'text' => strip_tags(
+ $sMsg,
+ ['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike','del', 'span','tg-spoiler', 'a', 'tg-emoji',
+ 'code', 'pre',
+ ]
+ ),
+ 'parse_mode' => 'HTML',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ } else if ($this->sMsgType === "markdown") {
+ $sendMessage = [
+ 'chat_id' => $this->iTo,
+ 'text' => $sMsg,
+ 'parse_mode' => 'Markdown',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ } else if ($this->sMsgType === "markdownV2") {
+ $sendMessage = [
+ 'chat_id' => $this->iTo,
+ 'text' => $sMsg,
+ 'parse_mode' => 'MarkdownV2',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ } else {
+ $sendMessage = [
+ 'chat_id' => $this->iTo,
+ 'text' => $sMsg,
+ 'parse_mode' => '',
+ 'disable_web_page_preview' => false,
+ 'reply_to_message_id' => null,
+ 'reply_markup' => null,
+ ];
+ }
+
+ return $this->bot->sendMessage($sendMessage);
+ }
+
+}