This commit is contained in:
hellcat 2026-07-27 14:41:20 +08:00
parent 934c2f931b
commit 55198846ae
115 changed files with 4804 additions and 80 deletions

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Api\Nasa\V1\Base\ListController;
final class ArticleBaseController extends ListController
{
protected string $sModelPath = 'App\Models\ArticleSchema';
protected array $aFilterFields = [ // 允许where的字段不设视为全允许
// 's_name',
// 'i_status'
];
protected string $sPageName = "article";
protected array $aListFields = [ // list展示的字段和名字映射
'id' => 'id',
'code' => 'code',
'name' => 'name',
'schema' => 'schema',
'schema_pro'=> 'schema_pro',
'group_code'=> 'group_code',
'cmd' => 'cmd',
'cmd_log' => 'cmd_log',
'opt' => 'opt',
'enable_sync'=> 'enable_sync',
'status' => 'status',
'created_at'=> 'created_at',
'updated_at'=> 'updated_at'
];
protected array $aValueStyle = [ // 特定文字渲染样式
// '关闭' => "<span class='text-danger'>关闭</span>",
// '开启' => "<span class='text-success'>开启</span>"
];
protected array $aFieldTypes = [
'schema' => 'text',
'schema_pro' => 'text',
'cmd' => 'text',
'cmd_log' => 'text',
'opt' => 'text'
];
protected array $aOrderBy = [
[
"sField" => "id",
"sSort" => "desc",
]
];
protected array $aJsonFields = [ // json字段
// 'schema'
];
protected array $aDateFields = [ // date字段
// 'dtUpdatedAt'
];
protected array $aLenthFields = [ // 限制长度的字段
// "xx" => 100,
];
protected array $aSelectFields = [ // 作为默认select的字段
// 'status',
];
protected array $aValueFields = [ // 值映射
// 'status' => [
// '1' => '开启',
// '0' => '关闭',
// ]
];
protected array $aCcExtFields = [
// 'dtCreatedAt' => '创建时间'
];
// protected array $aLimit = [3, 15, 40, 100]; // list长度第一个为默认
}

View File

