no message
This commit is contained in:
parent
d677511bc9
commit
c0eddda516
File diff suppressed because one or more lines are too long
@ -49,14 +49,13 @@ final class Cron
|
||||
}
|
||||
|
||||
if ($h % 6 === 0 && $i === 3) {
|
||||
ShadowRocketCrawl::run();
|
||||
ShadowRocketPublish::run();
|
||||
// exit;
|
||||
|
||||
ShadowRocketCrawl::run(); // 爬取小火箭id
|
||||
ShadowRocketPublish::run(); // 小火箭id发布到site
|
||||
CronFreenodeCrawl::run(); // 1.爬免费节点
|
||||
CronFreenodePool::save(); // 2.格式化入库
|
||||
CronFreenodeMerge::save(); // 3.加工成文件
|
||||
CronFreenodeMerge::baseToSite(); // 4.文件分散为各站用
|
||||
CronFreenodeMerge::baseToSite("fn_a"); // 4.文件分散为各站用
|
||||
CronFreenodeMerge::baseToSite("fn_b");
|
||||
}
|
||||
|
||||
if ($h % 3 === 0 && $i === 3) {
|
||||
|
||||
@ -68,7 +68,12 @@ final class FnSitePublish
|
||||
$sSiteFnClientNick = $sSiteFnClient;
|
||||
if ($sSiteFnClient == "v2ray") {
|
||||
$sSiteFnClientNick = "v2ray\shadowrocket\小火箭";
|
||||
} else if ($sSiteFnClient == "singbox") {
|
||||
$sSiteFnClientNick = "singbox\hiddify";
|
||||
} else if ($sSiteFnClient == "clash") {
|
||||
$sSiteFnClientNick = "clash\mihomo";
|
||||
}
|
||||
|
||||
$sFeedLink = "https://".FreenodeHelperService::urlFeedToday($oSite->code_short, $sSiteFnClient);
|
||||
$str = "";
|
||||
$str .= "<b>".$sSiteFnClientNick."订阅链接:</b>\n\n";
|
||||
|
||||
@ -8,6 +8,8 @@ use App\Models\CrawlFreenode as ModelCrawlFreenode;
|
||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||
//use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
|
||||
// 第一步,从哪爬,run
|
||||
final class FreenodeCrawl
|
||||
{
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ use App\Models\SiteMap as ModelSiteMap;
|
||||
use App\Models\Option as ModelOption;
|
||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||
|
||||
// 其实是清理文件
|
||||
final class FreenodeFile
|
||||
{
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ use App\Models\Option as ModelOption;
|
||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
// 应该是最终步骤,整合处理为对应站点的
|
||||
final class FreenodeMerge
|
||||
{
|
||||
private static $aCountryOrder = [
|
||||
@ -148,6 +149,11 @@ final class FreenodeMerge
|
||||
$aNode["config"]["name"] = $sFlagCountry;
|
||||
$aNodeMerge[] = $aNode["config"];
|
||||
$aNodeCountryList[] = $sFlagCountry;
|
||||
} else if ($sPoolClient == "singbox") {
|
||||
$sFlagCountry = self::countryJoinFlag($aNode["country"]);
|
||||
$aNode["config"]["name"] = $sFlagCountry;
|
||||
$aNodeMerge[] = $aNode["config"];
|
||||
$aNodeCountryList[] = $sFlagCountry;
|
||||
} else {
|
||||
$aNodeMerge[] = json_encode($aNode);
|
||||
}
|
||||
@ -203,10 +209,36 @@ final class FreenodeMerge
|
||||
$sMerge = Yaml::dump($aClashTemplate, 4, 2);
|
||||
|
||||
$sPattern = '/(?<=^|\s)(cipher|name|type):\s+((?!\'|")[^\s"\'\n]+)/m';
|
||||
$sMerge = preg_replace($sPattern, '$1: "$2"', $sMerge);
|
||||
$sMerge = preg_replace($sPattern, '$1: "$2"', $sMerge); // 什么作用
|
||||
|
||||
// tomd($sMerge, 1);
|
||||
} else if ($sPoolClient == "singbox") {
|
||||
|
||||
$sFile = $sDirBase."public/freenode/template/singbox.txt";
|
||||
|
||||
if (file_exists($sFile) && is_readable($sFile)) {
|
||||
$sSingboxTemplate = file_get_contents($sFile); // data
|
||||
} else {
|
||||
$sMsg = "singbox的template不存在,这不应该 \n";
|
||||
TeleSlave::warn()->send($sMsg);
|
||||
echo $sMsg;
|
||||
continue;
|
||||
}
|
||||
|
||||
$aSingboxConfig = json_decode($sSingboxTemplate, true);
|
||||
|
||||
foreach ($aSingboxConfig["outbounds"] as &$aOutbounds) {
|
||||
|
||||
if ($aOutbounds["type"] == "selector") {
|
||||
$aOutbounds["outbounds"] = $aNodeCountryList;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
array_push($aSingboxConfig["outbounds"], $aNodeMerge);
|
||||
|
||||
$sMerge = json_encode($aSingboxConfig);
|
||||
} else {
|
||||
$sMerge = "?";
|
||||
}
|
||||
|
||||
$sFileDir = $sDirBase."public/freenode/merge/base/".$sPoolClient;
|
||||
@ -224,7 +256,7 @@ final class FreenodeMerge
|
||||
|
||||
}
|
||||
|
||||
public static function baseToSite()
|
||||
public static function baseToSite($sSiteGroupCodeTom)
|
||||
{
|
||||
echo "\nFreenodeMerge::baseToSite - 开始 \n";
|
||||
|
||||
@ -234,7 +266,7 @@ final class FreenodeMerge
|
||||
|
||||
$sDirBase = $_ENV["dir_base"];
|
||||
|
||||
$sFnClientList = ModelOption::_hit("freenode_client-fn_a");
|
||||
$sFnClientList = ModelOption::_hit("freenode_client-".$sSiteGroupCodeTom);
|
||||
|
||||
if (!$sFnClientList) {
|
||||
throw new \Exception("未设置option的freenode_client?");
|
||||
@ -253,7 +285,7 @@ final class FreenodeMerge
|
||||
}
|
||||
}
|
||||
|
||||
$oSiteMap = ModelSiteMap::whereIn("group_code", ["fn_a", "fn_b"])->get();
|
||||
$oSiteMap = ModelSiteMap::whereIn("group_code", [$sSiteGroupCodeTom])->get();
|
||||
|
||||
if ($oSiteMap->isEmpty()) {
|
||||
throw new \Exception("没找到siteMap的fn_a组?");
|
||||
|
||||
@ -8,7 +8,9 @@ use App\Models\CrawlFreenode as ModelCrawlFreenode;
|
||||
use App\Models\FreenodePool as ModelFreenodePool;
|
||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use App\Services\ServiceFnPoolV2rayTo;
|
||||
|
||||
// 应该是第二步:入库先
|
||||
final class FreenodePool
|
||||
{
|
||||
|
||||
@ -100,6 +102,12 @@ final class FreenodePool
|
||||
}
|
||||
|
||||
$aMerge[$sFromFileRealClient][$sConfigMd5] = $aNodeFormat;
|
||||
|
||||
$aNodeFormat_singbox = (new ServiceFnPoolV2rayTo())->singbox_row($aNodeFormat);
|
||||
if (!empty($aNodeFormat_singbox)) {
|
||||
$aMerge["singbox"][$sConfigMd5] = $aNodeFormat_singbox;
|
||||
}
|
||||
|
||||
}
|
||||
} else if ($sFromFileRealClient == "clash") {
|
||||
|
||||
@ -137,6 +145,8 @@ final class FreenodePool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $aMerge["singbox"] = ServiceFnPool::SingboxByV2ray($aMerge["v2ray"]); // 免费节点都是必有v2ray,clash则未必,所以默认必有v2ray
|
||||
|
||||
$oPool->content = $aMerge;
|
||||
$oPool->save();
|
||||
|
||||
@ -9,6 +9,7 @@ use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||
use App\Services\TomTool\HttpV2;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
// 这是向站点推送,步骤之外
|
||||
final class FreenodeSync
|
||||
{
|
||||
static function run()
|
||||
|
||||
53
app/Services/Cron/ReportTraffic.php
Executable file
53
app/Services/Cron/ReportTraffic.php
Executable file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Cron;
|
||||
|
||||
//use App\Services\Cron;
|
||||
//use App\Models\HttpQueue as ModelHttpQueue;
|
||||
//use App\Services\TomTool\Telegram\Master as TelegramMaster;
|
||||
//use App\Services\TomTool\Telegram\Slave as TelegramSlave;
|
||||
//use App\Services\TomTool\HttpV2;
|
||||
//use Telegram\Bot\Api;
|
||||
//use Telegram\Bot\Exceptions\TelegramSDKException;
|
||||
//use Telegram\Bot\Exceptions\TelegramSDKException;
|
||||
//use Telegram\Bot\Exceptions\TelegramResponse;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
final class ReportTraffic
|
||||
{
|
||||
|
||||
public function all()
|
||||
{
|
||||
$this->umami();
|
||||
}
|
||||
|
||||
public function umami()
|
||||
{
|
||||
$r = $this->umami_checkToken();
|
||||
|
||||
tt($r);
|
||||
}
|
||||
|
||||
private function umami_checkToken()
|
||||
{
|
||||
$oResponse = Http::post('https://api.umami.is/api/auth/login', [
|
||||
'username' => "ad1473af-2b82-4e9b-a726-36bef6cc512b",
|
||||
'password' => "UOtaonihouzi123#",
|
||||
]);
|
||||
|
||||
// 工业级防御:非 2xx 响应直接抛出 Http 异常
|
||||
$oResponse->throw();
|
||||
|
||||
// 解析 JSON
|
||||
$aData = $oResponse->json();
|
||||
|
||||
// 检查 Key 是否符合预期(假设返回结构为 {"token": "xxx"})
|
||||
if (empty($aData['token'])) {
|
||||
throw new Exception('接口响应格式有误:缺失 token 字段');
|
||||
}
|
||||
|
||||
return $aData['token'];
|
||||
}
|
||||
}
|
||||
@ -27,12 +27,13 @@ class FreenodeHelperService
|
||||
return $sFeedUrl;
|
||||
}
|
||||
|
||||
// 应该是没用上的,按理应该有code和groupcode映射
|
||||
public static function getClients()
|
||||
{
|
||||
$aClients = [
|
||||
"clash",
|
||||
"v2ray",
|
||||
// "singbox",
|
||||
"singbox",
|
||||
];
|
||||
|
||||
return $aClients;
|
||||
|
||||
514
app/Services/ServiceFnPoolV2rayTo.php
Normal file
514
app/Services/ServiceFnPoolV2rayTo.php
Normal file
@ -0,0 +1,514 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class ServiceFnPoolV2rayTo
|
||||
{
|
||||
public function singbox($cPoolV2ray)
|
||||
{
|
||||
$i = 0;
|
||||
foreach ($cPoolV2ray as $aPoolV2ray) {
|
||||
$i++;
|
||||
// echo $i.":<br>";
|
||||
if ($i <= 0) {
|
||||
// continue;
|
||||
}
|
||||
|
||||
if ($i >= 100) {
|
||||
// continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$aPoolSingbox = $this->singbox_row($aPoolV2ray);
|
||||
if (!$aPoolSingbox) continue; //测试用
|
||||
t($aPoolV2ray);
|
||||
t($aPoolSingbox);
|
||||
|
||||
echo "=======================================================";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
public function singbox_row($aPoolV2ray)
|
||||
{
|
||||
$aNode = $aPoolV2ray;
|
||||
if (isset($aPoolV2ray['protocolType'])) {
|
||||
$aPoolV2ray['protocolType'] = $this->removeUtf8Bom($aPoolV2ray['protocolType']);
|
||||
}
|
||||
|
||||
$sProtocol = $aPoolV2ray['protocolType'] ?? '';
|
||||
|
||||
if (isset($aPoolV2ray["config"]) && !is_array($aPoolV2ray["config"])) {
|
||||
$aPoolV2ray["config"] = rawurldecode($aPoolV2ray["config"]);
|
||||
}
|
||||
|
||||
$sModTo = 'mod_to_'.$sProtocol;
|
||||
if (!method_exists($this, $sModTo)) {
|
||||
return false; // 没做对应处理的直接排除
|
||||
}
|
||||
|
||||
$aNode = $this->$sModTo($aPoolV2ray);
|
||||
|
||||
return $aNode;
|
||||
}
|
||||
|
||||
private function mod_to_socks($aV2ray)
|
||||
{
|
||||
$sConfig = $aV2ray["config"] ?? '';
|
||||
|
||||
$aTmp = explode("@", $sConfig, 2);
|
||||
$sPwd = $aTmp[0] ?? '';
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
|
||||
$sPwd = $this->tomBase64Decode($sPwd);
|
||||
$aTmp = explode(":", $sPwd, 2);
|
||||
$sUser = $aTmp[0] ?? '';
|
||||
$sPwd = $aTmp[1] ?? '';
|
||||
|
||||
$aTmp = explode(":", $sConfig, 2);
|
||||
$sAddr = $aTmp[0] ?? '';
|
||||
$iPort = (int) $aTmp[1] ?? '';
|
||||
|
||||
$aNode = [
|
||||
"type" => "socks",
|
||||
"tag" => $aV2ray['country'] ?? '',
|
||||
"server"=> $sAddr,
|
||||
"server_port" => $iPort,
|
||||
"username" => $sUser,
|
||||
"password" => $sPwd
|
||||
];
|
||||
|
||||
$aV2ray["config"] = $aNode;
|
||||
|
||||
return $aV2ray;
|
||||
}
|
||||
|
||||
private function mod_to_trojan($aV2ray)
|
||||
{
|
||||
$sConfig = $aV2ray["config"] ?? '';
|
||||
|
||||
$aTmp = explode("@", $sConfig, 2);
|
||||
$sPwd = $aTmp[0] ?? '';
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
|
||||
$aTmp = explode("?", $sConfig, 2);
|
||||
$sAddrFull = $aTmp[0] ?? '';
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
|
||||
$aTmp = explode(":", $sAddrFull, 2);
|
||||
$sAddr = $aTmp[0] ?? '';
|
||||
$iPort = (int) $aTmp[1] ?? '';
|
||||
|
||||
$aNode = [
|
||||
"type" => "trojan",
|
||||
"tag" => $aV2ray['country'] ?? '',
|
||||
"server"=> $sAddr,
|
||||
"server_port" => $iPort,
|
||||
"password" => $sPwd
|
||||
];
|
||||
|
||||
$aCustomYuan = explode("&", $sConfig);
|
||||
$aCustom = [];
|
||||
foreach ($aCustomYuan as $sRow) {
|
||||
$aRow = explode("=", $sRow, 2);
|
||||
$k = $aRow[0] ?? '';
|
||||
$v = $aRow[1] ?? '';
|
||||
$aCustom[$k] = $v;
|
||||
}
|
||||
|
||||
$aTls = []; // 层1的tls
|
||||
$aUtls = []; // 层2的utls
|
||||
|
||||
if (isset($aCustom["sni"])) { // 写层1的tls
|
||||
$aTls["server_name"] = $this->parseServerName($aCustom["sni"], $aCustom["sni"]);
|
||||
}
|
||||
if (isset($aCustom["host"])) { // 写层1的tls -- host覆盖sni=host优先
|
||||
$aTls["server_name"] = $this->parseServerName($aCustom["host"], $aCustom["host"]);
|
||||
}
|
||||
if (isset($aCustom["alpn"])) { // 写层1的tls
|
||||
$aTls["alpn"] = explode(",", $aCustom["alpn"]);
|
||||
}
|
||||
if (isset($aCustom["allowInsecure"])) { // 写层1的tls
|
||||
$aTls["insecure"] = (bool) $aCustom["allowInsecure"];
|
||||
}
|
||||
|
||||
if (isset($aCustom["fp"])) { // 写层2的utls
|
||||
$aUtls["fingerprint"] = $aCustom["fp"];
|
||||
}
|
||||
if (!empty($aUtls)) { // 层2的utls并入tls
|
||||
$aUtls["enabled"] = true;
|
||||
$aTls["utls"] = $aUtls;
|
||||
}
|
||||
|
||||
if (!empty($aTls)) { // 层1的tlstls并入node
|
||||
$aTls["enabled"] = true;
|
||||
$aNode["tls"] = $aTls;
|
||||
}
|
||||
|
||||
$aTransport = [];
|
||||
if (isset($aCustom["type"]) && $aCustom["type"] != "none" && $aCustom["type"] != "tcp" && $aCustom["type"] != "raw") {
|
||||
$aTransport["type"] = $aCustom["type"];
|
||||
if ($aCustom["type"] == "ws" && isset($aTls["server_name"])) {
|
||||
$aTransport["headers"]["Host"] = $aTls["server_name"];
|
||||
}
|
||||
}
|
||||
if (isset($aCustom["path"])) {
|
||||
$aTransport["path"] = rawurldecode($aCustom["path"]);
|
||||
}
|
||||
if (!empty($aTransport)) {
|
||||
$aNode["transport"] = $aTransport;
|
||||
}
|
||||
|
||||
$aV2ray["config"] = $aNode;
|
||||
|
||||
return $aV2ray;
|
||||
}
|
||||
|
||||
private function mod_to_hysteria2($aV2ray)
|
||||
{
|
||||
$sConfig = $aV2ray["config"] ?? '';
|
||||
|
||||
$aTmp = explode("@", $sConfig, 2);
|
||||
$sPwd = $aTmp[0] ?? '';
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
|
||||
$aTmp = explode("?", $sConfig, 2);
|
||||
$sAddrFull = $aTmp[0] ?? '';
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
$aTmp = explode(":", $sAddrFull, 2);
|
||||
$sAddr = $aTmp[0] ?? '';
|
||||
$iPort = (int) $aTmp[1] ?? '';
|
||||
|
||||
$cCustom = explode("&", $sConfig);
|
||||
$aCustom = [];
|
||||
foreach ($cCustom as $sCustom) {
|
||||
$aTmp = explode("=", $sCustom);
|
||||
$k = $aTmp[0] ?? '';
|
||||
$v = $aTmp[1] ?? '';
|
||||
$aCustom[$k] = $v;
|
||||
}
|
||||
|
||||
$aNode = [
|
||||
"type" => "hysteria2",
|
||||
"tag" => $aV2ray['country'] ?? '',
|
||||
"server"=> $sAddr,
|
||||
"server_port" => $iPort,
|
||||
"password" => $sPwd
|
||||
];
|
||||
|
||||
$aTls = [];
|
||||
if (isset($aCustom['sni'])) {
|
||||
$aTls['server_name'] = $this->parseServerName($aCustom['sni'], $aCustom['sni']);
|
||||
}
|
||||
if (isset($aCustom['insecure'])) {
|
||||
$aTls['insecure'] = (bool) $aCustom['insecure'] ?? 0;
|
||||
}
|
||||
if (!empty($aTls)) {
|
||||
$aTls['enabled'] = true;
|
||||
$aNode["tls"] = $aTls;
|
||||
}
|
||||
|
||||
$aObfs = [];
|
||||
if (isset($aCustom["obfs"])) {
|
||||
$aObfs["type"] = $aCustom["obfs"];
|
||||
}
|
||||
if (isset($aCustom["obfs-password"])) {
|
||||
$aObfs["password"] = $aCustom["obfs-password"];
|
||||
}
|
||||
if (!empty($aObfs)) {
|
||||
$aNode["obfs"] = $aObfs;
|
||||
}
|
||||
|
||||
$aV2ray["config"] = $aNode;
|
||||
|
||||
return $aV2ray;
|
||||
}
|
||||
|
||||
private function mod_to_ss($aV2ray)
|
||||
{
|
||||
$sConfig = $aV2ray["config"] ?? '';
|
||||
|
||||
$aTmp = explode("@", $sConfig, 2);
|
||||
$sPwd = $aTmp[0] ?? '';
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
|
||||
$sPwd = $this->tomBase64Decode($sPwd);
|
||||
|
||||
$aTmp = explode(":", $sPwd, 2);
|
||||
$sPwdType = $aTmp[0] ?? '';
|
||||
$sPwd = $aTmp[1] ?? '';
|
||||
|
||||
$aTmp = explode(":", $sConfig, 2);
|
||||
$sAddr = $aTmp[0] ?? '';
|
||||
$iPort = (int) $aTmp[1] ?? '';
|
||||
|
||||
// 推荐的现代化 SS 算法白名单
|
||||
$aAllowedCiphers = [
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
'2022-blake3-chacha20-poly1305',
|
||||
'aes-128-gcm',
|
||||
'aes-192-gcm',
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
];
|
||||
|
||||
if (!in_array($sPwdType, $aAllowedCiphers, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$aNode = [
|
||||
"type" => "shadowsocks",
|
||||
"tag" => $aV2ray['country'] ?? '',
|
||||
"server" => $sAddr,
|
||||
"server_port" => $iPort,
|
||||
"method" => $sPwdType,
|
||||
"password" => $sPwd,
|
||||
];
|
||||
|
||||
return $aNode;
|
||||
}
|
||||
|
||||
private function mod_to_vmess($aV2ray)
|
||||
{
|
||||
$aNode = [];
|
||||
$aConfig = $aV2ray["config"];
|
||||
|
||||
$aNode["type"] = "vmess";
|
||||
$aNode["tag"] = $aV2ray['country'] ?? '';
|
||||
$aNode["server"] = $aConfig["add"] ?? '';
|
||||
$aNode["server_port"] = (int) $aConfig["port"] ?? '';
|
||||
$aNode["uuid"] = $aConfig["id"] ?? '';
|
||||
$aNode["security"] = $aConfig["scy"] ?? 'auto';
|
||||
$aNode["alter_id"] = (int) $aConfig["aid"] ?? 0; // 猜的
|
||||
|
||||
$aTransport = [];
|
||||
if (isset($aConfig["net"]) && $aConfig["net"] != "tcp" && $aConfig["net"] != "none" && $aConfig["net"] != "raw") {
|
||||
$aTransport["type"] = $aConfig["net"];
|
||||
}
|
||||
if (isset($aConfig["path"])) {
|
||||
$aTransport["path"] = rawurldecode($aConfig["path"]);
|
||||
}
|
||||
if (isset($aConfig["host"])) {
|
||||
$aTransport["headers"]["Host"] = $aConfig["host"];
|
||||
}
|
||||
if (!empty($aTransport)) {
|
||||
$aNode["transport"] = $aTransport;
|
||||
}
|
||||
|
||||
$aTls = [];
|
||||
if (isset($aConfig["tls"])) {
|
||||
if ($aConfig["tls"] == "tls") {
|
||||
$aTls["server_name"] = !empty($aConfig['host']) ? $aConfig['host'] : $aConfig['add'];
|
||||
$aTls["server_name"] = $this->parseServerName($aTls["server_name"], $aTls["server_name"]);
|
||||
}
|
||||
}
|
||||
if (!empty($aTls)) {
|
||||
$aTls["enabled"] = true;
|
||||
$aNode["tls"] = $aTls;
|
||||
}
|
||||
|
||||
$aV2ray["config"] = $aNode;
|
||||
|
||||
return $aV2ray;
|
||||
}
|
||||
|
||||
private function mod_to_vless($aV2ray)
|
||||
{
|
||||
$sConfig = $aV2ray['config'] ?? '';
|
||||
|
||||
$aTmp = explode("@", $sConfig, 2);
|
||||
$sPwd = $aTmp[0] ?? ''; // *
|
||||
$sConfig = $aTmp[1] ?? '';
|
||||
|
||||
$aTmp = explode("?", $sConfig, 2);
|
||||
$sAddrFull = $aTmp[0];
|
||||
$sConfig = $aTmp[1];
|
||||
|
||||
$aTmp = explode(":", $sAddrFull, 2);
|
||||
$sAddr = $aTmp[0] ?? ''; // *
|
||||
$sPort = $aTmp[1] ?? ''; // *
|
||||
|
||||
$aCustomTmp = explode("&", $sConfig);
|
||||
$aCustom = [];
|
||||
foreach ($aCustomTmp as $aCustomRow) {
|
||||
$aTmp = explode("=", $aCustomRow, 2);
|
||||
$k = $aTmp[0] ?? '';
|
||||
$v = $aTmp[1] ?? '';
|
||||
$aCustom[$k] = $v; // *
|
||||
}
|
||||
|
||||
$aNode = [
|
||||
'type' => 'vless',
|
||||
'tag' => $aV2ray['country'] ?? '',
|
||||
'server' => $sAddr,
|
||||
'server_port' => (int) $sPort,
|
||||
'uuid' => $sPwd,
|
||||
];
|
||||
if (isset($aCustom["packetEncoding"]) && $aCustom["packetEncoding"] != "none") {
|
||||
$aNode["packet_encoding"] = $aCustom["packetEncoding"];
|
||||
}
|
||||
if (isset($aCustom["flow"])) {
|
||||
$aNode["flow"] = $aCustom["flow"];
|
||||
}
|
||||
|
||||
|
||||
$aTransport = [];
|
||||
if (isset($aCustom["type"]) && $aCustom["type"] != "tcp" && $aCustom["type"] != "raw") {
|
||||
$aTransport["type"] = $aCustom["type"];
|
||||
}
|
||||
if (isset($aCustom["headerType"]) && $aCustom["headerType"] == "http") {
|
||||
$aTransport["type"] = "http"; // 整个http覆盖默认tcp
|
||||
}
|
||||
if (isset($aCustom["path"])) {
|
||||
$aTransport["path"] = rawurldecode($aCustom["path"]);
|
||||
}
|
||||
if (isset($aCustom["host"])) {
|
||||
$aTransport["headers"]["Host"] = $aCustom["host"];
|
||||
}
|
||||
if (isset($aCustom["serviceName"])) {
|
||||
$aTransport["service_name"] = $aCustom["serviceName"];
|
||||
}
|
||||
if (!empty($aTransport)) {
|
||||
$aNode["transport"] = $aTransport;
|
||||
}
|
||||
|
||||
|
||||
$aTls = []; // 本体
|
||||
$aUtls = []; // 二层
|
||||
$aReality = []; // 二层
|
||||
|
||||
if (isset($aCustom["fp"])) { // 二层的utls构建
|
||||
if ($aCustom["fp"] == "random") {
|
||||
$aCustom["fp"] = "chrome";
|
||||
}
|
||||
$aUtls["fingerprint"] = $aCustom["fp"];
|
||||
}
|
||||
if (!empty($aUtls)) { // 二层的utls封装进tls
|
||||
$aUtls["enabled"] = true;
|
||||
$aTls["utls"] = $aUtls;
|
||||
}
|
||||
if (isset($aCustom["security"])) {
|
||||
$sSecurity = $aCustom["security"];
|
||||
if ($sSecurity == "reality") { // 二层的reality构建
|
||||
if (isset($aCustom["pbk"])) {
|
||||
$aReality["public_key"] = $aCustom["pbk"];
|
||||
}
|
||||
if (isset($aCustom["sid"])) {
|
||||
$aReality["short_id"] = (string) $aCustom["sid"];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($aReality)) { // 二层的reality封建进tls
|
||||
$aReality["enabled"] = true;
|
||||
$aTls["reality"] = $aReality;
|
||||
}
|
||||
|
||||
if (isset($aCustom["alpn"])) {
|
||||
$sTmp = $aCustom["alpn"] ?? "";
|
||||
$aTmp = explode(",", $sTmp);
|
||||
$aTls["alpn"] = $aTmp;
|
||||
}
|
||||
if (isset($aCustom["sni"])) { // 本体的tls构建
|
||||
$aTls["server_name"] = $this->parseServerName($aCustom["sni"], $aCustom["sni"]);
|
||||
}
|
||||
if (isset($aCustom["encryption"]) && $aCustom["encryption"] != "none") { // 本体的tls构建
|
||||
$aTls["encryption"] = $aCustom["encryption"];
|
||||
}
|
||||
if (!empty($aTls)) { // 本体的tls封装进node
|
||||
$aTls["enabled"] = true;
|
||||
$aNode["tls"] = $aTls;
|
||||
}
|
||||
|
||||
$aV2ray["config"] = $aNode;
|
||||
|
||||
return $aV2ray;
|
||||
}
|
||||
|
||||
private function tomBase64Decode(mixed $xInput): string
|
||||
{
|
||||
// 1. 防御性类型检查
|
||||
if (!is_string($xInput) || $xInput === '') {
|
||||
return (string) $xInput;
|
||||
}
|
||||
|
||||
// 2. 开启严格模式解码
|
||||
$sDecoded = base64_decode($xInput, true);
|
||||
|
||||
if ($sDecoded === false) {
|
||||
return $xInput;
|
||||
}
|
||||
|
||||
// 3. 反向验证:确保它不是像 humanity 这种恰好能被解码的纯普通文本
|
||||
if (base64_encode($sDecoded) !== $xInput) {
|
||||
return $xInput;
|
||||
}
|
||||
|
||||
// 4. 核心:校验解码后的数据是否为合法 UTF-8 文本,且不包含不可打印控制字符(\x00-\x08, \x0B-\x0C, \x0E-\x1F)
|
||||
$bIsValidUtf8 = mb_check_encoding($sDecoded, 'UTF-8');
|
||||
$bHasControlChars = (bool) preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $sDecoded);
|
||||
|
||||
if ($bIsValidUtf8 && !$bHasControlChars) {
|
||||
return $sDecoded; // 解码出来是合法的正常文本(包含中文、英文、数字等)
|
||||
}
|
||||
|
||||
// 解码出来包含真正的二进制控制符(乱码),说明输入原本就是纯字符串,返回原值
|
||||
return rawurldecode($xInput);
|
||||
}
|
||||
|
||||
public function test_allXieyi($cPoolV2ray)
|
||||
{
|
||||
$aXieyi = [];
|
||||
foreach ($cPoolV2ray as $aV2ray) {
|
||||
$sXieyi = $aV2ray["protocolType"];
|
||||
$sXieyi = $this->removeUtf8Bom($sXieyi);
|
||||
|
||||
$iXieyi = $aXieyi[$sXieyi] ?? 0;
|
||||
$iXieyi++;
|
||||
|
||||
$aXieyi[$sXieyi] = $iXieyi;
|
||||
}
|
||||
|
||||
return $aXieyi;
|
||||
}
|
||||
|
||||
private function removeUtf8Bom(string $sInput): string
|
||||
{
|
||||
if (str_starts_with($sInput, "\xEF\xBB\xBF")) {
|
||||
return substr($sInput, 3);
|
||||
}
|
||||
|
||||
return $sInput;
|
||||
}
|
||||
|
||||
private function parseServerName(string $sSni, string $sFallbackServer): string
|
||||
{
|
||||
$sSni = trim($sSni);
|
||||
$sSni = rawurldecode($sSni);
|
||||
if (empty($sSni)) {
|
||||
return $sFallbackServer;
|
||||
}
|
||||
|
||||
// 针对包含 http:// 或 https:// 的情况进行解析
|
||||
if (preg_match('#^https?://#i', $sSni)) {
|
||||
$aUrl = parse_url($sSni);
|
||||
$sSni = $aUrl['host'] ?? '';
|
||||
}
|
||||
|
||||
// 剔除可能残余的路径与端口号 (例如 domain.com/path 或 domain.com:8080)
|
||||
if (str_contains($sSni, '/')) {
|
||||
$sSni = explode('/', $sSni)[0];
|
||||
}
|
||||
if (str_contains($sSni, ':')) {
|
||||
$sSni = explode(':', $sSni)[0];
|
||||
}
|
||||
|
||||
// 若提取后仍为空,回退到节点自身的 server 字段
|
||||
return !empty($sSni) ? $sSni : $sFallbackServer;
|
||||
}
|
||||
|
||||
}
|
||||
119
public/freenode/template/singbox.txt
Executable file
119
public/freenode/template/singbox.txt
Executable file
@ -0,0 +1,119 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "local"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
}
|
||||
],
|
||||
"final": "local",
|
||||
"strategy": "prefer_ipv6"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1\/30",
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"endpoint_independent_nat": true,
|
||||
"udp_timeout": 60,
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": true,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 7891
|
||||
}
|
||||
},
|
||||
"sniff": true
|
||||
},
|
||||
{
|
||||
"type": "mixed",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 7891,
|
||||
"sniff": true,
|
||||
"domain_strategy": "prefer_ipv6"
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "default",
|
||||
"outbounds": []
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Rule",
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"protocol": "stun",
|
||||
"outbound": "block"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-cn",
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"rule_set": [
|
||||
{
|
||||
"tag": "geoip-cn",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https:\/\/fastly.jsdelivr.net\/gh\/SagerNet\/sing-geoip@rule-set\/geoip-cn.srs",
|
||||
"download_detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "geosite-cn",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https:\/\/fastly.jsdelivr.net\/gh\/SagerNet\/sing-geosite@rule-set\/geosite-cn.srs",
|
||||
"download_detour": "direct"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"cache_id": "clashfreenode"
|
||||
},
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user