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) {
|
if ($h % 6 === 0 && $i === 3) {
|
||||||
ShadowRocketCrawl::run();
|
ShadowRocketCrawl::run(); // 爬取小火箭id
|
||||||
ShadowRocketPublish::run();
|
ShadowRocketPublish::run(); // 小火箭id发布到site
|
||||||
// exit;
|
|
||||||
|
|
||||||
CronFreenodeCrawl::run(); // 1.爬免费节点
|
CronFreenodeCrawl::run(); // 1.爬免费节点
|
||||||
CronFreenodePool::save(); // 2.格式化入库
|
CronFreenodePool::save(); // 2.格式化入库
|
||||||
CronFreenodeMerge::save(); // 3.加工成文件
|
CronFreenodeMerge::save(); // 3.加工成文件
|
||||||
CronFreenodeMerge::baseToSite(); // 4.文件分散为各站用
|
CronFreenodeMerge::baseToSite("fn_a"); // 4.文件分散为各站用
|
||||||
|
CronFreenodeMerge::baseToSite("fn_b");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($h % 3 === 0 && $i === 3) {
|
if ($h % 3 === 0 && $i === 3) {
|
||||||
|
|||||||
@ -68,7 +68,12 @@ final class FnSitePublish
|
|||||||
$sSiteFnClientNick = $sSiteFnClient;
|
$sSiteFnClientNick = $sSiteFnClient;
|
||||||
if ($sSiteFnClient == "v2ray") {
|
if ($sSiteFnClient == "v2ray") {
|
||||||
$sSiteFnClientNick = "v2ray\shadowrocket\小火箭";
|
$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);
|
$sFeedLink = "https://".FreenodeHelperService::urlFeedToday($oSite->code_short, $sSiteFnClient);
|
||||||
$str = "";
|
$str = "";
|
||||||
$str .= "<b>".$sSiteFnClientNick."订阅链接:</b>\n\n";
|
$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 App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||||
//use Symfony\Component\Yaml\Yaml;
|
//use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
|
||||||
|
// 第一步,从哪爬,run
|
||||||
final class FreenodeCrawl
|
final class FreenodeCrawl
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use App\Models\SiteMap as ModelSiteMap;
|
|||||||
use App\Models\Option as ModelOption;
|
use App\Models\Option as ModelOption;
|
||||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||||
|
|
||||||
|
// 其实是清理文件
|
||||||
final class FreenodeFile
|
final class FreenodeFile
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use App\Models\Option as ModelOption;
|
|||||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
// 应该是最终步骤,整合处理为对应站点的
|
||||||
final class FreenodeMerge
|
final class FreenodeMerge
|
||||||
{
|
{
|
||||||
private static $aCountryOrder = [
|
private static $aCountryOrder = [
|
||||||
@ -148,6 +149,11 @@ final class FreenodeMerge
|
|||||||
$aNode["config"]["name"] = $sFlagCountry;
|
$aNode["config"]["name"] = $sFlagCountry;
|
||||||
$aNodeMerge[] = $aNode["config"];
|
$aNodeMerge[] = $aNode["config"];
|
||||||
$aNodeCountryList[] = $sFlagCountry;
|
$aNodeCountryList[] = $sFlagCountry;
|
||||||
|
} else if ($sPoolClient == "singbox") {
|
||||||
|
$sFlagCountry = self::countryJoinFlag($aNode["country"]);
|
||||||
|
$aNode["config"]["name"] = $sFlagCountry;
|
||||||
|
$aNodeMerge[] = $aNode["config"];
|
||||||
|
$aNodeCountryList[] = $sFlagCountry;
|
||||||
} else {
|
} else {
|
||||||
$aNodeMerge[] = json_encode($aNode);
|
$aNodeMerge[] = json_encode($aNode);
|
||||||
}
|
}
|
||||||
@ -203,10 +209,36 @@ final class FreenodeMerge
|
|||||||
$sMerge = Yaml::dump($aClashTemplate, 4, 2);
|
$sMerge = Yaml::dump($aClashTemplate, 4, 2);
|
||||||
|
|
||||||
$sPattern = '/(?<=^|\s)(cipher|name|type):\s+((?!\'|")[^\s"\'\n]+)/m';
|
$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;
|
$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";
|
echo "\nFreenodeMerge::baseToSite - 开始 \n";
|
||||||
|
|
||||||
@ -234,7 +266,7 @@ final class FreenodeMerge
|
|||||||
|
|
||||||
$sDirBase = $_ENV["dir_base"];
|
$sDirBase = $_ENV["dir_base"];
|
||||||
|
|
||||||
$sFnClientList = ModelOption::_hit("freenode_client-fn_a");
|
$sFnClientList = ModelOption::_hit("freenode_client-".$sSiteGroupCodeTom);
|
||||||
|
|
||||||
if (!$sFnClientList) {
|
if (!$sFnClientList) {
|
||||||
throw new \Exception("未设置option的freenode_client?");
|
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()) {
|
if ($oSiteMap->isEmpty()) {
|
||||||
throw new \Exception("没找到siteMap的fn_a组?");
|
throw new \Exception("没找到siteMap的fn_a组?");
|
||||||
|
|||||||
@ -8,7 +8,9 @@ use App\Models\CrawlFreenode as ModelCrawlFreenode;
|
|||||||
use App\Models\FreenodePool as ModelFreenodePool;
|
use App\Models\FreenodePool as ModelFreenodePool;
|
||||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use App\Services\ServiceFnPoolV2rayTo;
|
||||||
|
|
||||||
|
// 应该是第二步:入库先
|
||||||
final class FreenodePool
|
final class FreenodePool
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -100,6 +102,12 @@ final class FreenodePool
|
|||||||
}
|
}
|
||||||
|
|
||||||
$aMerge[$sFromFileRealClient][$sConfigMd5] = $aNodeFormat;
|
$aMerge[$sFromFileRealClient][$sConfigMd5] = $aNodeFormat;
|
||||||
|
|
||||||
|
$aNodeFormat_singbox = (new ServiceFnPoolV2rayTo())->singbox_row($aNodeFormat);
|
||||||
|
if (!empty($aNodeFormat_singbox)) {
|
||||||
|
$aMerge["singbox"][$sConfigMd5] = $aNodeFormat_singbox;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if ($sFromFileRealClient == "clash") {
|
} else if ($sFromFileRealClient == "clash") {
|
||||||
|
|
||||||
@ -138,6 +146,8 @@ final class FreenodePool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $aMerge["singbox"] = ServiceFnPool::SingboxByV2ray($aMerge["v2ray"]); // 免费节点都是必有v2ray,clash则未必,所以默认必有v2ray
|
||||||
|
|
||||||
$oPool->content = $aMerge;
|
$oPool->content = $aMerge;
|
||||||
$oPool->save();
|
$oPool->save();
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
|||||||
use App\Services\TomTool\HttpV2;
|
use App\Services\TomTool\HttpV2;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
// 这是向站点推送,步骤之外
|
||||||
final class FreenodeSync
|
final class FreenodeSync
|
||||||
{
|
{
|
||||||
static function run()
|
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;
|
return $sFeedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应该是没用上的,按理应该有code和groupcode映射
|
||||||
public static function getClients()
|
public static function getClients()
|
||||||
{
|
{
|
||||||
$aClients = [
|
$aClients = [
|
||||||
"clash",
|
"clash",
|
||||||
"v2ray",
|
"v2ray",
|
||||||
// "singbox",
|
"singbox",
|
||||||
];
|
];
|
||||||
|
|
||||||
return $aClients;
|
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