@ -240,6 +240,9 @@ abstract class ListController
$sField = request('field');
$sHit = $this->oModel->find($iId)?->{$sField};
if (is_array($sHit)) {
$sHit = json_encode($sHit, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
return response()->json([
'iCode' => 200,

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Api\Nasa\V1\Base\ListController;
final class BladeBaseController extends ListController
{
protected string $sModelPath = 'App\Models\Blade';
protected array $aFilterFields = [ // 允许where的字段不设视为全允许
// 's_name',
// 'i_status'
];
protected string $sPageName = "fn爬取设置";
protected array $aListFields = [ // list展示的字段和名字映射
'id' => 'id',
'code' => 'code',
'content' => 'content'
];
protected array $aValueStyle = [ // 特定文字渲染样式
'关闭' => "<span class='text-danger'>关闭</span>",
'开启' => "<span class='text-success'>开启</span>"
];
protected array $aFieldTypes = [
'content' => 'text',
];
protected array $aOrderBy = [
// [
// "sField" => "status",
// "sSort" => "desc",
// ],
[
"sField" => "id",
"sSort" => "desc",
]
];
protected array $aJsonFields = [ // json字段
];
protected array $aDateFields = [ // date字段
// 'dtUpdatedAt'
];
protected array $aLenthFields = [ // 限制长度的字段
// "xx" => 100,
];
protected array $aSelectFields = [ // 作为默认select的字段
// 'status',
];
protected array $aValueFields = [ // 值映射
'status' => [
'1' => '开启',
'0' => '关闭',
]
];
protected array $aCcExtFields = [
// 'dtCreatedAt' => '创建时间'
];
// protected array $aLimit = [3, 15, 40, 100]; // list长度第一个为默认
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Api\Nasa\V1\Base\ListController;
final class OptionBaseController extends ListController
{
protected string $sModelPath = 'App\Models\Option';
protected array $aFilterFields = [ // 允许where的字段不设视为全允许
// 's_name',
// 'i_status'
];
protected string $sPageName = "option";
protected array $aListFields = [ // list展示的字段和名字映射
'id' => 'id',
'k' => 'k',
'v' => 'v'
];
protected array $aValueStyle = [ // 特定文字渲染样式
// '关闭' => "<span class='text-danger'>关闭</span>",
// '开启' => "<span class='text-success'>开启</span>"
];
protected array $aFieldTypes = [
// 'schema' => 'text',
// 'schema_pro' => 'text',
// 'cmd' => 'text',
// 'cmd_log' => 'text',
// 'opt' => 'text'
];
protected array $aOrderBy = [
[
"sField" => "k",
"sSort" => "desc",
]
];
protected array $aJsonFields = [ // json字段
// 'schema'
];
protected array $aDateFields = [ // date字段
// 'dtUpdatedAt'
];
protected array $aLenthFields = [ // 限制长度的字段
// "xx" => 100,
];
protected array $aSelectFields = [ // 作为默认select的字段
// 'status',
];
protected array $aValueFields = [ // 值映射
// 'status' => [
// '1' => '开启',
// '0' => '关闭',
// ]
];
protected array $aCcExtFields = [
// 'dtCreatedAt' => '创建时间'
];
// protected array $aLimit = [3, 15, 40, 100]; // list长度第一个为默认
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Api\Nasa\V1\Base\ListController;
final class SitemapBaseController extends ListController
{
protected string $sModelPath = 'App\Models\SiteMap';
protected array $aFilterFields = [ // 允许where的字段不设视为全允许
// 's_name',
// 'i_status'
];
protected string $sPageName = "site_map";
protected array $aListFields = [ // list展示的字段和名字映射
'id' => 'id',
'code' => 'code',
'code_short'=> 'code_short',
'name' => 'name',
'url' => 'url',
'url_front' => 'url_front',
'group_code'=> 'group_code',
'memo' => 'memo',
'config' => 'config',
'api_path' => 'api_path',
'status' => 'status'
];
protected array $aValueStyle = [ // 特定文字渲染样式
// '关闭' => "<span class='text-danger'>关闭</span>",
// '开启' => "<span class='text-success'>开启</span>"
];
protected array $aFieldTypes = [
// 'schema' => 'text',
// 'schema_pro' => 'text',
// 'cmd' => 'text',
// 'cmd_log' => 'text',
// 'opt' => 'text'
'sFileName' => 'image'
];
protected array $aOrderBy = [
[
"sField" => "id",
"sSort" => "desc",
]
];
protected array $aJsonFields = [ // json字段
// 'schema'
];
protected array $aDateFields = [ // date字段
// 'dtUpdatedAt'
];
protected array $aLenthFields = [ // 限制长度的字段
// "xx" => 100,
];
protected array $aSelectFields = [ // 作为默认select的字段
// 'status',
];
protected array $aValueFields = [ // 值映射
// 'status' => [
// '1' => '开启',
// '0' => '关闭',
// ]
];
protected array $aCcExtFields = [
// 'dtCreatedAt' => '创建时间'
];
// protected array $aLimit = [3, 15, 40, 100]; // list长度第一个为默认
}

View File

@ -11,6 +11,7 @@ use App\Models\Hook as ModelHook;
use App\Services\TomTool\ThrowableHandler;
//use App\Services\TomTool\TelegramVdb;
use App\Services\TomTool\Telegram\Slave as TelegramSlave;
use App\Models\Option;
final class HookController
{
@ -45,6 +46,43 @@ final class HookController
}
}
public function cache_tg_new()
{
$sCacheTgHook = Option::where("k", "cache_tg::hook")->first()?->v; // 做了一半
if (!$sCacheTgHook) {
echo "$sCacheTgHook 未设置";
exit;
}
try {
$sUpdate = file_get_contents("php://input");
$aUpdate = json_decode($sUpdate, true);
$sText = $aUpdate["message"]["text"] ?? "没有text";
if (!str_starts_with($sText, '/')) {
$this->sCustomText = "/cacheTg#add__{$sCacheTgHook}"." ".$sUpdate;
}
$this->cmd();
} catch (\Throwable $e) {
$aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA([
"msg" => $aDataTg,
"data" => $sUpdate,
])->send();
$aDataCmd = $aDataTg;
$sDataCmd = json_encode($aDataCmd, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$sDataCmd = str_replace("\\n", "<br>", $sDataCmd);
echo $sDataCmd;
exit;
}
}
public function cmd_ffq_article()
{
try {

View File

@ -95,10 +95,10 @@ class CmdController
return "没有这个dove";
}
$sUrl = "https://".$sUrl;
if ($sUrl == "dd.loc/api/tg/hook") {
$sUrl = "http://".$sUrl;
} else {
$sUrl = "https://".$sUrl;
}
$aData = [

View File

@ -57,6 +57,9 @@ class ListController
$aResponse['sHeaderActionView'] = $this->sHeaderActionView;
$aResponse['sListView'] = $this->sListView;
$aResponse['cNasaBook'] = $this->getNasaBook();
$aResponse['aCustom'] = $this->aCustom ?? [];
// tt($aResponse);
// tt($this->aCustom);
return view($this->sView, $aResponse);
}
@ -91,7 +94,7 @@ class ListController
$sApiPath = Arr::pull($aParams, 'api_path');
$sSiteWww = Arr::pull($aParams, 'site_www');
$oResponse = Http::nasa($sSiteWww)->delete($sApiPath.'/delete/save/' . $iId);
$oResponse = Http::nasa($sSiteWww)->delete($sApiPath.'/delete/' . $iId);
return response()->json($oResponse->json(), 200);
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Requests\Nasa\NasaQueryRequest;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\Web\Nasa\V1\Base\ListController;
class MasterArticleBaseController extends ListController
{
protected string $sApiPath = "article/base";
protected string $sView = 'nasa._commons.list';
protected string $sSiteWww;
protected string $sHeaderActionView = 'nasa.v1._actions.list';
protected array $aBookCode = [
// "fn爬取说明书",
];
public function __construct()
{
$this->sSiteWww = config("path.url_master_base") ?? '';
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Requests\Nasa\NasaQueryRequest;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\Web\Nasa\V1\Base\ListController;
class MasterBladeBaseController extends ListController
{
protected string $sApiPath = "blade/base";
protected string $sView = 'nasa._commons.list';
protected string $sSiteWww;
protected string $sHeaderActionView = 'nasa.v1._actions.list';
protected array $aBookCode = [
// "fn爬取说明书",
];
public function __construct()
{
$this->sSiteWww = config("path.url_master_base") ?? '';
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Requests\Nasa\NasaQueryRequest;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\Web\Nasa\V1\Base\ListController;
class MasterOptionBaseController extends ListController
{
protected string $sApiPath = "option/base";
protected string $sView = 'nasa._commons.list';
protected string $sSiteWww;
protected string $sHeaderActionView = 'nasa.v1._actions.list';
protected array $aBookCode = [
// "fn爬取说明书",
];
public function __construct()
{
$this->sSiteWww = config("path.url_master_base") ?? '';
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Requests\Nasa\NasaQueryRequest;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\Web\Nasa\V1\Base\ListController;
class MasterSitemapBaseController extends ListController
{
protected string $sApiPath = "sitemap/base";
protected string $sView = 'nasa._commons.list';
protected string $sSiteWww;
protected string $sHeaderActionView = 'nasa.v1._actions.list';
protected array $aBookCode = [
// "fn爬取说明书",
];
public function __construct()
{
$this->sSiteWww = config("path.url_master_base") ?? '';
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Requests\Nasa\NasaQueryRequest;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\Web\Nasa\V1\Base\ListController;
class ResourceArticleBaseController extends ListController
{
protected string $sApiPath = "article/base";
protected string $sView = 'nasa._commons.list';
protected string $sSiteWww;
protected string $sHeaderActionView = 'nasa.v1._actions.list';
protected array $aBookCode = [
// "fn爬取说明书",
];
public function __construct()
{
$this->sSiteWww = config("path.url_resource_base") ?? '';
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Web\Nasa\V1;
use Illuminate\Http\Request;
use App\Http\Requests\Nasa\NasaQueryRequest;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\Web\Nasa\V1\Base\ListController;
//use Illuminate\Support\Facades\View;
class ResourceUploadimagesBaseController extends ListController
{
protected string $sApiPath = "uploadimages/base";
protected string $sView = 'nasa._commons.list';
protected string $sSiteWww;
// protected array $aCustom;
protected string $sHeaderActionView = 'nasa.v1._actions.list';
protected array $aBookCode = [
// "fn爬取说明书",
];
// protected array $aCustom = [
// "sImagePath" => config("path.url_resource_base").'/public/images'
// ];
public function __construct()
{
$this->sSiteWww = config("path.url_resource_base") ?? '';
$this->aCustom = [
"sImagePath" => "https://".config("path.url_resource_base").'/upload/images'
];
}
// public function hit(NasaQueryRequest $oRequest)
// {
// $iId = intval(request('id'));
// $sApiPath = $oRequest->input('api_path');
// $sSiteWww = $oRequest->input('site_www');
//
// $sField = request('field');
//
// $oResponse = Http::nasa($sSiteWww)->get($sApiPath.'/hit/' . $iId . '/' . $sField);
//
// return response()->json($oResponse->json(), 200);
// }
}

15
app/Models/ShadowRocketIds.php Executable file
View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ShadowRocketIds extends Model
{
protected $table = 'shadowRocketIds';
// public $timestamps = false;
protected $fillable = [
'arr',
];
}

View File

@ -8,5 +8,10 @@ class SiteMap extends Model
{
protected $table = 'site_map';
public $timestamps = false;
public static function byGroupCode($sGroupCode)
{
return self::where("group_code", $sGroupCode)->where("status", 1)->get();
}
}

View File

@ -213,6 +213,32 @@ final class Schema
$aSchemaPro[] = $aSchemaProRow; // 关键
$aDataSend[] = $aSchemaProRow; // 公约
} // is photo
} else if ($this->oArticle->group_code == "tg_image") {
if ($aSchemaProRow["type"] == "photo") {
$iPhotoFullSizeKey = $aSchemaDataRow["photo_fullSizeKey"];
$aPhotoFullSize = $aSchemaDataRow["photo"][$iPhotoFullSizeKey];
$oImageManager = new ImageManager(new Driver());
$oImgMain = $oImageManager->read($aPhotoFullSize["file_path"]);
$sExtension = pathinfo($aPhotoFullSize["file_path"], PATHINFO_EXTENSION);
$sUploadPath = 'upload/';
$sUploadDir = config('system.dir_base') . "public/" . $sUploadPath;
$aSavedPath = []; // 弹药库:存放所有生成的路径
$sTmp = $this->oArticle->code . '-'.$iRowId.'-full.' . $sExtension;
$sFullSavePath = $sUploadDir . $sTmp;
$sFullSavePathUrl = $sUploadPath . $sTmp;
$oImgMain->save($sFullSavePath);
$aSavedPath['full'] = $sFullSavePathUrl;
$aSchemaProRow["data"] = $aSavedPath;
$aSchemaPro[] = $aSchemaProRow; // 关键
$aDataSend[] = $aSchemaProRow; // 公约
}
} // xiuren end
} // cache_tg end

View File

@ -3,6 +3,7 @@
declare(strict_types=1);
namespace App\Services;
use App\Models\FreenodePool;
final class CountrySpeedService
{
@ -58,11 +59,15 @@ final class CountrySpeedService
//// 速度
$iNodeSpeed = rand(config("freenode.speed_min"), config("freenode.speed_max"));
$iNodeCount = rand(config("freenode.count_min"), config("freenode.count_max"));
$oFnp = FreenodePool::whereDate('date', date("Y-m-d"))->first();
$aFnpContent = json_decode($oFnp->content, true);
$iNodeCount = count($aFnpContent["v2ray"]);
// $iNodeCount = rand(config("freenode.count_min"), config("freenode.count_max"));
$sContent = "本次更新共".$iNodeCount."个可用节点,最高速度".$iNodeSpeed."M/S。
覆盖".$sNodeZone."等多个区域。
复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。";
复制下方的v2ray/clash订阅链接在客户端添加即可正常使用。";
return $sContent;
}

View File

@ -18,6 +18,8 @@ use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Services\CacheTg as ServiceCacheTg;
use App\Services\Cron\FnSitePublish as CronFnSitePublish;
use App\Services\Cron\NasaTestFnSubfile;
use App\Services\Cron\ShadowRocketCrawl;
use App\Services\Cron\ShadowRocketPublish;
final class Cron
{
@ -47,6 +49,10 @@ final class Cron
}
if ($h % 6 === 0 && $i === 3) {
ShadowRocketCrawl::run();
ShadowRocketPublish::run();
// exit;
CronFreenodeCrawl::run(); // 1.爬免费节点
CronFreenodePool::save(); // 2.格式化入库
CronFreenodeMerge::save(); // 3.加工成文件

View File

@ -12,6 +12,7 @@ use Illuminate\Support\Carbon;
use App\Services\CountrySpeedService;
use App\Models\Ship;
use App\Services\FreenodeHelperService;
use App\Services\Ship\ServiceShipHtml;
final class FnSitePublish
{
@ -71,27 +72,14 @@ final class FnSitePublish
<span class='subCopy'><i class='iconfont icon-link'></i> ".$sFeedLink."</span></a>";
$aDylj[$sSiteFnClient] = $str;
}
// <a href="javascript:;" data-clipboard-text="https://cfn.loc/sub/20260204-v2ray.txt" class="copy-permalink">
// <span class="subCopy">
// <i class="iconfont icon-link"></i> https://cfn.loc/sub/20260204-v2ray.txt
// </span>
// </a>
$sDylj = implode("\n\n", $aDylj);
$sJctj = "";
foreach ($aShipCode as $sShipCode) {
$sJctj .= "<h2>机场推荐:</h2>\n\n";
$oShip = Ship::where("code", $sShipCode)->first();
$aShipGood = json_decode($oShip->good, true);
$sShipGood = implode("\n\n", $aShipGood);
$sJctj .= $sShipGood;
$sJctj .= "\n\n";
$sJctj .= "<b><a target='__blank' style='color:blue;' href='https://{$oShip->www}'>点击进入机场官网:{$oShip->name}</a>。</b>";
}
$sJctj = ServiceShipHtml::byShipCode($aSiteConfig['ship_code']);
$aFilter = [
'$sShipWww' => $oShip->www,
'$sShipName' => $oShip->name,
// '$sShipWww' => $oShip->www,
// '$sShipName' => $oShip->name,
'$sDateCN' => $sDateCN,
'$sYear' => $sYear,
'$sTitleSub' => $sCountrySpeed,
@ -99,6 +87,8 @@ final class FnSitePublish
'$sCountrySpeed'=> $sCountrySpeed,
'$sJctj' => $sJctj,
'$sDylj' => $sDylj,
'$sGithubLink' => $aSiteConfig['github'],
'$sSiteUrl' => $oSite->url
];
$aResult["article"]["content"] = self::nl2p(str_replace(array_keys($aFilter), array_values($aFilter), $oArticleTemplate->content));

View File

@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\Pool;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Symfony\Component\DomCrawler\Crawler;
use App\Models\ShadowRocketIds;
use Illuminate\Support\Carbon;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
final class ShadowRocketCrawl
{
private const ARRAY_TARGET_URLS = [
'https://proxygo.org/freeappleid/',
'https://proxygo.org/1-freeappleid/',
];
/**
* 执行多页面并发账号爬取任务并合并去重
*
* @return array
*/
public static function run()
{
echo "/n/n ShadowRocketCrawl::run";
try {
$aResponses = Http::pool(fn (Pool $oPool) => array_map(
fn (string $sUrl) => $oPool->as($sUrl)
->withHeaders([
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8',
])
->withOptions([
'curl' => [
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
],
])
->connectTimeout(10)
->timeout(30)
->retry(3, 1000)
->get($sUrl),
self::ARRAY_TARGET_URLS
));
} catch (\Throwable $oException) {
TeleSlave::warn("ShadowRocketCrawl:并发请求执行异常:" . $oException->getMessage());
return [];
}
$aAllAccountList = [];
foreach (self::ARRAY_TARGET_URLS as $sUrl) {
$oResponse = $aResponses[$sUrl] ?? null;
if ($oResponse instanceof \Throwable) {
TeleSlave::warn("ShadowRocketCrawl:请求网络异常:" . $sUrl . ",错误: " . $oResponse->getMessage());
continue;
}
if (!$oResponse || $oResponse->failed()) {
$iStatusCode = $oResponse ? $oResponse->status() : 0;
TeleSlave::warn("ShadowRocketCrawl:请求页面失败:" . $sUrl . ",状态码: " . $iStatusCode);
continue;
}
$sPageContent = $oResponse->body();
if (empty(trim($sPageContent))) {
TeleSlave::warn("ShadowRocketCrawl:请求页面内容为空:" . $sUrl);
continue;
}
$aParsedAccounts = self::parseHtmlToAccounts($sPageContent);
$aAllAccountList = array_merge($aAllAccountList, $aParsedAccounts);
}
$arr = self::deduplicateAccounts($aAllAccountList);
$json = json_encode($arr);
$sTodayDate = date('Y-m-d');
$oShadowRocketRecord = ShadowRocketIds::whereDate('created_at', $sTodayDate)->first()
?? new ShadowRocketIds();
$oShadowRocketRecord->arr = $json;
$oShadowRocketRecord->save();
echo "\n ok";
}
/**
* 按邮箱对爬取的账号进行去重
*
* @param array $aAccountList
* @return array
*/
private static function deduplicateAccounts(array $aAccountList): array
{
$aUniqueAccounts = [];
foreach ($aAccountList as $aAccount) {
$sEmail = $aAccount['sEmail'] ?? '';
if (!empty($sEmail) && !isset($aUniqueAccounts[$sEmail])) {
$aUniqueAccounts[$sEmail] = $aAccount;
}
}
return array_values($aUniqueAccounts);
}
/**
* 解析 HTML 并提取账号列表(包含地区与检测时间)
*
* @param string $sHtmlContent
* @return array
*/
private static function parseHtmlToAccounts(string $sHtmlContent): array
{
$aAccountList = [];
$oCrawler = new Crawler($sHtmlContent);
$oCrawler->filter('.posts-item')->each(function (Crawler $oNode) use (&$aAccountList) {
$sEmail = self::extractEmailFromNode($oNode);
$sPassword = self::extractPasswordFromNode($oNode);
$sRegion = self::extractRegionFromNode($oNode);
$sCheckTime = self::extractCheckTimeFromNode($oNode);
if (!empty($sEmail) && !empty($sPassword)) {
$aAccountList[] = [
'sEmail' => $sEmail,
'sPassword' => $sPassword,
'sRegion' => $sRegion,
'sCheckTime' => $sCheckTime,
];
}
});
return $aAccountList;
}
/**
* 从单个卡片节点中精准提取完整 Email
*
* @param Crawler $oNode
* @return string
*/
private static function extractEmailFromNode(Crawler $oNode): string
{
$oFullLinkNode = $oNode->filter('.item-tags a.external');
if ($oFullLinkNode->count() > 0) {
$sHref = $oFullLinkNode->attr('href') ?? '';
if (str_contains($sHref, '#')) {
$aParts = explode('#', $sHref);
$sDecoded = self::decodeCloudflareEmail($aParts[1] ?? '');
if (self::isValidEmail($sDecoded)) {
return $sDecoded;
}
}
}
$oTagEmailNode = $oNode->filter('.item-tags [data-cfemail]');
if ($oTagEmailNode->count() > 0) {
$sHex = $oTagEmailNode->attr('data-cfemail') ?? '';
$sDecoded = self::decodeCloudflareEmail($sHex);
if (self::isValidEmail($sDecoded)) {
return $sDecoded;
}
}
$oAllCfNodes = $oNode->filter('[data-cfemail]');
foreach ($oAllCfNodes as $oDomElement) {
$sHex = $oDomElement->getAttribute('data-cfemail');
$sDecoded = self::decodeCloudflareEmail($sHex);
if (self::isValidEmail($sDecoded)) {
return $sDecoded;
}
}
return '';
}
/**
* 从单个卡片节点中提取密码
*
* @param Crawler $oNode
* @return string
*/
private static function extractPasswordFromNode(Crawler $oNode): string
{
$oBtnNodes = $oNode->filter('button[copy]');
foreach ($oBtnNodes as $oDomElement) {
$sCopyVal = trim($oDomElement->getAttribute('copy'));
if (!empty($sCopyVal)) {
return $sCopyVal;
}
}
return '';
}
/**
* 从单个卡片节点中提取账号地区(如:美区、日本、中国大陆)
*
* @param Crawler $oNode
* @return string
*/
private static function extractRegionFromNode(Crawler $oNode): string
{
$oSpans = $oNode->filter('h5 span');
if ($oSpans->count() >= 2) {
$sRawText = trim($oSpans->eq(1)->text(''));
return trim(str_replace(['【', '】'], '', $sRawText));
}
return '';
}
/**
* 从单个卡片节点中提取检测时间
*
* @param Crawler $oNode
* @return string
*/
private static function extractCheckTimeFromNode(Crawler $oNode): string
{
$oTimeNode = $oNode->filter('.text-muted');
if ($oTimeNode->count() > 0) {
$sRawText = trim($oTimeNode->text(''));
return trim(str_replace('检测时间:', '', $sRawText));
}
return '';
}
/**
* 解密 Cloudflare XOR 加密邮箱
*
* @param string $sEncodedHex
* @return string
*/
private static function decodeCloudflareEmail(string $sEncodedHex): string
{
if (empty($sEncodedHex) || strlen($sEncodedHex) < 4 || !ctype_xdigit($sEncodedHex)) {
return '';
}
$iKey = hexdec(substr($sEncodedHex, 0, 2));
$sDecodedEmail = '';
$iLength = strlen($sEncodedHex);
for ($iIndex = 2; $iIndex < $iLength; $iIndex += 2) {
$sDecodedEmail .= chr(hexdec(substr($sEncodedHex, $iIndex, 2)) ^ $iKey);
}
return trim($sDecodedEmail);
}
/**
* 校验邮箱合法性
*
* @param string $sEmail
* @return bool
*/
private static function isValidEmail(string $sEmail): bool
{
return !empty($sEmail) && filter_var($sEmail, FILTER_VALIDATE_EMAIL) !== false;
}
}

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\SiteMap;
use App\Models\Blade;
use App\Models\ShadowRocketIds;
use App\Services\TomTool\HttpV2;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Services\Ship\ServiceShipHtml;
final class ShadowRocketPublish
{
public static function run()
{
$cSiteFnb = SiteMap::byGroupCode("fn_b");
$sBlade = Blade::where("code", "shadowRocketIds_a")->first()?->content;
$sShadowRocketIds = ShadowRocketIds::whereDate('created_at', date("Y-m-d"))->first()?->arr;
$aShadowRocketIds = json_decode($sShadowRocketIds, true);
$iShadowRocketIdsCount = count($aShadowRocketIds);
foreach ($cSiteFnb as $oSiteFnb) {
$sArticle = $sBlade;
$aSiteConfig = json_decode($oSiteFnb->config, true);
$sShip = ServiceShipHtml::byShipCode($aSiteConfig['ship_code']);
$aShadowRocketIdsHtml = [];
foreach ($aShadowRocketIds as $a) {
$s = "";
$s .= "<p>";
$s .= '账号:<a href="javascript:;" data-clipboard-text="'.$a['sEmail'].'" class="copy-permalink"><span class="subCopy"><i class="iconfont icon-link"></i> '.$a['sEmail'].'</span></a>';
$s .= '密码:<a href="javascript:;" data-clipboard-text="'.$a['sPassword'].'" class="copy-permalink"><span class="subCopy"><i class="iconfont icon-link"></i> '.$a['sPassword'].'</span></a>';
$s .= "地区:".$a['sRegion'].",检测时间:".$a['sCheckTime'];
$s .= "</p>";
$aShadowRocketIdsHtml[] = $s;
}
$sShadowRocketIdsHtml = implode("\n\n", $aShadowRocketIdsHtml);
$aFilter = [
'{$sContent}' => $sShadowRocketIdsHtml,
'{$sShip}' => $sShip,
'{$sTgHref}' => $aSiteConfig['telegram'],
'{$sGithubHref}' => $aSiteConfig['github']
];
$sArticle = self::nl2p(str_replace(array_keys($aFilter), array_values($aFilter), $sArticle));
$aResult = [
"content" => $sArticle,
"title" => '「'.date("n月j日").'」'.date("Y").'年最新免费苹果账号小火箭账号、shadowrocket id',
"title_sub" => "本次更新共{$iShadowRocketIdsCount}个免费苹果账号可用于使用小火箭shadowrocket",
"tags" => ["ios", "shadowrocket", "小火箭", "小火箭账号", "shadowrocket id", "免费苹果账号"],
"7like" => 9
];
$aApiPath = json_decode($oSiteFnb->api_path, true);
$sArticleReceiveShadowrocketids = "https://".$oSiteFnb->url."/".$aApiPath["article_receive_shadowrocketids"];
$sFlow = HttpV2::make($sArticleReceiveShadowrocketids, "post")->setDataA($aResult)->send();
$aFlow = json_decode($sFlow, true);
if (!isset($aFlow["isDone"]) || !$aFlow["isDone"]) {
echo $sFlow;
TeleSlave::warn()->send('ShadowRocketPublish::fn_b()的发送文章失败');
continue;
}
echo "\n ok";
}
}
public static function nl2p($text) {
// 将文本按换行符拆分成数组
$paragraphs = explode("\n", $text);
// 将每一段用 <p> 标签包裹
$paragraphs = array_map(function($paragraph) {
return '<p>' . trim($paragraph) . '</p>';
}, $paragraphs);
// 使用 implode 拼接成一个字符串
return implode('', $paragraphs);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Services\Ship;
use App\Models\Ship;
final class ServiceShipHtml
{
public static function byShipCode($aShipCode)
{
$sJctj = "";
foreach ($aShipCode as $sShipCode) {
$oShip = Ship::where("code", $sShipCode)->first();
$sJctj .= "<h2>机场推荐 - {$oShip->name}</h2>\n\n";
$aShipGood = json_decode($oShip->good, true);
$sShipGood = implode("\n\n", $aShipGood);
$sJctj .= $sShipGood;
$sJctj .= "\n\n";
$sJctj .= "<b><a target='__blank' style='color:blue;' href='https://{$oShip->www}'>👉 点击进入机场官网:{$oShip->name}。</a></b>";
}
return $sJctj;
}
}

View File

@ -14,6 +14,8 @@
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9",
"league/commonmark": "^2.8",
"symfony/css-selector": "^7.4",
"symfony/dom-crawler": "^8.1",
"symfony/yaml": "^7.4",
"voku/anti-xss": "^4.1"
},

88
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6d33a9af7883a91f1b6c86abbd6a7d5e",
"content-hash": "9165966b6edbbe9f8c56722c2ead2f8b",
"packages": [
{
"name": "brick/math",
@ -3817,16 +3817,16 @@
},
{
"name": "symfony/css-selector",
"version": "v7.2.0",
"version": "v7.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
"reference": "b75663ed96cf4756e28e3105476f220f92886cc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4",
"reference": "b75663ed96cf4756e28e3105476f220f92886cc4",
"shasum": ""
},
"require": {
@ -3862,7 +3862,7 @@
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/css-selector/tree/v7.2.0"
"source": "https://github.com/symfony/css-selector/tree/v7.4.9"
},
"funding": [
{
@ -3873,12 +3873,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:21:43+00:00"
"time": "2026-04-18T13:18:21+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -3947,6 +3951,76 @@
],
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/dom-crawler",
"version": "v8.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "1dfadd25537c8fcb6752cce5775f24647d976bdc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1dfadd25537c8fcb6752cce5775f24647d976bdc",
"reference": "1dfadd25537c8fcb6752cce5775f24647d976bdc",
"shasum": ""
},
"require": {
"php": ">=8.4.1",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
"symfony/css-selector": "^7.4|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases DOM navigation for HTML and XML documents",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dom-crawler/tree/v8.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/error-handler",
"version": "v7.2.1",

View File

@ -4,4 +4,5 @@ return [
"url_nodehub_base" => env("url_nodehub_base"),
"url_master_base" => env("url_master_base"),
"url_ship_fu_base" => env("url_ship_fu_base"),
"url_resource_base" => env("url_resource_base"),
];

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@
"isEntry": true
},
"resources/js/app.js": {
"file": "assets/app-psC-g_pG.js",
"file": "assets/app-BD91q7Y3.js",
"name": "app",
"src": "resources/js/app.js",
"isEntry": true

View File

@ -124,6 +124,48 @@ $(document).on('click', '.js-tom-modal-open-json', function(oEvent) {
});
$(document).on('click', '.js-tom-modal-open-image', function(oEvent) {
let _this = $('.tom-modal[tom-modal-name="image"]');
let iId = $(this).attr('tom-modal-save-id');
let sField = $(this).attr('tom-modal-field');
_this.attr('tom-modal-data-id', iId);
_this.find('.js-tom-modal-save').attr('tom-modal-save-id', iId);
// _this.find('textarea').attr('field', sField);
// _this.find('input').attr('field', sField);
// let sApiPath = $("#api_path").val();
//
// let sApiUrl = "/nasa/list/hit/"+iId+"/"+sField;
// alert($(".list-image-"+iId).attr('src')); // 获取<img>的src不对吗
_this.find('img').attr("src", $(".list-image-"+iId).attr('src'));
tomModalShow("image");
// $.ajax({
// url: sApiUrl,
// type: 'GET',
// dataType: 'json',
// data: {
// 'sHitType': 'json',
// 'api_path': sApiPath,
// 'site_www': $("#site_www").val()
// },
// success: function(j) {
// _this.find('img').attr("src", j.aData.sHit);
// tomModalShow("image");
// },
// error: function(oXHR, sTextStatus, sErrorThrown) {
// if (sTextStatus === 'timeout') {
// alert('📡 网络极度拥堵,请求超时,请稍后再试!');
// } else {
// alert('💀 系统核心暴裂,错误代码: ' + oXHR.status);
// }
// },
// complete: function() {}
// });
});
$(document).on('click', '.js-list-tool-clone-trigger', function(oEvent) {
let oThisModal = $(this).closest('.tom-modal');
let iId = oThisModal.attr('tom-modal-data-id');

View File

@ -5,6 +5,7 @@
</div>
<div class="tom-modal-body">
@foreach ($aData['cList'] as $k => $v)
@php if (is_array($v)) {$v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);} @endphp
<span class="text-to-right">{{$aData["aListField"][$k] ?? $k}}</span>
@if (isset($aData['aMap'][$k]))
<select class="js-tom-modal-save-input" field="{{$k}}">

View File

@ -199,6 +199,19 @@
</div>
</div>
<div class="tom-modal" tom-modal-name="image" tabindex="-1" tom-modal-lock="false" hidden>
<div class="tom-modal-inner">
<div class="tom-modal-header">
<span class="text-muted">image</span>
<a class="text-muted js-tom-modal-close"></a>
</div>
<div class="tom-modal-body-flex">
<img src="">
<input type="checkbox" class="js-tom-modal-save-checked" value="1" lock checked hidden>
</div>
</div>
</div>
<div class="tom-modal" tom-modal-name="text" tabindex="-1" tom-modal-lock="true" hidden>
<div class="tom-modal-inner">
<div class="tom-modal-header">
@ -206,7 +219,7 @@
<a class="text-muted js-tom-modal-close"></a>
</div>
<div class="tom-modal-body-flex">
<textarea class="js-tom-modal-save-input" style="min-width:500px; min-height:600px;"></textarea>
<textarea class="js-tom-modal-save-input" style="min-width:800px; min-height:600px;"></textarea>
<input type="checkbox" class="js-tom-modal-save-checked" value="1" lock checked hidden>
</div>
<div class="tom-modal-footer">

View File

@ -15,10 +15,11 @@
<tbody>
@php
function showValue($id, $k, $v, $aData, $sFieldNick = '')
function showValue($id, $k, $v, $aData, $sFieldNick = '', $aCustom)
{
// echo "<pre>";var_dump($aData["aMap"]);exit;
$sValueType = $aData["aFieldTypes"][$k] ?? '';
if (is_array($v)) {$v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);}
$sMapValue = $aData["aMap"][$k][$v] ?? $v;
$sShowValue = $aData["aValueStyle"][$sMapValue] ?? $sMapValue;
@ -47,6 +48,12 @@
"<span class='text-muted' style='width:100px; display:inline-block; vertical-align: middle; word-break: break-all; max-height: 20px;'>&nbsp;".$v."</span>
<textarea hidden class='js-copy-text'>".$v."</textarea>".
"</a>";
} else if ($sValueType == 'array') {
$sShowValue = "<a class='text-info js-tom-modal-open-json' tom-modal-field='".$k."' tom-modal-save-id='".$id."'>array <span class='text-muted'>(".strlen($v).")</span></a>";
} else if ($sValueType == 'image') {
$sShowValue = "<a class='text-info js-tom-modal-open-image' tom-modal-field='".$k."' tom-modal-save-id='".$id."'><span class='text-muted'>".$v."</span>";
$sShowValue .= "<img class='list-image-".$id."' style='max-height:300px;' src='".$aCustom['sImagePath'].'/'.$v."'></a>";
// $sShowValue .= "<input hidden class='image_path' value='".$aCustom['sImagePath']."'></a>";
}
return $sShowValue;
@ -60,12 +67,12 @@
@php $sShowValue = ''; @endphp
@foreach ($sFieldV as $kk => $vv)
@php
$sShowValue .= '<p>'.showValue($aRow['id'], $kk, $aRow[$kk], $aData, $vv).'</p>';
$sShowValue .= '<p>'.showValue($aRow['id'], $kk, $aRow[$kk], $aData, $vv, $aCustom).'</p>';
@endphp
@endforeach
@else
@php
$sShowValue = showValue($aRow['id'], $sField, $aRow[$sField], $aData, $sFieldV);
$sShowValue = showValue($aRow['id'], $sField, $aRow[$sField], $aData, $sFieldV, $aCustom);
@endphp
@endif

View File

@ -5,6 +5,10 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\Nasa\V1\SettingNavController as NasaV1SettingNavController;
use App\Http\Controllers\Api\Nasa\V1\TestsFnSubfileController as NasaV1TestsFnSubfileController;
use App\Http\Controllers\Api\Nasa\V1\FnCrawController as NasaV1FnCrawController;
use App\Http\Controllers\Api\Nasa\V1\BladeBaseController as NasaV1BladeBaseController;
use App\Http\Controllers\Api\Nasa\V1\ArticleBaseController as NasaV1ArticleBaseController;
use App\Http\Controllers\Api\Nasa\V1\OptionBaseController as NasaV1OptionBaseController;
use App\Http\Controllers\Api\Nasa\V1\SitemapBaseController as NasaV1SitemapBaseController;
Route::prefix('nasa/v1')->middleware('nasa.auth')->group(function () {
@ -43,5 +47,53 @@ Route::prefix('nasa/v1')->middleware('nasa.auth')->group(function () {
Route::put('clone/save', [NasaV1FnCrawController::class, 'cloneSave']);
Route::get('hit/{id}/{field}', [NasaV1FnCrawController::class, 'hit']);
});
Route::prefix('blade/base')->group(function() {
Route::get('', [NasaV1BladeBaseController::class, 'index']);
Route::get('detail/{id}', [NasaV1BladeBaseController::class, 'detail']);
Route::put('detail/save/{id}', [NasaV1BladeBaseController::class, 'detailSave']);
Route::delete('delete/{id}', [NasaV1BladeBaseController::class, 'delete']);
Route::get('add', [NasaV1BladeBaseController::class, 'add']);
Route::put('add/save', [NasaV1BladeBaseController::class, 'addSave']);
Route::get('clone/{id}', [NasaV1BladeBaseController::class, 'clone']);
Route::put('clone/save', [NasaV1BladeBaseController::class, 'cloneSave']);
Route::get('hit/{id}/{field}', [NasaV1BladeBaseController::class, 'hit']);
});
Route::prefix('article/base')->group(function() {
Route::get('', [NasaV1ArticleBaseController::class, 'index']);
Route::get('detail/{id}', [NasaV1ArticleBaseController::class, 'detail']);
Route::put('detail/save/{id}', [NasaV1ArticleBaseController::class, 'detailSave']);
Route::delete('delete/{id}', [NasaV1ArticleBaseController::class, 'delete']);
Route::get('add', [NasaV1ArticleBaseController::class, 'add']);
Route::put('add/save', [NasaV1ArticleBaseController::class, 'addSave']);
Route::get('clone/{id}', [NasaV1ArticleBaseController::class, 'clone']);
Route::put('clone/save', [NasaV1ArticleBaseController::class, 'cloneSave']);
Route::get('hit/{id}/{field}', [NasaV1ArticleBaseController::class, 'hit']);
});
Route::prefix('option/base')->group(function() {
Route::get('', [NasaV1OptionBaseController::class, 'index']);
Route::get('detail/{id}', [NasaV1OptionBaseController::class, 'detail']);
Route::put('detail/save/{id}', [NasaV1OptionBaseController::class, 'detailSave']);
Route::delete('delete/{id}', [NasaV1OptionBaseController::class, 'delete']);
Route::get('add', [NasaV1OptionBaseController::class, 'add']);
Route::put('add/save', [NasaV1OptionBaseController::class, 'addSave']);
Route::get('clone/{id}', [NasaV1OptionBaseController::class, 'clone']);
Route::put('clone/save', [NasaV1OptionBaseController::class, 'cloneSave']);
Route::get('hit/{id}/{field}', [NasaV1OptionBaseController::class, 'hit']);
});
Route::prefix('sitemap/base')->group(function() {
Route::get('', [NasaV1SitemapBaseController::class, 'index']);
Route::get('detail/{id}', [NasaV1SitemapBaseController::class, 'detail']);
Route::put('detail/save/{id}', [NasaV1SitemapBaseController::class, 'detailSave']);
Route::delete('delete/{id}', [NasaV1SitemapBaseController::class, 'delete']);
Route::get('add', [NasaV1SitemapBaseController::class, 'add']);
Route::put('add/save', [NasaV1SitemapBaseController::class, 'addSave']);
Route::get('clone/{id}', [NasaV1SitemapBaseController::class, 'clone']);
Route::put('clone/save', [NasaV1SitemapBaseController::class, 'cloneSave']);
Route::get('hit/{id}/{field}', [NasaV1SitemapBaseController::class, 'hit']);
});
});

View File

@ -36,6 +36,12 @@ use App\Http\Controllers\Web\Nasa\V1\FuMailMustlogController as NasaFuMailMustlo
use App\Http\Controllers\Web\Nasa\V1\TestsFnSubfileController as NasaTestsFnSubfileController;
use App\Http\Controllers\Web\Nasa\V1\FuAnnBaseController as NasaFuAnnBaseController;
use App\Http\Controllers\Web\Nasa\V1\BookController as NasaBookController;
use App\Http\Controllers\Web\Nasa\V1\MasterBladeBaseController as NasaMasterBladeBaseController;
use App\Http\Controllers\Web\Nasa\V1\MasterOptionBaseController as NasaMasterOptionBaseController;
use App\Http\Controllers\Web\Nasa\V1\MasterSitemapBaseController as NasaMasterSiteMapBaseController;
use App\Http\Controllers\Web\Nasa\V1\MasterArticleBaseController as NasaMasterArticleBaseController;
use App\Http\Controllers\Web\Nasa\V1\ResourceArticleBaseController as NasaResourceArticleBaseController;
use App\Http\Controllers\Web\Nasa\V1\ResourceUploadimagesBaseController as NasaResourceUploadimagesBaseController;
Route::get('/birds/create', [BirdController::class, 'create'])->name('birds.create'); // 录入界面
Route::get('/birds/{id}', [BirdController::class, 'show'])->name('birds.show'); // 详情展示界面
@ -71,6 +77,12 @@ Route::prefix('nasa')->middleware(['auth'])->group(function () {
Route::get('/master/panel/nav', [NasaMasterPanelNavController::class, 'index'])->name('nasa.master.panel.nav');
Route::get('/master/fn/craw', [NasaMasterFnCrawController::class, 'index'])->name('nasa.master.fn.craw');
Route::get('/master/blade/base', [NasaMasterBladeBaseController::class, 'index'])->name('nasa.master.blade.base');
Route::get('/master/article/base', [NasaMasterArticleBaseController::class, 'index'])->name('nasa.master.article.base');
Route::get('/master/option/base', [NasaMasterOptionBaseController::class, 'index'])->name('nasa.master.option.base');
Route::get('/master/sitemap/base', [NasaMasterSitemapBaseController::class, 'index'])->name('nasa.master.sitemap.base');
Route::get('/resource/article/base', [NasaResourceArticleBaseController::class, 'index'])->name('nasa.resource.article.base');
Route::get('/resource/uploadimages/base', [NasaResourceUploadimagesBaseController::class, 'index'])->name('nasa.resource.uploadimages.base');
Route::get('/fu/node/base', [NasaFuNodeBaseController::class, 'index'])->name('nasa.fu.node.base');
Route::get('/fu/node/action/fly', [NasaFuNodeActionController::class, 'fly']);
Route::get('/fu/env/config', [NasaFuEnvConfigController::class, 'index'])->name('nasa.fu.env.config');

View File

@ -10,8 +10,11 @@ return array(
'App\\Http\\Controllers\\Api\\FreenodeController' => $baseDir . '/app/Http/Controllers/Api/FreenodeController.php',
'App\\Http\\Controllers\\Api\\GithubController' => $baseDir . '/app/Http/Controllers/Api/GithubController.php',
'App\\Http\\Controllers\\Api\\GoodController' => $baseDir . '/app/Http/Controllers/Api/GoodController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\Controller' => $baseDir . '/app/Http/Controllers/Api/Nasa/V1/Controller.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\Base\\ListController' => $baseDir . '/app/Http/Controllers/Api/Nasa/V1/Base/ListController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\BladeBaseController' => $baseDir . '/app/Http/Controllers/Api/Nasa/V1/BladeBaseController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\FnCrawController' => $baseDir . '/app/Http/Controllers/Api/Nasa/V1/FnCrawController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\SettingNavController' => $baseDir . '/app/Http/Controllers/Api/Nasa/V1/SettingNavController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\TestsFnSubfileController' => $baseDir . '/app/Http/Controllers/Api/Nasa/V1/TestsFnSubfileController.php',
'App\\Http\\Controllers\\Api\\ShipController' => $baseDir . '/app/Http/Controllers/Api/ShipController.php',
'App\\Http\\Controllers\\Api\\Tg\\HookController' => $baseDir . '/app/Http/Controllers/Api/Tg/HookController.php',
'App\\Http\\Controllers\\Api\\v1\\FreenodeCountrySpeedController' => $baseDir . '/app/Http/Controllers/Api/v1/FreenodeCountrySpeedController.php',
@ -20,9 +23,21 @@ return array(
'App\\Http\\Controllers\\CronController' => $baseDir . '/app/Http/Controllers/CronController.php',
'App\\Http\\Controllers\\NimaController' => $baseDir . '/app/Http/Controllers/NimaController.php',
'App\\Http\\Controllers\\SakaiController' => $baseDir . '/app/Http/Controllers/SakaiController.php',
'App\\Http\\Controllers\\Web\\BirdController' => $baseDir . '/app/Http/Controllers/Web/BirdController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\AuthController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/AuthController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Base\\ListController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/Base/ListController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\BookController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/BookController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Demo\\NasaStyleController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/Demo/NasaStyleController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Setting\\MasterNavController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/Setting/MasterNavController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuAnnBaseController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/FuAnnBaseController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuEnvConfigController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/FuEnvConfigController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuMailMustlogController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/FuMailMustlogController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuNodeActionController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/FuNodeActionController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuNodeBaseController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/FuNodeBaseController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\MasterBladeBaseController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/MasterBladeBaseController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\MasterFnCrawController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/MasterFnCrawController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\MasterPanelNavController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/MasterPanelNavController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Stats\\DashboardTotalController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/Stats/DashboardTotalController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\TestsFnSubfileController' => $baseDir . '/app/Http/Controllers/Web/Nasa/V1/TestsFnSubfileController.php',
'App\\Http\\Middleware\\VerifyNasaToken' => $baseDir . '/app/Http/Middleware/VerifyNasaToken.php',
'App\\Http\\Requests\\Nasa\\NasaQueryRequest' => $baseDir . '/app/Http/Requests/Nasa/NasaQueryRequest.php',
'App\\Models\\ArticleCache' => $baseDir . '/app/Models/ArticleCache.php',
@ -32,6 +47,7 @@ return array(
'App\\Models\\ArticleSchema' => $baseDir . '/app/Models/ArticleSchema.php',
'App\\Models\\ArticleSiteMapGroup' => $baseDir . '/app/Models/ArticleSiteMapGroup.php',
'App\\Models\\ArticleTemplateFreenode' => $baseDir . '/app/Models/ArticleTemplateFreenode.php',
'App\\Models\\Bird' => $baseDir . '/app/Models/Bird.php',
'App\\Models\\Blade' => $baseDir . '/app/Models/Blade.php',
'App\\Models\\BladeJctj' => $baseDir . '/app/Models/BladeJctj.php',
'App\\Models\\BladeJctjOption' => $baseDir . '/app/Models/BladeJctjOption.php',
@ -51,6 +67,7 @@ return array(
'App\\Models\\Hook' => $baseDir . '/app/Models/Hook.php',
'App\\Models\\HttpQueue' => $baseDir . '/app/Models/HttpQueue.php',
'App\\Models\\Me' => $baseDir . '/app/Models/Me.php',
'App\\Models\\Nasa\\Book' => $baseDir . '/app/Models/Nasa/Book.php',
'App\\Models\\Nasa\\Navigation' => $baseDir . '/app/Models/Nasa/Navigation.php',
'App\\Models\\Note' => $baseDir . '/app/Models/Note.php',
'App\\Models\\NoteOption' => $baseDir . '/app/Models/NoteOption.php',
@ -59,6 +76,8 @@ return array(
'App\\Models\\SiteMap' => $baseDir . '/app/Models/SiteMap.php',
'App\\Models\\SitePathMap' => $baseDir . '/app/Models/SitePathMap.php',
'App\\Models\\TelegramKey' => $baseDir . '/app/Models/TelegramKey.php',
'App\\Models\\TestSubfile' => $baseDir . '/app/Models/TestSubfile.php',
'App\\Models\\Users' => $baseDir . '/app/Models/Users.php',
'App\\Providers\\AppServiceProvider' => $baseDir . '/app/Providers/AppServiceProvider.php',
'App\\Services\\Article\\CacheTg' => $baseDir . '/app/Services/Article/CacheTg.php',
'App\\Services\\Article\\Cmd' => $baseDir . '/app/Services/Article/Cmd.php',
@ -77,6 +96,8 @@ return array(
'App\\Services\\Cron\\FreenodeSync' => $baseDir . '/app/Services/Cron/FreenodeSync.php',
'App\\Services\\Cron\\FreenodeWatch' => $baseDir . '/app/Services/Cron/FreenodeWatch.php',
'App\\Services\\Cron\\HttpQueue' => $baseDir . '/app/Services/Cron/HttpQueue.php',
'App\\Services\\Cron\\NasaTestFnSubfile' => $baseDir . '/app/Services/Cron/NasaTestFnSubfile.php',
'App\\Services\\Cron\\ShadowRocketCrawl' => $baseDir . '/app/Services/Cron/ShadowRocketCrawl.php',
'App\\Services\\FreenodeHelperService' => $baseDir . '/app/Services/FreenodeHelperService.php',
'App\\Services\\HttpQueue' => $baseDir . '/app/Services/HttpQueue.php',
'App\\Services\\Nasa\\NavigationService' => $baseDir . '/app/Services/Nasa/NavigationService.php',
@ -5734,6 +5755,25 @@ return array(
'Symfony\\Component\\CssSelector\\XPath\\Translator' => $vendorDir . '/symfony/css-selector/XPath/Translator.php',
'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => $vendorDir . '/symfony/css-selector/XPath/TranslatorInterface.php',
'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => $vendorDir . '/symfony/css-selector/XPath/XPathExpr.php',
'Symfony\\Component\\DomCrawler\\AbstractUriElement' => $vendorDir . '/symfony/dom-crawler/AbstractUriElement.php',
'Symfony\\Component\\DomCrawler\\Crawler' => $vendorDir . '/symfony/dom-crawler/Crawler.php',
'Symfony\\Component\\DomCrawler\\Field\\ChoiceFormField' => $vendorDir . '/symfony/dom-crawler/Field/ChoiceFormField.php',
'Symfony\\Component\\DomCrawler\\Field\\FileFormField' => $vendorDir . '/symfony/dom-crawler/Field/FileFormField.php',
'Symfony\\Component\\DomCrawler\\Field\\FormField' => $vendorDir . '/symfony/dom-crawler/Field/FormField.php',
'Symfony\\Component\\DomCrawler\\Field\\InputFormField' => $vendorDir . '/symfony/dom-crawler/Field/InputFormField.php',
'Symfony\\Component\\DomCrawler\\Field\\TextareaFormField' => $vendorDir . '/symfony/dom-crawler/Field/TextareaFormField.php',
'Symfony\\Component\\DomCrawler\\Form' => $vendorDir . '/symfony/dom-crawler/Form.php',
'Symfony\\Component\\DomCrawler\\FormFieldRegistry' => $vendorDir . '/symfony/dom-crawler/FormFieldRegistry.php',
'Symfony\\Component\\DomCrawler\\Image' => $vendorDir . '/symfony/dom-crawler/Image.php',
'Symfony\\Component\\DomCrawler\\Link' => $vendorDir . '/symfony/dom-crawler/Link.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerAnySelectorTextContains' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerAnySelectorTextContains.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerAnySelectorTextSame' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerAnySelectorTextSame.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorAttributeValueSame' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorCount' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorCount.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorExists' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorExists.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorTextContains' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextContains.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorTextSame' => $vendorDir . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextSame.php',
'Symfony\\Component\\DomCrawler\\UriResolver' => $vendorDir . '/symfony/dom-crawler/UriResolver.php',
'Symfony\\Component\\ErrorHandler\\BufferingLogger' => $vendorDir . '/symfony/error-handler/BufferingLogger.php',
'Symfony\\Component\\ErrorHandler\\Debug' => $vendorDir . '/symfony/error-handler/Debug.php',
'Symfony\\Component\\ErrorHandler\\DebugClassLoader' => $vendorDir . '/symfony/error-handler/DebugClassLoader.php',

View File

@ -40,6 +40,7 @@ return array(
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'),
'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'),
'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'),

View File

@ -89,6 +89,7 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\ErrorHandler\\' => 31,
'Symfony\\Component\\DomCrawler\\' => 29,
'Symfony\\Component\\CssSelector\\' => 30,
'Symfony\\Component\\Console\\' => 26,
'Symfony\\Component\\Clock\\' => 24,
@ -324,6 +325,10 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
array (
0 => __DIR__ . '/..' . '/symfony/error-handler',
),
'Symfony\\Component\\DomCrawler\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/dom-crawler',
),
'Symfony\\Component\\CssSelector\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/css-selector',
@ -558,8 +563,11 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'App\\Http\\Controllers\\Api\\FreenodeController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/FreenodeController.php',
'App\\Http\\Controllers\\Api\\GithubController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/GithubController.php',
'App\\Http\\Controllers\\Api\\GoodController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/GoodController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\Controller' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Nasa/V1/Controller.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\Base\\ListController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Nasa/V1/Base/ListController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\BladeBaseController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Nasa/V1/BladeBaseController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\FnCrawController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Nasa/V1/FnCrawController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\SettingNavController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Nasa/V1/SettingNavController.php',
'App\\Http\\Controllers\\Api\\Nasa\\V1\\TestsFnSubfileController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Nasa/V1/TestsFnSubfileController.php',
'App\\Http\\Controllers\\Api\\ShipController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/ShipController.php',
'App\\Http\\Controllers\\Api\\Tg\\HookController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/Tg/HookController.php',
'App\\Http\\Controllers\\Api\\v1\\FreenodeCountrySpeedController' => __DIR__ . '/../..' . '/app/Http/Controllers/Api/v1/FreenodeCountrySpeedController.php',
@ -568,9 +576,21 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'App\\Http\\Controllers\\CronController' => __DIR__ . '/../..' . '/app/Http/Controllers/CronController.php',
'App\\Http\\Controllers\\NimaController' => __DIR__ . '/../..' . '/app/Http/Controllers/NimaController.php',
'App\\Http\\Controllers\\SakaiController' => __DIR__ . '/../..' . '/app/Http/Controllers/SakaiController.php',
'App\\Http\\Controllers\\Web\\BirdController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/BirdController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\AuthController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/AuthController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Base\\ListController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/Base/ListController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\BookController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/BookController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Demo\\NasaStyleController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/Demo/NasaStyleController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Setting\\MasterNavController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/Setting/MasterNavController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuAnnBaseController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/FuAnnBaseController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuEnvConfigController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/FuEnvConfigController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuMailMustlogController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/FuMailMustlogController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuNodeActionController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/FuNodeActionController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\FuNodeBaseController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/FuNodeBaseController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\MasterBladeBaseController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/MasterBladeBaseController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\MasterFnCrawController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/MasterFnCrawController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\MasterPanelNavController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/MasterPanelNavController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\Stats\\DashboardTotalController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/Stats/DashboardTotalController.php',
'App\\Http\\Controllers\\Web\\Nasa\\V1\\TestsFnSubfileController' => __DIR__ . '/../..' . '/app/Http/Controllers/Web/Nasa/V1/TestsFnSubfileController.php',
'App\\Http\\Middleware\\VerifyNasaToken' => __DIR__ . '/../..' . '/app/Http/Middleware/VerifyNasaToken.php',
'App\\Http\\Requests\\Nasa\\NasaQueryRequest' => __DIR__ . '/../..' . '/app/Http/Requests/Nasa/NasaQueryRequest.php',
'App\\Models\\ArticleCache' => __DIR__ . '/../..' . '/app/Models/ArticleCache.php',
@ -580,6 +600,7 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'App\\Models\\ArticleSchema' => __DIR__ . '/../..' . '/app/Models/ArticleSchema.php',
'App\\Models\\ArticleSiteMapGroup' => __DIR__ . '/../..' . '/app/Models/ArticleSiteMapGroup.php',
'App\\Models\\ArticleTemplateFreenode' => __DIR__ . '/../..' . '/app/Models/ArticleTemplateFreenode.php',
'App\\Models\\Bird' => __DIR__ . '/../..' . '/app/Models/Bird.php',
'App\\Models\\Blade' => __DIR__ . '/../..' . '/app/Models/Blade.php',
'App\\Models\\BladeJctj' => __DIR__ . '/../..' . '/app/Models/BladeJctj.php',
'App\\Models\\BladeJctjOption' => __DIR__ . '/../..' . '/app/Models/BladeJctjOption.php',
@ -599,6 +620,7 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'App\\Models\\Hook' => __DIR__ . '/../..' . '/app/Models/Hook.php',
'App\\Models\\HttpQueue' => __DIR__ . '/../..' . '/app/Models/HttpQueue.php',
'App\\Models\\Me' => __DIR__ . '/../..' . '/app/Models/Me.php',
'App\\Models\\Nasa\\Book' => __DIR__ . '/../..' . '/app/Models/Nasa/Book.php',
'App\\Models\\Nasa\\Navigation' => __DIR__ . '/../..' . '/app/Models/Nasa/Navigation.php',
'App\\Models\\Note' => __DIR__ . '/../..' . '/app/Models/Note.php',
'App\\Models\\NoteOption' => __DIR__ . '/../..' . '/app/Models/NoteOption.php',
@ -607,6 +629,8 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'App\\Models\\SiteMap' => __DIR__ . '/../..' . '/app/Models/SiteMap.php',
'App\\Models\\SitePathMap' => __DIR__ . '/../..' . '/app/Models/SitePathMap.php',
'App\\Models\\TelegramKey' => __DIR__ . '/../..' . '/app/Models/TelegramKey.php',
'App\\Models\\TestSubfile' => __DIR__ . '/../..' . '/app/Models/TestSubfile.php',
'App\\Models\\Users' => __DIR__ . '/../..' . '/app/Models/Users.php',
'App\\Providers\\AppServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AppServiceProvider.php',
'App\\Services\\Article\\CacheTg' => __DIR__ . '/../..' . '/app/Services/Article/CacheTg.php',
'App\\Services\\Article\\Cmd' => __DIR__ . '/../..' . '/app/Services/Article/Cmd.php',
@ -625,6 +649,8 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'App\\Services\\Cron\\FreenodeSync' => __DIR__ . '/../..' . '/app/Services/Cron/FreenodeSync.php',
'App\\Services\\Cron\\FreenodeWatch' => __DIR__ . '/../..' . '/app/Services/Cron/FreenodeWatch.php',
'App\\Services\\Cron\\HttpQueue' => __DIR__ . '/../..' . '/app/Services/Cron/HttpQueue.php',
'App\\Services\\Cron\\NasaTestFnSubfile' => __DIR__ . '/../..' . '/app/Services/Cron/NasaTestFnSubfile.php',
'App\\Services\\Cron\\ShadowRocketCrawl' => __DIR__ . '/../..' . '/app/Services/Cron/ShadowRocketCrawl.php',
'App\\Services\\FreenodeHelperService' => __DIR__ . '/../..' . '/app/Services/FreenodeHelperService.php',
'App\\Services\\HttpQueue' => __DIR__ . '/../..' . '/app/Services/HttpQueue.php',
'App\\Services\\Nasa\\NavigationService' => __DIR__ . '/../..' . '/app/Services/Nasa/NavigationService.php',
@ -6282,6 +6308,25 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'Symfony\\Component\\CssSelector\\XPath\\Translator' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Translator.php',
'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/css-selector/XPath/TranslatorInterface.php',
'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => __DIR__ . '/..' . '/symfony/css-selector/XPath/XPathExpr.php',
'Symfony\\Component\\DomCrawler\\AbstractUriElement' => __DIR__ . '/..' . '/symfony/dom-crawler/AbstractUriElement.php',
'Symfony\\Component\\DomCrawler\\Crawler' => __DIR__ . '/..' . '/symfony/dom-crawler/Crawler.php',
'Symfony\\Component\\DomCrawler\\Field\\ChoiceFormField' => __DIR__ . '/..' . '/symfony/dom-crawler/Field/ChoiceFormField.php',
'Symfony\\Component\\DomCrawler\\Field\\FileFormField' => __DIR__ . '/..' . '/symfony/dom-crawler/Field/FileFormField.php',
'Symfony\\Component\\DomCrawler\\Field\\FormField' => __DIR__ . '/..' . '/symfony/dom-crawler/Field/FormField.php',
'Symfony\\Component\\DomCrawler\\Field\\InputFormField' => __DIR__ . '/..' . '/symfony/dom-crawler/Field/InputFormField.php',
'Symfony\\Component\\DomCrawler\\Field\\TextareaFormField' => __DIR__ . '/..' . '/symfony/dom-crawler/Field/TextareaFormField.php',
'Symfony\\Component\\DomCrawler\\Form' => __DIR__ . '/..' . '/symfony/dom-crawler/Form.php',
'Symfony\\Component\\DomCrawler\\FormFieldRegistry' => __DIR__ . '/..' . '/symfony/dom-crawler/FormFieldRegistry.php',
'Symfony\\Component\\DomCrawler\\Image' => __DIR__ . '/..' . '/symfony/dom-crawler/Image.php',
'Symfony\\Component\\DomCrawler\\Link' => __DIR__ . '/..' . '/symfony/dom-crawler/Link.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerAnySelectorTextContains' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerAnySelectorTextContains.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerAnySelectorTextSame' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerAnySelectorTextSame.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorAttributeValueSame' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorCount' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorCount.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorExists' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorExists.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorTextContains' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextContains.php',
'Symfony\\Component\\DomCrawler\\Test\\Constraint\\CrawlerSelectorTextSame' => __DIR__ . '/..' . '/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextSame.php',
'Symfony\\Component\\DomCrawler\\UriResolver' => __DIR__ . '/..' . '/symfony/dom-crawler/UriResolver.php',
'Symfony\\Component\\ErrorHandler\\BufferingLogger' => __DIR__ . '/..' . '/symfony/error-handler/BufferingLogger.php',
'Symfony\\Component\\ErrorHandler\\Debug' => __DIR__ . '/..' . '/symfony/error-handler/Debug.php',
'Symfony\\Component\\ErrorHandler\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/error-handler/DebugClassLoader.php',

View File

@ -6209,23 +6209,23 @@
},
{
"name": "symfony/css-selector",
"version": "v7.2.0",
"version_normalized": "7.2.0.0",
"version": "v7.4.9",
"version_normalized": "7.4.9.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
"reference": "b75663ed96cf4756e28e3105476f220f92886cc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4",
"reference": "b75663ed96cf4756e28e3105476f220f92886cc4",
"shasum": ""
},
"require": {
"php": ">=8.2"
},
"time": "2024-09-25T14:21:43+00:00",
"time": "2026-04-18T13:18:21+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -6257,7 +6257,7 @@
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/css-selector/tree/v7.2.0"
"source": "https://github.com/symfony/css-selector/tree/v7.4.9"
},
"funding": [
{
@ -6268,6 +6268,10 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@ -6345,6 +6349,79 @@
],
"install-path": "../symfony/deprecation-contracts"
},
{
"name": "symfony/dom-crawler",
"version": "v8.1.1",
"version_normalized": "8.1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "1dfadd25537c8fcb6752cce5775f24647d976bdc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1dfadd25537c8fcb6752cce5775f24647d976bdc",
"reference": "1dfadd25537c8fcb6752cce5775f24647d976bdc",
"shasum": ""
},
"require": {
"php": ">=8.4.1",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
"symfony/css-selector": "^7.4|^8.0"
},
"time": "2026-06-05T06:23:12+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases DOM navigation for HTML and XML documents",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dom-crawler/tree/v8.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/dom-crawler"
},
{
"name": "symfony/error-handler",
"version": "v7.2.1",

View File

@ -3,7 +3,7 @@
'name' => 'laravel/laravel',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '1215f3b6667e56176f7a2d8a1c846208d8d52147',
'reference' => '934c2f931bf5bf8f30653081328f355b0ca1d7fe',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -415,7 +415,7 @@
'laravel/laravel' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '1215f3b6667e56176f7a2d8a1c846208d8d52147',
'reference' => '934c2f931bf5bf8f30653081328f355b0ca1d7fe',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -1056,9 +1056,9 @@
'dev_requirement' => false,
),
'symfony/css-selector' => array(
'pretty_version' => 'v7.2.0',
'version' => '7.2.0.0',
'reference' => '601a5ce9aaad7bf10797e3663faefce9e26c24e2',
'pretty_version' => 'v7.4.9',
'version' => '7.4.9.0',
'reference' => 'b75663ed96cf4756e28e3105476f220f92886cc4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/css-selector',
'aliases' => array(),
@ -1073,6 +1073,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/dom-crawler' => array(
'pretty_version' => 'v8.1.1',
'version' => '8.1.1.0',
'reference' => '1dfadd25537c8fcb6752cce5775f24647d976bdc',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/dom-crawler',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/error-handler' => array(
'pretty_version' => 'v7.2.1',
'version' => '7.2.1.0',

View File

@ -4,8 +4,8 @@
$issues = array();
if (!(PHP_VERSION_ID >= 80200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
if (!(PHP_VERSION_ID >= 80401)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.4.1". You are running ' . PHP_VERSION . '.';
}
if ($issues) {

0
vendor/symfony/css-selector/CHANGELOG.md vendored Executable file → Normal file
View File

19
vendor/symfony/css-selector/CssSelectorConverter.php vendored Executable file → Normal file
View File

@ -26,6 +26,8 @@ use Symfony\Component\CssSelector\XPath\Translator;
*/
class CssSelectorConverter
{
public static int $maxCachedItems = 1024;
private Translator $translator;
private array $cache;
@ -62,6 +64,21 @@ class CssSelectorConverter
*/
public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
{
return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix);
$cacheKey = $prefix."\0".$cssExpr;
if (isset($this->cache[$cacheKey])) {
// Move the item last in cache (LRU)
$value = $this->cache[$cacheKey];
unset($this->cache[$cacheKey]);
return $this->cache[$cacheKey] = $value;
}
if (\count($this->cache) >= self::$maxCachedItems) {
// Evict the oldest entry
unset($this->cache[array_key_first($this->cache)]);
}
return $this->cache[$cacheKey] = $this->translator->cssToXPath($cssExpr, $prefix);
}
}

0
vendor/symfony/css-selector/Exception/ExceptionInterface.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Exception/ExpressionErrorException.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Exception/InternalErrorException.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Exception/ParseException.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Exception/SyntaxErrorException.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/LICENSE vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/AbstractNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/AttributeNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/ClassNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/CombinedSelectorNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/ElementNode.php vendored Executable file → Normal file
View File

2
vendor/symfony/css-selector/Node/FunctionNode.php vendored Executable file → Normal file
View File

@ -63,7 +63,7 @@ class FunctionNode extends AbstractNode
public function __toString(): string
{
$arguments = implode(', ', array_map(fn (Token $token) => "'".$token->getValue()."'", $this->arguments));
$arguments = implode(', ', array_map(static fn (Token $token) => "'".$token->getValue()."'", $this->arguments));
return \sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : '');
}

0
vendor/symfony/css-selector/Node/HashNode.php vendored Executable file → Normal file
View File

4
vendor/symfony/css-selector/Node/MatchingNode.php vendored Executable file → Normal file
View File

@ -36,7 +36,7 @@ class MatchingNode extends AbstractNode
{
$argumentsSpecificity = array_reduce(
$this->arguments,
fn ($c, $n) => 1 === $n->getSpecificity()->compareTo($c) ? $n->getSpecificity() : $c,
static fn ($c, $n) => 1 === $n->getSpecificity()->compareTo($c) ? $n->getSpecificity() : $c,
new Specificity(0, 0, 0),
);
@ -46,7 +46,7 @@ class MatchingNode extends AbstractNode
public function __toString(): string
{
$selectorArguments = array_map(
fn ($n): string => ltrim((string) $n, '*'),
static fn ($n): string => ltrim((string) $n, '*'),
$this->arguments,
);

0
vendor/symfony/css-selector/Node/NegationNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/NodeInterface.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/PseudoNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/SelectorNode.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Node/Specificity.php vendored Executable file → Normal file
View File

2
vendor/symfony/css-selector/Node/SpecificityAdjustmentNode.php vendored Executable file → Normal file
View File

@ -40,7 +40,7 @@ class SpecificityAdjustmentNode extends AbstractNode
public function __toString(): string
{
$selectorArguments = array_map(
fn ($n) => ltrim((string) $n, '*'),
static fn ($n) => ltrim((string) $n, '*'),
$this->arguments,
);

0
vendor/symfony/css-selector/Parser/Handler/CommentHandler.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Handler/HashHandler.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Handler/NumberHandler.php vendored Executable file → Normal file
View File

2
vendor/symfony/css-selector/Parser/Handler/StringHandler.php vendored Executable file → Normal file
View File

@ -41,7 +41,7 @@ class StringHandler implements HandlerInterface
{
$quote = $reader->getSubstring(1);
if (!\in_array($quote, ["'", '"'])) {
if (!\in_array($quote, ["'", '"'], true)) {
return false;
}

0
vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php vendored Executable file → Normal file
View File

6
vendor/symfony/css-selector/Parser/Parser.php vendored Executable file → Normal file
View File

@ -57,9 +57,9 @@ class Parser implements ParserInterface
}
}
$joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens)));
$joined = trim(implode('', array_map(static fn (Token $token) => $token->getValue(), $tokens)));
$int = function ($string) {
$int = static function ($string) {
if (!is_numeric($string)) {
throw SyntaxErrorException::stringAsFunctionArgument();
}
@ -190,7 +190,7 @@ class Parser implements ParserInterface
}
$identifier = $stream->getNextIdentifier();
if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) {
if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'], true)) {
// Special case: CSS 2.1 pseudo-elements can have a single ':'.
// Any new pseudo-element must have two.
$pseudoElement = $identifier;

0
vendor/symfony/css-selector/Parser/ParserInterface.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Reader.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Shortcut/HashParser.php vendored Executable file → Normal file
View File

8
vendor/symfony/css-selector/Parser/Token.php vendored Executable file → Normal file
View File

@ -31,6 +31,9 @@ class Token
public const TYPE_NUMBER = 'number';
public const TYPE_STRING = 'string';
/**
* @param self::TYPE_*|null $type
*/
public function __construct(
private ?string $type,
private ?string $value,
@ -38,7 +41,10 @@ class Token
) {
}
public function getType(): ?int
/**
* @return self::TYPE_*|null
*/
public function getType(): ?string
{
return $this->type;
}

0
vendor/symfony/css-selector/Parser/TokenStream.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php vendored Executable file → Normal file
View File

2
vendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php vendored Executable file → Normal file
View File

@ -44,7 +44,7 @@ class TokenizerEscaping
private function replaceUnicodeSequences(string $value): string
{
return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) {
return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), static function ($match) {
$c = hexdec($match[1]);
if (0x80 > $c %= 0x200000) {

0
vendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/XPath/Extension/AbstractExtension.php vendored Executable file → Normal file
View File

View File

View File

0
vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/XPath/Extension/FunctionExtension.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php vendored Executable file → Normal file
View File

33
vendor/symfony/css-selector/XPath/Extension/NodeExtension.php vendored Executable file → Normal file
View File

@ -99,31 +99,34 @@ class NodeExtension extends AbstractExtension
public function translateMatching(Node\MatchingNode $node, Translator $translator): XPathExpr
{
$xpath = $translator->nodeToXPath($node->selector);
foreach ($node->arguments as $argument) {
$expr = $translator->nodeToXPath($argument);
$expr->addNameTest();
if ($condition = $expr->getCondition()) {
$xpath->addCondition($condition, 'or');
}
}
return $xpath;
return $this->translateMatchingOrSpecificityAdjustment($node->selector, $node->arguments, $translator);
}
public function translateSpecificityAdjustment(Node\SpecificityAdjustmentNode $node, Translator $translator): XPathExpr
{
$xpath = $translator->nodeToXPath($node->selector);
return $this->translateMatchingOrSpecificityAdjustment($node->selector, $node->arguments, $translator);
}
foreach ($node->arguments as $argument) {
/**
* @param array<Node\NodeInterface> $arguments
*/
private function translateMatchingOrSpecificityAdjustment(Node\NodeInterface $selector, array $arguments, Translator $translator): XPathExpr
{
$xpath = $translator->nodeToXPath($selector);
$conditions = [];
foreach ($arguments as $argument) {
$expr = $translator->nodeToXPath($argument);
$expr->addNameTest();
if ($condition = $expr->getCondition()) {
$xpath->addCondition($condition, 'or');
if ('' !== $condition = $expr->getCondition()) {
$conditions[] = $condition;
}
}
if ($conditions) {
$xpath->addCondition(1 === \count($conditions) ? $conditions[0] : '('.implode(') or (', $conditions).')');
}
return $xpath;
}

View File

1
vendor/symfony/css-selector/XPath/Translator.php vendored Executable file → Normal file
View File

@ -91,7 +91,6 @@ class Translator implements TranslatorInterface
{
$selectors = $this->parseSelectors($cssExpr);
/** @var SelectorNode $selector */
foreach ($selectors as $index => $selector) {
if (null !== $selector->getPseudoElement()) {
throw new ExpressionErrorException('Pseudo-elements are not supported.');

0
vendor/symfony/css-selector/XPath/TranslatorInterface.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/XPath/XPathExpr.php vendored Executable file → Normal file
View File

0
vendor/symfony/css-selector/composer.json vendored Executable file → Normal file
View File

View File

@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler;
/**
* Any HTML element that can link to an URI.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractUriElement
{
protected \DOMElement $node;
protected ?string $method;
/**
* @param \DOMElement $node A \DOMElement instance
* @param string|null $currentUri The URI of the page where the link is embedded (or the base href)
* @param string|null $method The method to use for the link (GET by default)
*
* @throws \InvalidArgumentException if the node is not a link
*/
public function __construct(
\DOMElement $node,
protected ?string $currentUri = null,
?string $method = 'GET',
) {
$this->setNode($node);
$this->method = $method ? strtoupper($method) : null;
$elementUriIsRelative = !parse_url(trim($this->getRawUri()), \PHP_URL_SCHEME);
$baseUriIsAbsolute = null !== $this->currentUri && \in_array(strtolower(substr($this->currentUri, 0, 4)), ['http', 'file'], true);
if ($elementUriIsRelative && !$baseUriIsAbsolute) {
throw new \InvalidArgumentException(\sprintf('The URL of the element is relative, so you must define its base URI passing an absolute URL to the constructor of the "%s" class ("%s" was passed).', __CLASS__, $this->currentUri));
}
}
/**
* Gets the node associated with this link.
*/
public function getNode(): \DOMElement
{
return $this->node;
}
/**
* Gets the method associated with this link.
*/
public function getMethod(): string
{
return $this->method ?? 'GET';
}
/**
* Gets the URI associated with this link.
*/
public function getUri(): string
{
return UriResolver::resolve($this->getRawUri(), $this->currentUri);
}
/**
* Returns raw URI data.
*/
abstract protected function getRawUri(): string;
/**
* Returns the canonicalized URI path (see RFC 3986, section 5.2.4).
*
* @param string $path URI path
*/
protected function canonicalizePath(string $path): string
{
if ('' === $path || '/' === $path) {
return $path;
}
if (str_ends_with($path, '.')) {
$path .= '/';
}
$output = [];
foreach (explode('/', $path) as $segment) {
if ('..' === $segment) {
array_pop($output);
} elseif ('.' !== $segment) {
$output[] = $segment;
}
}
return implode('/', $output);
}
/**
* Sets current \DOMElement instance.
*
* @param \DOMElement $node A \DOMElement instance
*
* @throws \LogicException If given node is not an anchor
*/
abstract protected function setNode(\DOMElement $node): void;
}

146
vendor/symfony/dom-crawler/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,146 @@
CHANGELOG
=========
8.1
---
* Make `ChoiceFormField::addChoice()` part of the supported public API
* Always set `LIBXML_NONET` in `Crawler::addXmlContent()` so external entities cannot trigger network requests
8.0
---
* Remove argument `$useHtml5Parser` of `Crawler`'s constructor; the native HTML5 parser is used unconditionally
7.4
---
* Disabling HTML5 parsing is deprecated; Symfony 8 will unconditionally use the native HTML5 parser
7.0
---
* Add argument `$normalizeWhitespace` to `Crawler::innerText()`
* Add argument `$default` to `Crawler::attr()`
6.4
---
* Add `CrawlerAnySelectorTextContains` test constraint
* Add `CrawlerAnySelectorTextSame` test constraint
* Add argument `$default` to `Crawler::attr()`
6.3
---
* Add `$useHtml5Parser` argument to `Crawler`
* Add `CrawlerSelectorCount` test constraint
* Add argument `$normalizeWhitespace` to `Crawler::innerText()`
* Make `Crawler::innerText()` return the first non-empty text
6.0
---
* Remove `Crawler::parents()` method, use `ancestors()` instead
5.4
---
* Add `Crawler::innerText` method.
5.3
---
* The `parents()` method is deprecated. Use `ancestors()` instead.
* Marked the `containsOption()`, `availableOptionValues()`, and `disableValidation()` methods of the
`ChoiceFormField` class as internal
5.1.0
-----
* Added an internal cache layer on top of the CssSelectorConverter
* Added `UriResolver` to resolve an URI according to a base URI
5.0.0
-----
* Added argument `$selector` to `Crawler::children()`
* Added argument `$default` to `Crawler::text()` and `html()`
4.4.0
-----
* Added `Form::getName()` method.
* Added `Crawler::matches()` method.
* Added `Crawler::closest()` method.
* Added `Crawler::outerHtml()` method.
* Added an argument to the `Crawler::text()` method to opt-in normalizing whitespaces.
4.3.0
-----
* Added PHPUnit constraints: `CrawlerSelectorAttributeValueSame`, `CrawlerSelectorExists`, `CrawlerSelectorTextContains`
and `CrawlerSelectorTextSame`
* Added return of element name (`_name`) in `extract()` method.
* Added ability to return a default value in `text()` and `html()` instead of throwing an exception when node is empty.
* When available, the [html5-php library](https://github.com/Masterminds/html5-php) is used to
parse HTML added to a Crawler for better support of HTML5 tags.
4.2.0
-----
* The `$currentUri` constructor argument of the `AbstractUriElement`, `Link` and
`Image` classes is now optional.
* The `Crawler::children()` method will have a new `$selector` argument in version 5.0,
not defining it is deprecated.
3.1.0
-----
* All the URI parsing logic have been abstracted in the `AbstractUriElement` class.
The `Link` class is now a child of `AbstractUriElement`.
* Added an `Image` class to crawl images and parse their `src` attribute,
and `selectImage`, `image`, `images` methods in the `Crawler` (the image version of the equivalent `link` methods).
2.5.0
-----
* [BC BREAK] The default value for checkbox and radio inputs without a value attribute have changed
from '1' to 'on' to match the HTML specification.
* [BC BREAK] The typehints on the `Link`, `Form` and `FormField` classes have been changed from
`\DOMNode` to `DOMElement`. Using any other type of `DOMNode` was triggering fatal errors in previous
versions. Code extending these classes will need to update the typehints when overwriting these methods.
2.4.0
-----
* `Crawler::addXmlContent()` removes the default document namespace again if it's an only namespace.
* added support for automatic discovery and explicit registration of document
namespaces for `Crawler::filterXPath()` and `Crawler::filter()`
* improved content type guessing in `Crawler::addContent()`
* [BC BREAK] `Crawler::addXmlContent()` no longer removes the default document
namespace
2.3.0
-----
* added Crawler::html()
* [BC BREAK] Crawler::each() and Crawler::reduce() now return Crawler instances instead of DomElement instances
* added schema relative URL support to links
* added support for HTML5 'form' attribute
2.2.0
-----
* added a way to set raw path to the file in FileFormField - necessary for
simulating HTTP requests
2.1.0
-----
* added support for the HTTP PATCH method
* refactored the Form class internals to support multi-dimensional fields
(the public API is backward compatible)
* added a way to get parsing errors for Crawler::addHtmlContent() and
Crawler::addXmlContent() via libxml functions
* added support for submitting a form without a submit button

1217
vendor/symfony/dom-crawler/Crawler.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,307 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* ChoiceFormField represents a choice form field.
*
* It is constructed from an HTML select tag, or an HTML checkbox, or radio inputs.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChoiceFormField extends FormField
{
private string $type;
private bool $multiple;
private array $options;
private bool $validationDisabled = false;
/**
* Returns true if the field should be included in the submitted values.
*
* @return bool true if the field should be included in the submitted values, false otherwise
*/
public function hasValue(): bool
{
// don't send a value for unchecked checkboxes
if (\in_array($this->type, ['checkbox', 'radio'], true) && null === $this->value) {
return false;
}
return true;
}
/**
* Check if the current selected option is disabled.
*/
public function isDisabled(): bool
{
if ('checkbox' === $this->type) {
return parent::isDisabled();
}
if (parent::isDisabled() && 'select' === $this->type) {
return true;
}
foreach ($this->options as $option) {
if ($option['value'] == $this->value && $option['disabled']) {
return true;
}
}
return false;
}
/**
* Sets the value of the field.
*/
public function select(string|array|bool $value): void
{
$this->setValue($value);
}
/**
* Ticks a checkbox.
*
* @throws \LogicException When the type provided is not correct
*/
public function tick(): void
{
if ('checkbox' !== $this->type) {
throw new \LogicException(\sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type));
}
$this->setValue(true);
}
/**
* Unticks a checkbox.
*
* @throws \LogicException When the type provided is not correct
*/
public function untick(): void
{
if ('checkbox' !== $this->type) {
throw new \LogicException(\sprintf('You cannot untick "%s" as it is not a checkbox (%s).', $this->name, $this->type));
}
$this->setValue(false);
}
/**
* Sets the value of the field.
*
* @throws \InvalidArgumentException When value type provided is not correct
*/
public function setValue(string|array|bool|null $value): void
{
if ('checkbox' === $this->type && false === $value) {
// uncheck
$this->value = null;
} elseif ('checkbox' === $this->type && true === $value) {
// check
$this->value = $this->options[0]['value'];
} else {
if (\is_array($value)) {
if (!$this->multiple) {
throw new \InvalidArgumentException(\sprintf('The value for "%s" cannot be an array.', $this->name));
}
foreach ($value as $v) {
if (!$this->containsOption($v, $this->options)) {
throw new \InvalidArgumentException(\sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $v, implode('", "', $this->availableOptionValues())));
}
}
} elseif (!$this->containsOption($value, $this->options)) {
throw new \InvalidArgumentException(\sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $value, implode('", "', $this->availableOptionValues())));
}
if ($this->multiple) {
$value = (array) $value;
}
if (\is_array($value)) {
$this->value = $value;
} else {
parent::setValue($value);
}
}
}
/**
* Adds a choice to the current ones.
*
* @throws \LogicException When choice provided is neither multiple, radio nor select,
* or when the node tag does not match the field type
*/
public function addChoice(\DOMElement $node): void
{
if (!$this->multiple && !\in_array($this->type, ['radio', 'select'], true)) {
throw new \LogicException(\sprintf('Unable to add a choice for "%s" as it is neither multiple, a radio button nor a select field (type is "%s").', $this->name, $this->type));
}
$expectedTag = 'select' === $this->type ? 'option' : 'input';
if ($expectedTag !== $node->nodeName) {
throw new \LogicException(\sprintf('Unable to add a choice for "%s": expected an "%s" tag, got "%s".', $this->name, $expectedTag, $node->nodeName));
}
$option = $this->buildOptionValue($node);
$this->options[] = $option;
if ($node->hasAttribute('select' === $this->type ? 'selected' : 'checked')) {
if ($this->multiple) {
$this->value[] = $option['value'];
} else {
$this->value = $option['value'];
}
}
}
/**
* Returns the type of the choice field (radio, select, or checkbox).
*/
public function getType(): string
{
return $this->type;
}
/**
* Returns true if the field accepts multiple values.
*/
public function isMultiple(): bool
{
return $this->multiple;
}
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize(): void
{
if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) {
throw new \LogicException(\sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName));
}
if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) {
throw new \LogicException(\sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is "%s").', $this->node->getAttribute('type')));
}
$this->value = null;
$this->options = [];
$this->multiple = false;
if ('input' == $this->node->nodeName) {
$this->type = strtolower($this->node->getAttribute('type'));
$optionValue = $this->buildOptionValue($this->node);
$this->options[] = $optionValue;
if ($this->node->hasAttribute('checked')) {
$this->value = $optionValue['value'];
}
} else {
$this->type = 'select';
if ($this->node->hasAttribute('multiple')) {
$this->multiple = true;
$this->value = [];
$this->name = str_replace('[]', '', $this->name);
}
$found = false;
foreach ($this->xpath->query('descendant::option', $this->node) as $option) {
$optionValue = $this->buildOptionValue($option);
$this->options[] = $optionValue;
if ($option->hasAttribute('selected')) {
$found = true;
if ($this->multiple) {
$this->value[] = $optionValue['value'];
} else {
$this->value = $optionValue['value'];
}
}
}
// if no option is selected and if it is a simple select box, take the first option as the value
if (!$found && !$this->multiple && $this->options) {
$this->value = $this->options[0]['value'];
}
}
}
/**
* Returns option value with associated disabled flag.
*/
private function buildOptionValue(\DOMElement $node): array
{
$option = [];
$defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on';
$defaultValue = (isset($node->nodeValue) && $node->nodeValue) ? $node->nodeValue : $defaultDefaultValue;
$option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue;
$option['disabled'] = $node->hasAttribute('disabled');
return $option;
}
/**
* Checks whether given value is in the existing options.
*
* @internal
*/
public function containsOption(string $optionValue, array $options): bool
{
if ($this->validationDisabled) {
return true;
}
foreach ($options as $option) {
if ($option['value'] == $optionValue) {
return true;
}
}
return false;
}
/**
* Returns list of available field options.
*
* @internal
*/
public function availableOptionValues(): array
{
$values = [];
foreach ($this->options as $option) {
$values[] = $option['value'];
}
return $values;
}
/**
* Disables the internal validation of the field.
*
* @internal
*
* @return $this
*/
public function disableValidation(): static
{
$this->validationDisabled = true;
return $this;
}
}

View File

@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* FileFormField represents a file form field (an HTML file input tag).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileFormField extends FormField
{
/**
* Sets the PHP error code associated with the field.
*
* @param int $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION)
*
* @throws \InvalidArgumentException When error code doesn't exist
*/
public function setErrorCode(int $error): void
{
if (!\in_array($error, [\UPLOAD_ERR_INI_SIZE, \UPLOAD_ERR_FORM_SIZE, \UPLOAD_ERR_PARTIAL, \UPLOAD_ERR_NO_FILE, \UPLOAD_ERR_NO_TMP_DIR, \UPLOAD_ERR_CANT_WRITE, \UPLOAD_ERR_EXTENSION], true)) {
throw new \InvalidArgumentException(\sprintf('The error code "%s" is not valid.', $error));
}
$this->value = ['name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0];
}
/**
* Sets the value of the field.
*/
public function upload(?string $value): void
{
$this->setValue($value);
}
/**
* Sets the value of the field.
*/
public function setValue(?string $value): void
{
if (null !== $value && is_readable($value)) {
$error = \UPLOAD_ERR_OK;
$size = filesize($value);
$info = pathinfo($value);
$name = $info['basename'];
// copy to a tmp location
$tmp = tempnam(sys_get_temp_dir(), $name);
if (\array_key_exists('extension', $info)) {
unlink($tmp);
$tmp .= '.'.$info['extension'];
}
if (is_file($tmp)) {
unlink($tmp);
}
copy($value, $tmp);
$value = $tmp;
} else {
$error = \UPLOAD_ERR_NO_FILE;
$size = 0;
$name = '';
$value = '';
}
$this->value = ['name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size];
}
/**
* Sets path to the file as string for simulating HTTP request.
*/
public function setFilePath(string $path): void
{
parent::setValue($path);
}
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize(): void
{
if ('input' !== $this->node->nodeName) {
throw new \LogicException(\sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName));
}
if ('file' !== strtolower($this->node->getAttribute('type'))) {
throw new \LogicException(\sprintf('A FileFormField can only be created from an input tag with a type of file (given type is "%s").', $this->node->getAttribute('type')));
}
$this->setValue(null);
}
}

View File

@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* FormField is the abstract class for all form fields.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FormField
{
protected string $name;
protected string|array|null $value = null;
protected \DOMDocument $document;
protected \DOMXPath $xpath;
protected bool $disabled = false;
/**
* @param \DOMElement $node The node associated with this field
*/
public function __construct(
protected \DOMElement $node,
) {
$this->name = $node->getAttribute('name');
$this->xpath = new \DOMXPath($node->ownerDocument);
$this->initialize();
}
/**
* Returns the label tag associated to the field or null if none.
*/
public function getLabel(): ?\DOMElement
{
$xpath = new \DOMXPath($this->node->ownerDocument);
if ($this->node->hasAttribute('id')) {
$labels = $xpath->query(\sprintf('descendant::label[@for="%s"]', $this->node->getAttribute('id')));
if ($labels->length > 0) {
return $labels->item(0);
}
}
$labels = $xpath->query('ancestor::label[1]', $this->node);
return $labels->length > 0 ? $labels->item(0) : null;
}
/**
* Returns the name of the field.
*/
public function getName(): string
{
return $this->name;
}
/**
* Gets the value of the field.
*/
public function getValue(): string|array|null
{
return $this->value;
}
/**
* Sets the value of the field.
*/
public function setValue(?string $value): void
{
$this->value = $value ?? '';
}
/**
* Returns true if the field should be included in the submitted values.
*/
public function hasValue(): bool
{
return true;
}
/**
* Check if the current field is disabled.
*/
public function isDisabled(): bool
{
return $this->node->hasAttribute('disabled');
}
/**
* Initializes the form field.
*/
abstract protected function initialize(): void;
}

View File

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* InputFormField represents an input form field (an HTML input tag).
*
* For inputs with type of file, checkbox, or radio, there are other more
* specialized classes (cf. FileFormField and ChoiceFormField).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputFormField extends FormField
{
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize(): void
{
if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) {
throw new \LogicException(\sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName));
}
$type = strtolower($this->node->getAttribute('type'));
if ('checkbox' === $type) {
throw new \LogicException('Checkboxes should be instances of ChoiceFormField.');
}
if ('file' === $type) {
throw new \LogicException('File inputs should be instances of FileFormField.');
}
$this->value = $this->node->getAttribute('value');
}
}

Some files were not shown because too many files have changed in this diff Show More