This commit is contained in:
hellcat 2026-01-10 16:40:33 +08:00
commit c69d35d1bb
8791 changed files with 1066695 additions and 0 deletions

18
.editorconfig Executable file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

66
.env.example Executable file
View File

@ -0,0 +1,66 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

6
.gitignore vendored Executable file
View File

@ -0,0 +1,6 @@
.env
public/fn/
README.md
_env/
public/upload/
public/freenode/

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
//use App\Models\Article;
//use App\Services\TomTool\Http;
//use App\Services\TG\Http as TGHttp;
//use App\Models\Ship as ModelShip;
use App\Models\Option as ModelOption;
use Illuminate\Http\Request;
use Symfony\Component\Yaml\Yaml;
final class FreenodeController
{
public function sub($sSiteCode, $sClient, $sDate = "")
{
header('Content-Type: application/x-yaml; charset=utf-8');
$sDirBase = $_ENV["dir_base"];
// $aData = $request->json()->all();
// $sSiteCode = $sSiteCode ?? "base";
// $sClient = $aData["client"] ?? "";
$sDate = $sDate ?? date("Y-m-d");
//// *test
$sDate = "2025-12-22";
// $sClient = "clash";
//// test end
$aClient = [];
if ($sClient == "_all") {
$sFnClient = ModelOption::_hit("freenode_client");
$aFnClient = explode(",", $sFnClient);
foreach ($aFnClient as $sFnClient) {
$aClient[] = $sFnClient;
}
} else {
$aClient[] = $sClient;
}
$aContent = [];
foreach ($aClient as $sClient) {
$sFilePath = $this->pathReal($sSiteCode, $sClient, $sDate);
$sFileContent = file_get_contents($sFilePath); // data
$aContent[$sClient] = $sFileContent;
}
$iContentCount = count($aContent);
if ($iContentCount == 1) {
$k = array_key_first($aContent);
$sContent = $aContent[$k];
if ($k == "clash") {
header('Content-Type: application/x-yaml; charset=utf-8');
} else {
header('Content-Type: text/plain; charset=utf-8');
}
} else {
$sContent = json_encode($aContent);
}
echo $sContent;
exit;
}
private function pathReal($sSiteCode, $sClient, $sDate, $iDeep = 0)
{
if ($iDeep > 30) {
return false;
}
$sDirBase = $_ENV["dir_base"];
$sFilePath = $sDirBase."public/freenode/merge/".$sSiteCode."/".$sClient."/".$sDate.".txt";
if (file_exists($sFilePath)) {
return $sFilePath;
} else {
$oData = new \DateTime($sDate);
$oData->modify('-1 day');
$sDatePrev = $oData->format('Y-m-d');
return $this->pathReal($sSiteCode, $sClient, $sDatePrev, ++$iDeep);
}
}
}

View File

@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Tg;
//use App\Models\Article;
use App\Services\TomTool\Http;
use App\Services\Tg\Http as TGHttp;
use App\Models\Hook as ModelHook;
use App\Services\TomTool\ThrowableHandler;
//use App\Services\TomTool\TelegramVdb;
use App\Services\TomTool\Telegram\Slave as TelegramSlave;
final class HookController
{
private $sCustomText = '';
public function cmd_ffq_article()
{
try {
$sUpdate = file_get_contents("php://input");
$aUpdate = json_decode($sUpdate, true);
$sText = $aUpdate["message"]["text"] ?? "没有text";
if (!str_starts_with($sText, '/')) {
// $this->sCustomCmd = "article";
// $this->sCustomFunc = "cache";
// $this->aCustomOption[1] = "ffq";
$this->sCustomText = "/article#cacheAdd__ffq_tg_a"." ".$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()
{
try {
$oTGHttp = new TGHttp();
$oTGHttp->sApiToken = env('telegram_token');
$oTGHttp->sApiChatId = env('telegram_chatid');
$sUpdate = file_get_contents("php://input");
$aUpdate = json_decode($sUpdate, true);
// @file_put_contents('/www/_tg/bot_log.txt', json_encode($aUpdate, JSON_PRETTY_PRINT));
$aData['chatId'] = (string) $aUpdate["message"]["chat"]["id"];
$aData['fromId'] = (string) $aUpdate["message"]["from"]["id"];
if ($this->sCustomText) {
$aUpdate["message"]["text"] = $this->sCustomText;
}
if (!isset($aUpdate["message"]["text"])) {
throw new \Exception(json_encode($aUpdate));
}
$sText = $aUpdate["message"]["text"];
// hook优先
// 非is_hook并且首字符非/
if ((!isset($aUpdate["is_hook"]) || $aUpdate["is_hook"] == false) && substr($sText, 0, 1) !== "/") {
$sHookKey = ModelHook::where("tg_user_id", $aData["fromId"])->first()?->key;
if ($sHookKey) {
if ($sText == "run") {
$sText = $sHookKey;
} else {
$sText = $sHookKey.$sText;
}
$aUpdate["is_hook"] = true;
}
}
@$aArg = explode(" ", $sText) ?? [];
@$aOption = explode("__", $aArg[0]) ?? [];
$sCmd = $this->cleanCmd($aOption[0]);
// 大于3个说明是hook或dove之类
$aCmd = explode("#", $sCmd);
// if (count($aCmd) >= 3) {
// $sHook = $aCmd[0];
// $sClass = $sHook;
// $sFunc = "go";
// } else {
$sClass = $aCmd[0] ?? false;
$sFunc = $aCmd[1] ?? false;
// }
if (strpos($sClass, "dove$") !== false) {
$sClass = "dove";
$sFunc = "go";
}
$sUClass = ucfirst($sClass);
$sUFunc = ucfirst($sFunc);
unset($aOption[0]);
unset($aArg[0]);
$aParams = [];
$aParams['aOption'] = $aOption;
$aParams['aArg'] = $aArg;
$sClassPath = "App\\Services\\Tg\\Hook\\Mod\\".$sClass;
$sUClassPath = "App\\Services\\Tg\\Hook\\Mod\\".$sUClass;
$oInstans = new $sUClassPath($aUpdate);
$r = $oInstans->$sFunc($aParams, $aUpdate);
$oTGHttp->send($r, $aUpdate);
echo $r;
} catch (\Throwable $e) {
$aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA($aDataTg)->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;
// old
// $sMessage = "错误(文件: " . $e->getFile() . ",行: " . $e->getLine() . ": " . $e->getMessage();
// $oTGHttp->send($sMessage, $sUpdate);
// //Http::response(200, 'error'); 注释的
// old end
}
}
private function cleanCmd($input) {
$input = str_replace('\\', '', $input);
$input = str_replace('/', '', $input);
return $input;
}
}

7
app/Http/Kernel.php Executable file
View File

@ -0,0 +1,7 @@
<?php
protected $routeMiddleware = [
// 其他中间件...
// 'test' => \App\Http\Middleware\Test::class,
];
?>

21
app/Http/Middleware/Test.php Executable file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
//use voku\helper\AntiXSS;
class Test
{
public function handle(Request $request, Closure $next): Response
{
$sSearch = request('search');
// echo $sSearch;exit;
return $next($request);
}
}

12
app/Models/BoxGithubTag.php Executable file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class BoxGithubTag extends Model
{
protected $table = 'box_github_tag';
public $timestamps = false;
}

22
app/Models/CrawlFreenode.php Executable file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class CrawlFreenode extends Model
{
protected $table = 'crawl_freenode';
public $timestamps = false;
// public static function _listKey()
// {
// $aSelf = self::all()->toArray();
//
// foreach ($aSelf as &$aSelfRow) {
// unset($aSelfRow["content"]);
// }
//
// return $aSelf;
// }
}

51
app/Models/DogOption.php Executable file
View File

@ -0,0 +1,51 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DogOption extends Model
{
protected $table = 'dog_option';
public $timestamps = false;
public static function _hit($sDogName, $k)
{
$v = self::where("dog_name", $sDogName)->where("k", $k)->first()?->v;
// tomd($sDogName);
return $v;
}
public static function _list($sDogName)
{
$oSelf = self::where("dog_name", $sDogName)->get();
return self::_filterList($oSelf);
}
// public static function _limit($sDogName, $iSkip, $iTake)
// {
// $oSelf = self::where("dog_name", $sDogName)->skip($iSkip)->take($iTake)->get();
//
// return self::__filterList($oSelf);
// }
public static function _filter($oOption)
{
$arr = [];
$arr[$oOption->dog_name."#".$oOption->k] = $oOption->v;
return $arr;
}
public static function _filterList($oOption)
{
$arr = [];
foreach ($oOption as $oOptionRow) {
$arr[] = self::_filter($oOptionRow);
}
return $arr;
}
}

24
app/Models/DoveSite.php Executable file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DoveSite extends Model
{
protected $table = 'dove_site';
public $timestamps = false;
public static function _getMap()
{
$oSelf = self::all();
$aMap = [];
foreach ($oSelf as $oSelfRow) {
$aMap[$oSelfRow["code"]] = $oSelfRow["www"].$oSelfRow["api_path"];
}
return $aMap;
}
}

12
app/Models/FreenodePool.php Executable file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class FreenodePool extends Model
{
protected $table = 'freenode_pool';
public $timestamps = false;
}

11
app/Models/Hook.php Executable file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Hook extends Model
{
protected $table = 'hook';
public $timestamps = false;
}

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

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class HttpQueue extends Model
{
protected $table = 'http_queue';
public $timestamps = false;
}

47
app/Models/Option.php Executable file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Option extends Model
{
protected $table = 'option';
public $timestamps = false;
protected $primaryKey = 'k';
public $incrementing = false;
protected $keyType = 'string';
public static function _hit($k)
{
$v = self::where("k", $k)->first()?->v;
return $v;
}
public static function _list()
{
$oSelf = self::get();
return self::_filterList($oSelf);
}
public static function _filter($oOption)
{
$arr = [];
$arr[$oOption->k] = $oOption->v;
return $arr;
}
public static function _filterList($oOption)
{
$arr = [];
foreach ($oOption as $oOptionRow) {
$arr[] = self::_filter($oOptionRow);
}
return $arr;
}
}

12
app/Models/SiteMap.php Executable file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SiteMap extends Model
{
protected $table = 'site_map';
public $timestamps = false;
}

12
app/Models/SitePathMap.php Executable file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SitePathMap extends Model
{
protected $table = 'site_path_map';
public $timestamps = false;
}

37
app/Models/TelegramKey.php Executable file
View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
class TelegramKey extends Model
{
protected $table = 'telegram_key';
public $timestamps = false;
public static function botTokenByCode($sBotCode)
{
$sBotToken = self::where("type", "bot_token")->where("key", $sBotCode)->first()?->value;
if (!$sBotToken) {
// TeleSlave::notify()->send("未映射botCode".$sBotCode."使用默认base。");
$sBotToken = self::where("type", "bot_token")->where("key", "base")->first()?->value;
}
return $sBotToken;
}
public static function chatIdByCode($sChatCode)
{
$iChatId = self::where("type", "chat_id")->where("key", $sChatCode)->first()?->value;
if (!$iChatId) {
// TeleSlave::notify()->send("未映射chatCode".$sChatCode."使用默认base。");
$iChatId = self::where("type", "chat_id")->where("key", "base")->first()?->value;
}
return (int) $iChatId;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}

122
app/Services/Cron.php Executable file
View File

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Services\Cron\HttpQueue as CronHttpQueue;
use App\Services\Cron\Book as CronBook;
use App\Services\Cron\FreenodeCrawl as CronFreenodeCrawl;
use App\Services\Cron\FreenodePool as CronFreenodePool;
use App\Services\Cron\FreenodeFile as CronFreenodeFile;
use App\Services\Cron\FreenodeMerge as CronFreenodeMerge;
use App\Services\Cron\FreenodeLog as CronFreenodeLog;
use App\Services\Cron\FreenodeSync as CronFreenodeSync;
use App\Services\Cron\ArticleSchema as CronArticleSchema;
use App\Services\Cron\FnTgPublish as CronFnTgPublish;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
final class Cron
{
public bool $bIsTest = false;
public ?int $iTestH = null;
public ?int $iTestI = null;
public ?int $iTestS = null;
public function run(): void
{
try {
$h = (int) date("H");
$i = (int) date("i");
$s = (int) date("s");
if ($this->bIsTest) {
$h = (int) ($this->iTestH ?? $h);
$i = (int) ($this->iTestI ?? $i);
$s = (int) ($this->iTestS ?? $s);
}
echo "cron开始执行 \n";
if ($h % 8 === 0 && $i === 3) {
CronBook::randbox_notify();
}
if ($h % 6 === 0 && $i === 3) {
CronFreenodeCrawl::run();
CronFreenodePool::save();
CronFreenodeMerge::save();
CronFreenodeMerge::baseToSite();
CronFreenodeSync::run();
}
// if ($i === 0) {
// // ReportSender::handle();
// }
if ($h == 5 && $i === 3) {
CronFreenodeFile::clear();
}
// if ($h == 15 && $i == 3) {
//// CronFreenodeLog::fromLast();
// }
if ($h == 11 && $i == 3) { // 中午12点03发布fn的tg
CronFnTgPublish::run();
}
if ($i % 3 === 0) {
CronArticleSchema::cacheTgSave();
CronArticleSchema::pushToSlave();
CronHttpQueue::pop();
}
} catch (\Throwable $e) {
$sMsg = "";
$sMsg .= "--- cron 异常 ---\n";
$sMsg .= "类型: " . get_class($e) . "\n";
$sMsg .= "信息: " . $e->getMessage() . "\n";
$sMsg .= "文件: " . $e->getFile() . " 在第 " . $e->getLine() . "\n";
$sMsg .= "代码跟踪:\n" . $e->getTraceAsString() . "\n";
$sMsg .= "-----------------------------\n";
echo $sMsg;
$sMsg = "";
$sMsg .= "--- cron 异常 ---\n";
$sMsg .= "类型: " . get_class($e) . "\n";
$sMsg .= "信息: " . $e->getMessage() . "\n";
$sMsg .= "文件: " . $e->getFile() . " 在第 " . $e->getLine() . "\n";
TeleSlave::fail()->send($sMsg);
}
}
public function setTestH(int $h): void
{
$this->enableTestMode();
$this->iTestH = $h;
}
public function setTestI(int $i): void
{
$this->enableTestMode();
$this->iTestI = $i;
}
public function setTestS(int $s): void
{
$this->enableTestMode();
$this->iTestS = $s;
}
public function enableTestMode(bool $enable = true): void
{
$this->bIsTest = $enable;
}
}

View File

@ -0,0 +1,299 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\CrawlFreenode as ModelCrawlFreenode;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
//use Symfony\Component\Yaml\Yaml;
final class FreenodeCrawl
{
// 自有系
// 必然获取到但是密码会变暂定每天8点变意味着8袋内03分获取的是新密码
private static function getFile___my($aFrom)
{
$aFileContent = [];
foreach ($aFrom["url_file"] as $k => $sFileName) {
$sUrlSend = "https://".$aFrom["url_base"].$aFrom["url_node"].$sFileName;
$sFileContent = self::curl($sUrlSend) ?? "";
if ($sFileContent) {
$aFileContent[] = [
"type" => $k,
"file_name" => $sFileName,
"file_content" => $sFileContent
];
}
}
return $aFileContent;
}
// 通用系有就拿没有就不拿暂时没有date
private static function getFile__common($aFrom)
{
$aFileContent = [];
foreach ($aFrom["url_file"] as $k => $v) {
$sFileName = $v;
$sUrlSend = "https://".$aFrom["url_base"].$aFrom["url_node"].$sFileName;
$sFileContent = self::curl($sUrlSend) ?? "";
if (!$sFileContent) {
return [];
}
if ($k == "v2ray") {
$sFileContent = base64_decode($sFileContent);
}
if ($sFileContent) {
$aFileContent[] = [
"type" => $k,
"file_name" => $sFileName,
"file_content" => $sFileContent
];
}
}
return $aFileContent;
}
// stairnode系
// ripaojiedian系stairnode本源
// 这个可能会没有,没有就不更新
private static function getFile__ripaojiedian($aFrom)
{
$aFileContent = [];
foreach ($aFrom["url_file"] as $k => $v) {
$sFileName = str_replace("{{Ymd}}", date("Ymd"), $v);
$sUrlSend = "https://".$aFrom["url_base"].$aFrom["url_node"].$sFileName;
$sFileContent = self::curl($sUrlSend) ?? "";
// tomd($sUrlSend);
// tomd($sFileContent, 1);
if (!$sFileContent) {
return [];
}
if ($k == "v2ray") {
$sFileContent = base64_decode($sFileContent);
}
if ($sFileContent) {
$aFileContent[] = [
"type" => $k,
"file_name" => $sFileName,
"file_content" => $sFileContent
];
}
}
return $aFileContent;
}
// v2rayshare系
// 这必然有,和下一天一致说明没更新(但是好像改了,现在它是一次生成很多天)
private static function getFile__v2rayshare($aFrom)
{
$aFileContent = [];
$sTime = time();
$sTimeNext = strtotime("+1 day", $sTime);
$sUrlBase = $aFrom["url_base"];
$sUrlNode = $aFrom["url_node"];
$sUrlNode = str_replace("{{Y}}", date("Y"), $sUrlNode);
$sUrlNode = str_replace("{{m}}", date("m"), $sUrlNode);
$sUrlNodeNext = str_replace("{{Y}}", date("Y", $sTimeNext), $sUrlNode);
$sUrlNodeNext = str_replace("{{m}}", date("m", $sTimeNext), $sUrlNode);
foreach ($aFrom["url_file"] as $k => $v) {
$sUrlFile = str_replace("{{Ymd}}", date("Ymd"), $v);
$sUrlFileNext = str_replace("{{Ymd}}", date("Ymd"), $v);
$sHouzhui = ".".$k;
if ($k == "v2ray") {
$sHouzhui = ".txt";
} else if ($k == "clash") {
$sHouzhui = ".yaml";
}
$sFileName = date("Ymd")."-".$k.$sHouzhui;
$sUrlSend = "https://".$sUrlBase.$sUrlNode.$sUrlFile;
$sUrlSendNext = "https://".$sUrlBase.$sUrlNodeNext.$sUrlFileNext;
$sFileContent = self::curl($sUrlSend) ?? "";
// $sFileContentNext = self::curl($sUrlSendNext) ?? "";
if (!$sFileContent) {
return [];
}
if ($k == "v2ray") {
$sFileContent = base64_decode($sFileContent);
}
$sFileContentMd5 = md5($sFileContent);
// $sFileContentMd5Next = md5($sFileContentNext);
// if ($sFileContentMd5 != $sFileContentMd5Next) {
$aFileContent[] = [
"type" => $k,
"file_name" => $sFileName,
"file_content" => $sFileContent
];
// }
}
return $aFileContent;
}
private static function fileRealUpd($iId, $sType, $sFileName)
{
$oFrom = ModelCrawlFreenode::where("id", $iId)->first();
$aFileReal = json_decode($oFrom->file_real ?? "[]", true);
$aFileReal[$sType] = $sFileName;
$oFrom->file_real = $aFileReal;
return $oFrom->save();
}
public static function run()
{
echo "\nCrawlFreenode::run - 开始\n";
$sDirBase = $_ENV["dir_base"];
$aFromList = ModelCrawlFreenode::where("status", 1)->get()->toArray();
foreach ($aFromList as $aFrom) {
$aFrom["url_file"] = json_decode($aFrom["url_file"], true);
$sMethod = "getFile__".$aFrom["group"];
$aFileList = self::$sMethod($aFrom); // 没有就返回空数组所以不用判断直接foreach
if (empty($aFileList)) {
$sMsg = " - CrawlFreenode爬from返回空 -> ".json_encode($aFrom)." \n";
echo $sMsg;
TeleSlave::log()->send($sMsg);
}
foreach ($aFileList as $sFileRow) {
$sType = $sFileRow["type"];
$sHouzhui = "";
if ($sType == "v2ray") {
$sHouzhui = ".txt";
} else if ($sType == "clash") {
$sHouzhui = ".yaml";
}
$sFileName = date("Ymd")."-".$sType.$sHouzhui;
$sFileContent = $sFileRow["file_content"];
$sDir = $sDirBase."public/freenode/from/".$aFrom["name"];
// tomd($sDir, 1);
$sPath = $sDir."/".$sFileName;
if (!is_dir($sDir)) {
mkdir($sDir, 0777, true);
}
// if ($sType == "v2ray") {
// $sFileContent = base64_decode($sFileContent);
// }
// file_put_contents($sPath, $sFileContent);
self::save_utf8_file($sPath, $sFileContent);
self::fileRealUpd($aFrom["id"], $sType, $sFileName);
echo "- ".$aFrom["name"]."::".$sFileName."\n";
}
}
}
private static function curl($sUrl)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $sUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回内容而不是直接输出
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 如果是 https可以关闭证书验证测试用
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 关闭主机名验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 关闭主机名验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$sContent = curl_exec($ch);
if ($sContent === false) {
$error = curl_error($ch);
$errno = curl_errno($ch);
$sMsg = "cURL error ({$errno}): {$error}";
echo $sMsg;
TeleSlave::log()->send($sMsg);
}
curl_close($ch);
return $sContent;
}
private static function fileGet($sUrl)
{
$sContent = file_get_contents($sUrl);
return $sContent;
}
private static function save_utf8_file($filename, $content) {
// 自动检测编码
$encoding = mb_detect_encoding(
$content,
['UTF-8', 'GBK', 'BIG5', 'ISO-8859-1'],
true
);
// 如果不是 UTF-8就转成 UTF-8
if ($encoding !== 'UTF-8') {
$content = mb_convert_encoding($content, 'UTF-8', $encoding);
}
// 确保文件头是 UTF-8可选
$content = "\xEF\xBB\xBF" . $content; // 如果需要 BOM可以加上
file_put_contents($filename, $content);
@chmod($filename, 0777);
return true;
}
}

View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\FreenodePool as ModelFreenodePool;
use App\Models\CrawlFreenode as ModelCrawlFreenode;
use App\Models\SiteMap as ModelSiteMap;
use App\Models\Option as ModelOption;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
final class FreenodeFile
{
private static $aBaseProtocol = [
"clash",
"v2ray"
];
public static function clear()
{
echo "\nFreenodeFile::clear - 开始\n";
self::clear_from();
self::clear_merge__fn_a();
}
public static function clear_merge__fn_a()
{
$sDirBase = $_ENV["dir_base"];
$sPrevDay = 30;
$sTimePrevX = strtotime("-$sPrevDay days");
$sDatePrevX = date("Y-m-d", $sTimePrevX);
$aFnSiteList = ModelSiteMap::where("group_code", "fn_a")->pluck("code")->toArray();
$aFnSiteList[] = "base";
$sFnClient = ModelOption::_hit("freenode_client-fn_a");
$aFnClient = explode(",", $sFnClient);
foreach ($aFnSiteList as $sFnSiteName) {
foreach ($aFnClient as $sClient) {
$sFileDir = $sDirBase."public/freenode/merge/".$sFnSiteName."/".$sClient;
foreach (scandir($sFileDir) as $sFileName) {
if ($sFileName === '.' || $sFileName == '..') continue;
$sFilePath = $sFileDir.'/'.$sFileName;
if (preg_match('/(\d{4}-\d{2}-\d{2})/', $sFileName, $matches)) {
$sDateCode = $matches[1];
$sFileDate = \DateTime::createFromFormat('Y-m-d', $sDateCode);
if ($sFileDate && $sFileDate->getTimestamp() < $sTimePrevX) {
if (unlink($sFilePath)) {
echo " - 已删除: $sFilePath\n";
} else {
echo " - 删除失败: $sFilePath\n";
}
}
}
}
echo " - fn的merge里的".$sFnSiteName."里的".$sClient."的file清理完成 \n";
}
}
}
private static function clear_from()
{
$sDirBase = $_ENV["dir_base"];
$sPrevDay = 30;
$sTimePrevX = strtotime("-$sPrevDay days");
$sDateCodePrevX = date("Ymd", $sTimePrevX);
$oFromList = ModelCrawlFreenode::where("status", 1)->get();
foreach ($oFromList as $oFrom) {
$aFileLast = json_decode($oFrom->file_real, true);
$sFileDir = $sDirBase."public/freenode/from/".$oFrom->name;
foreach (scandir($sFileDir) as $sFileName) {
if ($sFileName === '.' || $sFileName == '..') continue;
$sFilePath = $sFileDir.'/'.$sFileName;
if (preg_match('/(\d{8})/', $sFileName, $matches)) {
$sDateCode = $matches[1]; // 提取到的日期字符串
$sFileDate = \DateTime::createFromFormat('Ymd', $sDateCode);
if ($sFileDate && $sFileDate->getTimestamp() < $sTimePrevX) {
if (unlink($sFilePath)) {
echo " - 已删除: $sFilePath\n";
} else {
echo " - 删除失败: $sFilePath\n";
}
}
}
}
echo " - fn的from的".$oFrom->name."里的file清理完成 \n";
}
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
//use App\Models\CrawlFreenode as ModelCrawlFreenode;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Models\CrawlFreenode as ModelCrawlFreenode;
//use Symfony\Component\Yaml\Yaml;
final class FreenodeLog
{
public static function fromLast()
{
$oFrom = ModelCrawlFreenode::where("status", 1)->get();
tomd($oFrom->toArray());
exit;
}
}

View File

@ -0,0 +1,423 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\CrawlFreenode as ModelCrawlFreenode;
use App\Models\FreenodePool as ModelFreenodePool;
use App\Services\Cron\FreenodePool as CronFreenodePool;
use App\Models\SiteMap as ModelSiteMap;
use App\Models\Option as ModelOption;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use Symfony\Component\Yaml\Yaml;
final class FreenodeMerge
{
private static $aCountryOrder = [
"{{slogan_siteUrl}}",
"{{slogan_telegram}}",
"{{slogan_date}}",
"美国",
"日本",
"台湾",
"香港",
"新加坡",
];
private static $aCountryFlag = [
"美国" => "🇺🇸",
"日本" => "🇯🇵",
"台湾" => "🇹🇼",
"香港" => "🇭🇰",
"新加坡" => "🇸🇬",
"越南" => "🇻🇳",
"俄罗斯" => "🇷🇺",
"韩国" => "🇰🇷",
];
// public static function buildSogan($aNode)
// {
// // todo 广告位,这样就固定了
// }
private static function isSlogan($s)
{
if ($s == "{{slogan_siteUrl}}" || $s == "{{slogan_telegram}}" || $s == "{{slogan_date}}") {
return true;
}
return false;
}
public static function save()
{
echo "\nFreenodeFile::save - 开始 \n";
//// test
// $a = ["a", "b", "c"];
// $b = ["b", "c", "d"];
// $c = array_merge($a, $b);
// $c = array_unique($c);
// $c = array_values($c);
// tomd($c, 1);
//// test end
$sDirBase = $_ENV["dir_base"];
$sDate = date("Y-m-d");
// $sDate = "2025-12-12"; // *test
$oPool = ModelFreenodePool::whereDate("date", $sDate)->first();
$aPoolContentList = json_decode($oPool->content, true);
foreach ($aPoolContentList as $sPoolClient => $aPoolContent) {
$aNodeGroupCountry = [];
$aLastNode = [];
$aLastNodeMy = [];
foreach ($aPoolContent as $sMd5 => $aNode) {
$sFromName = $aNode["fromName"];
$sCountry = $aNode["country"];
$sProtocolType = $aNode["protocolType"];
$sConfig = $aNode["config"];
$sCountryClear = self::strReplace($sCountry, $sFromName);
$aNodeGroupCountry[$sCountryClear][] = $aNode;
if ($sFromName == "_my") {
$aLastNodeMy = $aNode;
}
$aLastNode = $aNode;
}
$aSloganNode = $aLastNode;
if ($aLastNodeMy) {
$aSloganNode = $aLastNodeMy;
}
$aNodeGroupCountry["{{slogan_siteUrl}}"][] = $aSloganNode;
$aNodeGroupCountry["{{slogan_telegram}}"][] = $aSloganNode;
$aNodeGroupCountry["{{slogan_date}}"][] = $aSloganNode;
$aNodeSortCountry = [];
foreach (self::$aCountryOrder as $sCountryOrder) {
if (array_key_exists($sCountryOrder, $aNodeGroupCountry)) {
if (self::isSlogan($sCountryOrder)) {
$aNodeSortCountry[$sCountryOrder] = [
$aNodeGroupCountry[$sCountryOrder][0] // 得到多个slogan只要第一个按理不用处理保险
];
} else {
$aNodeSortCountry[$sCountryOrder] = $aNodeGroupCountry[$sCountryOrder];
}
}
}
foreach ($aNodeGroupCountry as $sCountry => $aNode) {
if (!array_key_exists($sCountry, self::$aCountryOrder)) {
$aNodeSortCountry[$sCountry] = $aNode;
}
}
$aNodeMerge = [];
$aNodeCountryList = [];
foreach ($aNodeSortCountry as $sCountry => $aNodeList) {
$i = 1;
foreach ($aNodeList as $aNode) {
if (self::isSlogan($sCountry)) {
$aNode["country"] = $sCountry;
} else {
$aNode["country"] = $sCountry." ".sprintf('%02d', $i);
}
$aNode["country"] = $aNode["country"]." :[".$aNode["fromName"]."]:";
if ($sPoolClient == "v2ray") {
$sConfig = $aNode["config"];
if (is_array($sConfig)) {
$sConfig = json_encode($sConfig);
}
$sNode = $aNode["protocolType"]."://".$sConfig."#".urlencode($aNode["country"]);
$aNodeMerge[] = $sNode;
} else if ($sPoolClient == "clash") {
$sFlagCountry = self::countryJoinFlag($aNode["country"]);
$aNode["config"]["name"] = $sFlagCountry;
$aNodeMerge[] = $aNode["config"];
$aNodeCountryList[] = $sFlagCountry;
} else {
$aNodeMerge[] = json_encode($aNode);
}
$i++;
}
}
if ($sPoolClient == "v2ray") {
$sMerge = implode("\n", $aNodeMerge);
$sMerge = base64_encode($sMerge);
} else if ($sPoolClient == "clash") {
$sFile = $sDirBase."public/freenode/template/clash.txt";
if (file_exists($sFile) && is_readable($sFile)) {
$sClashTemplate = file_get_contents($sFile); // data
} else {
$sMsg = "clash的template不存在这不应该 \n";
TeleSlave::warn()->send($sMsg);
echo $sMsg;
continue;
}
$aClashTemplate = Yaml::parse($sClashTemplate);
$aClashTemplate["proxies"] = $aNodeMerge;
$aProxyGroupList = &$aClashTemplate["proxy-groups"];
foreach ($aProxyGroupList as &$aProxyGroup) {
if (!isset($aProxyGroup["proxies"])) {
$aProxyGroup["proxies"] = [];
}
if ($aProxyGroup["name"] == "🍃 应用净化") {
continue;
}
if ($aProxyGroup["name"] == "🛑 全球拦截") {
continue;
}
if ($aProxyGroup["name"] == "🎯 全球直连") {
continue;
}
$aProxyGroup["proxies"] = self::arrMerge($aProxyGroup["proxies"], $aNodeCountryList);
}
$sMerge = Yaml::dump($aClashTemplate, 4);
}
$sFileDir = $sDirBase."public/freenode/merge/base/".$sPoolClient;
if (!is_dir($sFileDir)) {
mkdir($sFileDir, 0777, true);
}
$sFilePath = $sFileDir."/".$sDate.".txt"; // 都是txt解析的时候另处理
file_put_contents($sFilePath, $sMerge);
@chmod($sFilePath, 0777);
echo " - base::".$sFilePath." 已保存 \n";
}
}
public static function baseToSite()
{
echo "\nFreenodeMerge::baseToSite - 开始 \n";
$sDate = date("Y-m-d");
// $sDateCode = date("Ymd");
// $sDate = "2025-12-12"; // *test
$sDirBase = $_ENV["dir_base"];
$sFnClientList = ModelOption::_hit("freenode_client-fn_a");
if (!$sFnClientList) {
throw new \Exception("未设置option的freenode_client");
}
$aFnClientList = explode(",", $sFnClientList);
$aMerge = [];
foreach ($aFnClientList as $sFnClient) {
$sMergeFilePath = $sDirBase."public/freenode/merge/base/".$sFnClient."/".$sDate.".txt";
if (file_exists($sMergeFilePath)) {
$aMerge[$sFnClient] = file_get_contents($sMergeFilePath);
} else {
throw new \Exception("baseToSite里没找到merge文件 - $sMergeFilePath");
}
}
$oSiteMap = ModelSiteMap::where("group_code", "fn_a")->get();
if ($oSiteMap->isEmpty()) {
throw new \Exception("没找到siteMap的fn_a组");
}
$aSiteMap = $oSiteMap->toArray();
$aSiteMap[] = [
"code" => "_admin",
"url" => "_admin.loc",
"name" => "_admin",
];
$iH4Rand = rand(1, 21600); // 6小时
$iH4Time = time() - $iH4Rand;
$sSloganDate = date("Y-m-d H:i:s", $iH4Time);
foreach ($aSiteMap as $aSiteMapRow) {
$aSiteConfig = json_decode($aSiteMapRow["config"] ?? "", true);
$sSiteConfigTelegram = $aSiteConfig["telegram"] ?? "";
$sSiteConfigSloganUrl = $aSiteConfig["freenode_slogan"]["siteUrl"] ?? "";
$sSiteConfigSloganTelegram = $aSiteConfig["freenode_slogan"]["telegram"] ?? "";
if ($sSiteConfigSloganUrl) {
$sSlogan_siteUrl = str_replace('{{siteUrl}}', $aSiteMapRow["url"], $sSiteConfigSloganUrl);
} else {
$sSlogan_siteUrl = "美国".uniqid();
}
if ($sSiteConfigSloganTelegram) {
$sSlogan_telegram = str_replace('{{telegram}}', '@'.$sSiteConfigTelegram, $sSiteConfigSloganTelegram);
} else {
$sSlogan_telegram = "美国".uniqid();
}
foreach ($aMerge as $sClient => $sFeedContent) {
$sFileSaveDir = $sDirBase."public/freenode/merge/".$aSiteMapRow["code"]."/".$sClient;
if (!is_dir($sFileSaveDir)) {
mkdir($sFileSaveDir, 0777, true);
}
$sFileSavePath = $sFileSaveDir."/".$sDate.".txt";
if ($sClient == "v2ray") {
$sFeedContent = self::v2rayFeedToStr($sFeedContent);
}
$sFeedContent = str_replace('{{slogan_siteUrl}}', $sSlogan_siteUrl, $sFeedContent);
$sFeedContent = str_replace('{{slogan_telegram}}', $sSlogan_telegram, $sFeedContent);
$sFeedContent = str_replace('{{slogan_date}}', "更新于:".$sSloganDate, $sFeedContent);
if ($aSiteMapRow["code"] !== "_admin") {
$sFeedContent = preg_replace('/ :\[.*?\]:/su', '', $sFeedContent);
}
if ($sClient == "v2ray") {
$sFeedContent = self::v2rayFeedByStr($sFeedContent);
}
file_put_contents($sFileSavePath, $sFeedContent);
@chmod($sFileSavePath, 0777);
echo " - fn的".$aSiteMapRow["code"]."{$sClient}{$sDate} 保存成功 \n";
}
}
return true;
}
public static function v2rayFeedByStr($s)
{
$a = explode("\n", $s);
$aFeed = [];
foreach ($a as $sConfig) {
if (!$sConfig) {continue;}
$aTmp = CronFreenodePool::v2ray_chai($sConfig);
$sConfig = $aTmp[0];
$sName = $aTmp[1];
$sName = rawurlencode($sName);
$aFeed[] = $sConfig."#".$sName;
}
$sFeed = implode("\n", $aFeed);
$sFeed = base64_encode($sFeed);
return $sFeed;
}
public static function v2rayFeedToStr($sV2rayFeed)
{
$sV2rayFeed = base64_decode($sV2rayFeed);
$aV2rayFeed = explode("\n", $sV2rayFeed);
$aFeed = [];
foreach ($aV2rayFeed as $sConfig) {
if (!$sConfig) {continue;}
$aTmp = CronFreenodePool::v2ray_chai($sConfig);
$sConfig = $aTmp[0];
$sName = $aTmp[1] ?? "美国"; // 没得到名字默认美国
$sName = urldecode($sName);
$aFeed[] = $sConfig."#".$sName;
}
$sFeed = implode("\n", $aFeed);
return $sFeed;
}
private static function arrMerge($aBase, $aNew)
{
$aMerge = array_merge($aBase, $aNew);
$aMerge = array_unique($aMerge);
$aMerge = array_values($aMerge);
return $aMerge;
}
public static function strReplace($sStr, $sFromName)
{
if (preg_match('/机场/u', $sStr)) {
tomd($sStr);
$sStr = "美国"; // 原本的slogan直接视为美国
}
if (preg_match('/频道/u', $sStr)) {
$sStr = "美国";
}
if ($sFromName == "ripaojiedian") {
$sStr = preg_replace('/\|\@ripaojiedian/', '', $sStr);
} else if ($sFromName == "stairnode") {
$sStr = preg_replace('/\|\@stairnode/', '', $sStr);
} else {
// 其他
}
// $sStr = preg_replace('/\d+/', '', $sStr); // 去数字
// $sStr = preg_replace('/[\x{1F1E6}-\x{1F1FF}]{2}\s*/u', '', $sStr); // 去国旗
// $sStr = preg_replace('/[a-zA-Z_]+/', '', $sStr); // 去下划线和字母
if (preg_match('/\p{Han}/u', $sStr)) {
$sStr = preg_replace('/[^\p{Han}]/u', '', $sStr);// 只留中文
} else { // 没有中文,再处理一下
$sStr = preg_replace('/^.*HK.*$/i', '香港', $sStr);
}
return $sStr;
}
private static function countryJoinFlag($str)
{
$aCountryFlagList = self::$aCountryFlag;
$sFlag = "";
foreach ($aCountryFlagList as $sCountryName => $sCountryFlag) {
if (str_contains($str, $sCountryName)) {
$sFlag = $sCountryFlag;
}
}
if ($sFlag) {
$str = $sFlag." ".$str;
}
return $str;
}
}

View File

@ -0,0 +1,501 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\CrawlFreenode as ModelCrawlFreenode;
use App\Models\FreenodePool as ModelFreenodePool;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use Symfony\Component\Yaml\Yaml;
final class FreenodePool
{
public static function save()
{
echo "FreenodePoll::save - 开始 \n";
$sDirBase = $_ENV["dir_base"];
$sDate = date("Y-m-d");
// $sDate = "2025-12-12"; // *test
$oPool = ModelFreenodePool::whereDate("date", $sDate)->first();
if (!$oPool) {
$oPool = new ModelFreenodePool();
$oPool->date = $sDate;
$oPool->save();
}
$oFromList = ModelCrawlFreenode::where("status", 1)->get();
$aMerge = [];
foreach ($oFromList as $oFrom) {
$aFromFileReal = json_decode($oFrom->file_real, true);
foreach ($aFromFileReal as $sFromFileRealClient => $aFromFileName) {
$sFromFilePath = $sDirBase."public/freenode/from/".$oFrom->name."/".$aFromFileName;
if (file_exists($sFromFilePath) && is_readable($sFromFilePath)) {
$sFromFileContent = file_get_contents($sFromFilePath); // data
} else {
$sMsg = "file_real不存在这不应该 -> ".$sFromFilePath." \n";
TeleSlave::warn()->send($sMsg);
echo $sMsg;
continue;
}
$aNodeFormat = [
"fromName" => $oFrom->name,
"country" => "",
"protocolType" => "",
"config" => "",
];
if ($sFromFileRealClient == "v2ray") {
$aFromFileContent = explode("\n", $sFromFileContent);
foreach ($aFromFileContent as $sNode) {
if (!$sNode) {
continue;
}
$aTmp = explode("://", $sNode, 2);
$aNodeFormat["protocolType"] = $sProtocolType = $aTmp[0]; // data
$sConfig = $aTmp[1];
if ($sProtocolType == "vmess") {
$sConfig = base64_decode($sConfig);
$aConfig = json_decode($sConfig, true);
$aNodeFormat["country"] = $sCountry = $aConfig["ps"]; // data
$aConfig["ps"] = "";
$aNodeFormat["config"] = $aConfig; // data
$sConfigMd5 = md5(json_encode($aConfig));
} else if ($sProtocolType == "ss") {
$sConfig = urldecode($sConfig);
$aTmp = self::v2ray_chai($sConfig);
$aNodeFormat["config"] = $sConfig = $aTmp[0]; // data
$aNodeFormat["country"] = $sCountry = $aTmp[1]; // data
// if ($oFrom->group == "A") { // ripaojiedian系ss协议bug修复
// $aTmp = explode("@", $sConfig);
// $qian = $aTmp[0];
// $hou = $aTmp[1];
// $qian = base64_decode($qian);
// $he = $qian."@".$hou;
// $he = base64_encode($he);
// $sConfig = $he;
// $aNodeFormat["config"] = $sConfig;
// }
$sConfigMd5 = md5($sConfig);
} else {
$sConfig = urldecode($sConfig);
$aTmp = self::v2ray_chai($sConfig);
$aNodeFormat["config"] = $sConfig = $aTmp[0]; // data
$aNodeFormat["country"] = $sCountry = $aTmp[1] ?? '美国'; // data
$sConfigMd5 = md5($sConfig);
}
$aMerge[$sFromFileRealClient][$sConfigMd5] = $aNodeFormat;
}
} else if ($sFromFileRealClient == "clash") {
$bom = pack('H*','EFBBBF');
$sFromFileContent = preg_replace("/^$bom/", '', $sFromFileContent);
$aFromFileContent = Yaml::parse($sFromFileContent);
$aFromFileContent = $aFromFileContent["proxies"];
foreach ($aFromFileContent as $aNode) {
$aNodeFormat["protocolType"] = $aNode["type"];
$aNodeFormat["country"] = $aNode["name"]; // data
$aNode["name"] = "";
$aNodeFormat["config"] = $aNode; // data
$sConfigMd5 = md5(json_encode($aNode));
$aMerge[$sFromFileRealClient][$sConfigMd5] = $aNodeFormat;
}
}
}
}
$oPool->content = $aMerge;
$oPool->save();
echo " - freenode_pool更新完成 \n";
}
// public static function contentFormat($aContentFrom, $sClientType)
// {
// $aDataMerge = [];
// foreach ($aContentFrom as $sFromName => $aContent) {
// foreach ($aContent as $mContent) {
// $aNodeData = [];
// $sProtocolType = "";
// $mConfig = "";
// $sCountry = "";
// $sConfig = "";
//
// if ($sClientType == "v2ray") {
// $aTmp = self::v2ray_chai($mContent);
// $sTmp = $aTmp[0] ?? "none";
// $sCountry = $aTmp[1] ?? "none"; // common
// $aTmp = explode("://", $sTmp, 2);
// $sProtocolType = $aTmp[0] ?? "none"; // common
// $mConfig = $aTmp[1] ?? "none"; // common
// $sConfig = $mConfig; // common
// } else if ($sClientType == "clash") {
// $sCountry = $mContent["name"]; // common
// $sProtocolType = $mContent["type"]; // common
// $mConfig = $mContent; // common
// $mConfig["name"] = "";
// $sConfig = json_encode($mContent); // common
// }
//
// $aNodeData = [
// "from" => $sFromName,
// "clientType" => $sClientType,
// "country" => $sCountry,
// "protocolType" => $sProtocolType,
// "config" => $mConfig,
// ];
//
// $sConfigMd5 = md5($sConfig);
//
// $aDataMerge[$sConfigMd5] = [$aNodeData];
//
//// tomd($mConfig);
//
//// tomd($sContent);
// }
// }
//
// tomd($aDataMerge, 1);
// }
public static function v2ray_chai($sFromContentRow)
{
$pos = strrpos($sFromContentRow, "#");
if ($pos !== false) {
$aTmp = [
substr($sFromContentRow, 0, $pos),
substr($sFromContentRow, $pos + 1)
];
} else {
$aTmp = [$sFromContentRow]; // 没有分隔符时,返回原字符串
}
return $aTmp;
}
// public static function save_old()
// {
// self::save_custom();
// self::save_v2ray();
// self::save_clash();
// }
// public static function save_clash()
// {
// $sDate = date("Y-m-d");
//
// $oPool = ModelFreenodePool::whereDate("date", $sDate)->first();
//
// if (!$oPool) {
// $oPool = new ModelFreenodePool();
// $oPool->date = $sDate;
// $oPool->save();
// }
//
// if (!$oPool->content_custom) {
// $oPool->content_clash = "[]";
// $oPool->save();
// }
//
// $aPoolContent = json_decode($oPool->content_clash, true);
//
// $oFromList = ModelCrawlFreenode::where("status", 1)->get();
//
// foreach ($oFromList as $oFrom) {
// $sFromName = $oFrom->name;
// $aFromFileReal = json_decode($oFrom->file_real, true);
// $sFromFileClash = $aFromFileReal["clash"];
//
// $sFromFilePath = "freenode/from/".$sFromName."/".$sFromFileClash;
//
// if (file_exists($sFromFilePath) && is_readable($sFromFilePath)) {
// $sFromFileContent = file_get_contents($sFromFilePath);
//
// } else {
// $sMsg = "file_real不存在这不应该 -> ".json_encode($oFrom->toArray());
// TeleSlave::warn()->send($sMsg);
// echo $sMsg;
// continue;
// }
//
// $aFromFileContent = Yaml::parse($sFromFileContent);
//
// $aFn = $aFromFileContent["proxies"];
//
// $aPoolContent[$sFromName] = $aFn;
//
// $oPool->content_clash = $aPoolContent;
// $oPool->save();
//
// echo " - {$sFromName}的clash更新完成 \n";
// }
//
// }
// public static function save_v2ray()
// {
// $sDate = date("Y-m-d");
//
// $oPool = ModelFreenodePool::whereDate("date", $sDate)->first();
//
// if (!$oPool) {
// $oPool = new ModelFreenodePool();
// $oPool->date = $sDate;
// $oPool->save();
// }
//
// if (!$oPool->content_custom) {
// $oPool->content_v2ray = "[]";
// $oPool->save();
// }
//
// $aPoolContent = json_decode($oPool->content_v2ray, true);
//
// $oFromList = ModelCrawlFreenode::where("status", 1)->get();
//
// foreach ($oFromList as $oFrom) {
// $sFromName = $oFrom->name;
// $aFromFileReal = json_decode($oFrom->file_real, true);
// $sFromFileV2ray = $aFromFileReal["v2ray"];
//
// $sFromFilePath = "freenode/from/".$sFromName."/".$sFromFileV2ray;
//
// if (file_exists($sFromFilePath) && is_readable($sFromFilePath)) {
// $sFromFileContent = file_get_contents($sFromFilePath);
//
// } else {
// $sMsg = "file_real不存在这不应该 -> ".json_encode($oFrom->toArray());
// TeleSlave::warn()->send($sMsg);
// echo $sMsg;
// continue;
// }
//
// $aFromFileContent = explode("\n", $sFromFileContent);
//
// foreach ($aFromFileContent as &$sFromFileContentRow) {
// if (!$sFromFileContentRow) {
// continue;
// }
//
//// $aTmp = explode("://", $sFromFileContentRow, 2);
//// $sVtype = $aTmp[0];
//// $sVconfig = $aTmp[1];
//// if ($sVtype == "vmess") {
//// $s = base64_decode($sVconfig);
//// tomd($s, 1);
//// }
//
// $sFromFileContentRow = urldecode($sFromFileContentRow);
// }
//
// $aPoolContent[$sFromName] = $aFromFileContent;
//
// $oPool->content_v2ray = $aPoolContent;
// $oPool->save();
//
// echo " - {$sFromName}的v2ray更新完成 \n";
// }
//
// }
public static function save_custom()
{
$sDate = date("Y-m-d");
$oPool = ModelFreenodePool::whereDate("date", $sDate)->first();
if (!$oPool) {
$oPool = new ModelFreenodePool();
$oPool->date = $sDate;
$oPool->save();
}
if (!$oPool->content_custom) {
$oPool->content_custom = "[]";
$oPool->save();
}
$aPoolContent = json_decode($oPool->content_custom, true);
$oFromList = ModelCrawlFreenode::where("status", 1)->get();
foreach ($oFromList as $oFrom) {
$sFromName = $oFrom->name;
$aFromFileReal = json_decode($oFrom->file_real, true);
$sFromFileV2ray = $aFromFileReal["v2ray"];
$sFromFilePath = "freenode/from/".$sFromName."/".$sFromFileV2ray;
if (file_exists($sFromFilePath) && is_readable($sFromFilePath)) {
$sFromFileContent = file_get_contents($sFromFilePath);
} else {
$sMsg = "file_real不存在这不应该 -> ".json_encode($oFrom->toArray());
TeleSlave::warn()->send($sMsg);
echo $sMsg;
}
$aFromFileContent = explode("\n", $sFromFileContent);
$i = 0;
$aFnNew = [];
foreach ($aFromFileContent as $sFromFileContentRow) {
if (!$sFromFileContentRow) {
continue;
}
$ss = explode("://", $sFromFileContentRow, 2);
list($sProtocolType, $sFn) = explode("://", $sFromFileContentRow, 2);
$sMethod = $sProtocolType . "ToArr";
if (method_exists(__CLASS__, $sMethod)) {
$aFn = self::$sMethod($sFn);
} else {
TeleSlave::warn()->send("未设处理方式的协议 -> ".$sFromFileContentRow);
continue;
}
$aFnNew[] = $aFn;
}
$aPoolContent[$sFromName] = $aFnNew;
$oPool->content_custom = $aPoolContent;
$oPool->save();
echo " - {$sFromName}的custom更新完成 \n";
}
}
private static function vmessToArr($sStr)
{
$sStr = base64_decode($sStr);
$a = json_decode($sStr, true);
$a["vtype"] = "vmess";
return $a;
}
private static function ssToArr($sStr)
{
// YWVzLTI1Ni1jZmI6YW1hem9uc2tyMDU@63.180.254.10:443#8%E5%85%83%E8%80%81%E7%89%8C%E4%B8%93%E7%BA%BF%E6%9C%BA%E5%9C%BA%EF%BC%9Acczzuu.top
$sStr = urldecode($sStr);
list($sPwd, $sStr) = explode("@", $sStr, 2);
$sPwd = base64_decode($sPwd);
list($sCipher, $sPwd) = explode(":", $sPwd, 2);
list($sServer, $sStr) = explode("#", $sStr, 2);
list($sServer, $sPort) = explode(":", $sServer, 2);
$aPort = explode("/?", $sPort, 2);
$sPort = $aPort[0];
$sOption = $aPort[1] ?? "";
if ($sOption) {
$aOptionList = explode(";", $sOption);
} else {
$aOptionList = [];
}
$aOptionArr = [];
foreach ($aOptionList as $sOption) {
if (!$sOption) {
continue;
}
$aTmp = explode("=", $sOption, 2);
$k = $aTmp[0] ?? "";
$v = $aTmp[1] ?? "";
$aOptionArr[$k] = $v;
}
$sName = $sStr;
$a = [
"vtype" => "ss",
"server" => $sServer,
"port" => $sPort,
"cipher" => $sCipher,
"password" => $sPwd,
"name" => $sName,
"option" => $aOptionArr,
];
return $a;
}
private static function trojanToArr($sStr)
{
// 76d630f2af6619c4a5de0ef953df3c6a@58.152.110.154:443?allowInsecure=0&sni=www.nintendogames.net#%F0%9F%87%AD%F0%9F%87%B0%20%E9%A6%99%E6%B8%AF3%7C%40stairnode
$sStr = urldecode($sStr);
$aTmp = explode("@", $sStr, 2);
$sPwd = $aTmp[0] ?? "";
$sStr = $aTmp[1] ?? "";
$aTmp = explode("#", $sStr, 2);
$sStr = $aTmp[0] ?? "";
$sName = $aTmp[1] ?? "";
$aTmp = explode("?", $sStr, 2);
$sServer = $aTmp[0] ?? "";
$sStr = $aTmp[1] ?? "";
$aTmp = explode(":", $sServer, 2);
$sServer = $aTmp[0] ?? "";
$sPort = $aTmp[1] ?? "";
$aOptionList = explode("&", $sStr, 2);
$aOptionArr = [];
foreach ($aOptionList as $sOption) {
if (!$sOption) {
continue;
}
$aTmp = explode("=", $sOption, 2);
$k = $aTmp[0] ?? "";
$v = $aTmp[1] ?? "";
$aOptionArr[$k] = $v;
}
$a = [
"vtype" => "trojan",
"name" => $sName,
"server" => $sServer,
"port" => $sPort,
"password" => $sPwd,
"option" => $aOptionArr,
];
return $a;
}
// public static function show($sDate = "")
// {
// if (!$sDate) {
// $sDate = date("Y-m-d");
// }
//
// $oPool = ModelFreenodePool::whereDate("date", $sDate)->first();
//
// if (!$oPool) {
// return "没有?";
// }
//
// return $oPool->content_custom;
// }
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\SiteMap as ModelSiteMap;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Services\TomTool\HttpV2;
final class FreenodeSync
{
static function run()
{
echo "\nFeenodeSync::run() - 开始 \n";
$sDirBase = $_ENV["dir_base"];
$oFnSiteList = ModelSiteMap::where("group_code", "fn_a")->get();
$aResultAll = [];
foreach ($oFnSiteList as $oFnSite) {
$aSiteConfig = json_decode($oFnSite->config, true);
$aSiteConfigFnClient = $aSiteConfig["freenode_client"] ?? [];
$aSiteApi = json_decode($oFnSite->api_path, true);
$sSiteApiFreenodeReceive = $aSiteApi["freenode_receive"] ?? "";
$aFnContent = [];
foreach ($aSiteConfigFnClient as $aSiteConfigFnClientRow) {
$sFilePath = $sDirBase."public/freenode/merge/".$oFnSite->code."/".$aSiteConfigFnClientRow."/".date("Y-m-d").".txt";
if (file_exists($sFilePath)) {
$aFnContent[$aSiteConfigFnClientRow] = file_get_contents($sFilePath);
} else {
$sMsg = "freenodeSync的run没有当天文件".$sFilePath;
TeleSlave::warn()->send($sMsg);
}
}
$sApiUrl = "https://".$oFnSite->url."/".$sSiteApiFreenodeReceive;
$sResult = HttpV2::make($sApiUrl, "post")->setDataA($aFnContent)->enableJsonSend()->send();
$aResult = json_decode($sResult, true);
if (!isset($aResult["is_done"]) || $aResult["is_done"] != true) {
$sMsg = "freenodeSync::同步失败 ->".$oFnSite->name."->".$sResult;
echo $sMsg;
TeleSlave::warn()->setMsgS($sMsg)->send();
continue;
}
}
echo " - ok \n";
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Models\SiteMap as ModelSiteMap;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Services\TomTool\HttpV2;
final class FreenodeWatch
{
static function run()
{
echo "\n\nFreenodeWatch::run() - 开始";
$sDirBase = $_ENV["dir_base"];
$oFnSiteList = ModelSiteMap::where("group_code", "fn_a")->get();
// foreach ()
echo "\n - ok";
}
}

91
app/Services/Cron/HttpQueue.php Executable file
View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace App\Services\Cron;
//use App\Services\Cron;
use App\Models\HttpQueue as ModelHttpQueue;
use App\Services\TomTool\Telegram\Master as TelegramMaster;
use App\Services\TomTool\Telegram\Slave as TelegramSlave;
use App\Services\TomTool\HttpV2;
use Telegram\Bot\Api;
//use Telegram\Bot\Exceptions\TelegramSDKException;
use Telegram\Bot\Exceptions\TelegramSDKException;
use Telegram\Bot\Exceptions\TelegramResponse;
final class HttpQueue
{
public static function pop()
{
echo "\nhttpQueue::pop - 开始\n";
$oHttpQueue = ModelHttpQueue::where("status", 1)->get();
foreach ($oHttpQueue as $oHttpQueueRow) {
$aArg = json_decode($oHttpQueueRow->arg, true);
$sBotCode = $aArg["sBotCode"] ?? '';
$sChatCode = $aArg["sChatCode"] ?? '';
$sBotToken = $aArg["sBotToken"] ?? '';
$iChatId = $aArg["iChatId"] ?? 0;
$sMsg = $aArg["sMsg"] ?? '';
$sPhotoUrl = $aArg["sPhotoUrl"] ?? '';
$iPhotoWidth = $aArg["iPhotoWidth"] ?? 0;
$iPhotoHeight = $aArg["iPhotoHeight"] ?? 0;
$enableMaster = $aArg["enableMaster"] ?? null;
$sType = $aArg["sType"] ?? '';
if ($enableMaster) {
$oTgSlave = TelegramSlave::start()
->enableDetail(false)
->enableMaster(true)
->enableAsync(false)
->enableSlaveQueue(false)
->setBotCode($sBotCode)
->setChatCode($sChatCode)
->setBotToken($sBotToken)
->setChatId($iChatId)
->setMsgS($sMsg)
->setPhotoUrl($sPhotoUrl, $iPhotoWidth, $iPhotoHeight)
->setType($sType)
->send();
if ($oTgSlave->isFail()) {
echo "失败从telegram_local发送到master失败\n";
var_dump($oTgSlave->getResult());
continue;
}
echo "发送到master成功\n";
} else { // 直接发送到tg必须有token和id
$oTgSlave = TelegramSlave::start()
->enableDetail(false)
->enableMaster(false)
->setBotCode($sBotCode)
->setChatCode($sChatCode)
->setBotToken($sBotToken)
->setChatId($iChatId)
->setMsgS($sMsg)
->setPhotoUrl($sPhotoUrl, $iPhotoWidth, $iPhotoHeight)
->setType($sType)
->send();
if ($oTgSlave->isFail()) {
echo "失败从telegram_local发送到tg失败\n";
var_dump($oTgSlave->getResult());
continue;
}
echo "发送到tg成功\n";
}
$oHttpQueueRow->delete();
}
echo "ok\n";
}
}

55
app/Services/HttpQueue.php Executable file
View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\HttpQueue as ModelHttpQueue;
use App\Services\TomTool\Flow;
final class HttpQueue
{
public $sName;
public $aArg = [];
public $sUrl;
public static function start(string $sName = ''): self
{
$oInstance = new self();
$oInstance->sName = $sName;
return $oInstance;
}
public function url($sUrl)
{
$this->sUrl = $sUrl;
return $this;
}
public function arg($aArg)
{
$this->aArg = $aArg;
return $this;
}
public function save(): Flow
{
$oResult = Flow::start("httpQueue");
$oModelHttpQueue = new ModelHttpQueue();
$oModelHttpQueue->name = $this->sName;
$oModelHttpQueue->url = $this->sUrl;
$oModelHttpQueue->arg = json_encode($this->aArg, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$r = $oModelHttpQueue->save();
if (!$r) {
return $oResult->fail("进入队列失败");
}
return $oResult->done("成功");
}
}

120
app/Services/SlaveMap.php Executable file
View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\SiteMap as ModelSiteMap;
use App\Models\SitePathMap as ModelSitePathMap;
use App\Models\ArticleSiteMapGroup as ModelArticleSiteMapGroup;
use App\Services\TomTool\Flow;
final class SlaveMap
{
public static function siteByGroup(string $sSiteGroupCode): array
{
$aSiteList = ModelSiteMap::where("group_code", $sSiteGroupCode)->pluck("code")->toArray();
return $aSiteList;
}
public static function siteGroupByArticleGroup(string $sArticleGoupCode)
{
$aSiteGroupList = ModelArticleSiteMapGroup::where("article_group_code", $sArticleGoupCode)->pluck("site_group_code")->toArray();
return $aSiteGroupList;
}
// public static function siteByArticleGroup(string $sArticleGoupCode)
// {
// $aSiteGroupList = self::siteGroupByArticleGroup($sArticleGoupCode);
//
// $aSiteList = ModelSiteMap::whereIn("group_code", $aSiteGroupList)->orderBy("group_code")->get()->toArray();
//
// return $aSiteList;
// }
public static function cmdListByArticleGroup(string $sArticleGoupCode): array
{
$aSiteGroupList = self::siteGroupByArticleGroup($sArticleGoupCode);
$aCmdList = [];
foreach ($aSiteGroupList as $sSiteGroupCode) {
$aSiteList = self::siteByGroup($sSiteGroupCode);
foreach ($aSiteList as $sSiteCode) {
$sCmdKey = "|{$sSiteGroupCode}|{$sSiteCode}|";
$aCmdList[] = $sCmdKey;
}
}
return $aCmdList;
}
// public static function cmdTreeByArticleGroup(string $sArticleGoupCode, $xDefaultCmd = null): array
// {
// $aSiteGroupList = self::siteGroupByArticleGroup($sArticleGoupCode);
//
// $aTree = [];
// foreach ($aSiteGroupList as $sSiteGroupCode) {
//
// $aSiteList = self::siteByGroup($sSiteGroupCode);
//
// foreach ($aSiteList as $sSiteCode) {
// $aTree[$sSiteGroupCode][$sSiteCode] = $xDefaultCmd;
// }
//
// }
//
// return $aTree;
// }
public static function sitePathByGroup(string $sSiteGroupCode, string $sPathGroupCode): Flow
{
$oFlow = Flow::start("sitePathByGroup");
$oSiteList = ModelSiteMap::where("group_code", $sSiteGroupCode)->get();
if ($oSiteList->isEmpty()) {
return $oFlow->fail("no have site");
}
$sPath = ModelSitePathMap::where("site_group_code", $sSiteGroupCode)->where("path_group_code", $sPathGroupCode)->first()?->path;
if (!$sPath) {
return $oFlow->fail("no have path");
}
$aSitePath = [];
foreach ($oSiteList as $oSite) {
$aSitePath[] = $oSite->url.$sPath;
}
return $oFlow->aData($aSitePath)->done();
}
public static function sitePathByCode(string $sSiteCode, string $sPathGroupCode): Flow
{
$oFlow = Flow::start("sitePathByCode");
$oSite = ModelSiteMap::where("code", $sSiteCode)->first();
if (!$oSite) {
return $oFlow->fail("no have site");
}
$sPath = ModelSitePathMap::where("site_group_code", $oSite->group_code)->where("path_group_code", $sPathGroupCode)->first()?->path;
if (!$sPath) {
return $oFlow->fail("no have path");
}
$sSitePath = $oSite->url.$sPath;
return $oFlow->sData($sSitePath)->done();
}
}

1052
app/Services/Tg/Hook/Dog.php Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
<?php
namespace App\Services\Tg\Hook\Mod;
//use App\Services\TomTool\Http;
//use App\Models\Article as ModelArticle;
use App\Services\TomTool\Http;
use App\Services\Tg\Http as TGHttp;
class Base {
public function __construct() {
$this->oHttp = new Http();
$this->oTGHttp = new TGHttp();
// $this->oTGHttp->sApiToken = env("telegram_token");
// $this->oTGHttp->sApiChatId = env("telegram_chatid");
}
public function toJson($aData)
{
return json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
public function eachSendTg($aData)
{
foreach ($aData as $row) {
$this->oTGHttp->sendToTG(json_encode($row, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
$this->oHttp::response(200, 'ok');
}
// protected function cmdGood($sCmd) {
//
// $sCmdNew = "";
// switch ($sCmd) {
// case "user":
// $sCmdNew = "user_name";
// break;
// case "project":
// $sCmdNew = "project_name";
// break;
// case "code":
// $sCmdNew = "sale_code";
// break;
// case "condition":
// $sCmdNew = "cron_condition";
// break;
// default:
// $sCmdNew = $sCmd;
// break;
// }
//
// return $sCmdNew;
//
// }
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Services\TomTool\Http;
use App\Services\Cron as ServicesCron;
use App\Services\Cron\HttpQueue as CronHttpQueue;
class Cron extends Base {
public function httpQueuePop()
{
$r = CronHttpQueue::pop();
return $r;
}
public function test($aParam)
{
$sType = $aParam["aOption"][1] ?? "i";
$sValue = $aParam["aArg"][1] ?? "03";
if (!$sType) {
return "需要his之一";
}
if (!$sValue) {
return "需要value";
}
if ($sValue == "?") {
return "#test__[his] [value]";
}
$oServicesCron = new ServicesCron();
$oServicesCron->setTestH('06');
$oServicesCron->setTestI('03');
$oServicesCron->setTestS('00');
if ($sType == "h") {
$oServicesCron->setTestH($sValue);
} else if ($sType == "i") {
$oServicesCron->setTestI($sValue);
} else if ($sType == "s") {
$oServicesCron->setTestS($sValue);
}
return $oServicesCron->run();
}
public function testOld($aParam)
{
$sType = $aParam["aArg"][1] ?? false;
$sValue = $aParam["aArg"][2] ?? false;
$oHttp = new Http();
$oHttp->sMethod = "get";
$oHttp->bIsJson = true;
$oHttp->sToUrl = env("APP_URL")."/cron";
if ($sType && $sValue) {
$oHttp->aData = ["k" => $sType, "v" => $sValue];
}
$r = $oHttp->send();
return $r;
}
}

243
app/Services/Tg/Hook/Mod/Dove.php Executable file
View File

@ -0,0 +1,243 @@
<?php
namespace App\Services\Tg\Hook\Mod;
//use App\Models\Hook as ModelHook;
use App\Models\DoveSite as ModelDoveSite;
use App\Services\TomTool\Http;
use App\Services\Tg\Http as TGHttp;
use App\Services\Tg\Hook\Dog;
class Dove extends Base {
private $oDog;
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelDoveSite());
$this->oDog->_setDogName("dove"); // 必须me用正常就是类名只不过后期没准改名所以就这里写死
$this->oDog->_setPrimaryKey("code"); // 可选默认id
// $this->oDog->_setStrField(["content"]); // 可选,指定长文本字段
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
public function go($aParam, $aData)
{
$oDoveSiteCheckedList = ModelDoveSite::where("dove_checked", 1)->get();
list(, $sSubCmd) = explode("$", $aData['message']['text'], 2);
$aData['message']['text'] = $sSubCmd;
$aData['is_dove'] = 1;
$aData['dove_token'] = env('dove_token');
$oTGHttp = new TGHttp();
$oHttp = new Http();
$oHttp->sMethod = "post";
$oHttp->bIsJson = true;
$r = [];
foreach ($oDoveSiteCheckedList as $row) {
$sApiUrl = "https://".$row->www.$row->api_path;
$oHttp->sToUrl = $sApiUrl;
$oHttp->aData = $aData;
$tmp = $oHttp->send();
$r = "***".$row->code."*** \n\n".$tmp;
$oTGHttp->sendToTG($r);
}
Http::response(200);
}
// public function del($aParam)
// {
// $sCode = $aParam["aArg"][1] ?? false;
//
// if (!$sCode) {
// return "要code";
// }
//
// $oDove = ModelDoveSite::where("code", $sCode)->first();
//
// if (!$oDove) {
// return "没有";
// }
//
// $oDove->delete();
//
// return $this->list();
// }
// public function add($aParam)
// {
// $sCode = $aParam["aArg"][1];
//
// $oDove = new ModelDoveSite();
//
// $oDove->code = $sCode;
//
// $oDove->save();
//
// return $this->list();
// }
// public function set($aParam)
// {
// $sCode = $aParam["aOption"][1] ?? false;
// $k = $aParam["aOption"][2] ?? false;
// $v = $aParam["aArg"][1] ?? false;
//
// if (!$sCode) {
// return "需要code";
// }
//
// $oDove = ModelDoveSite::where("code", $sCode)->first();
//
// if (!$oDove) {
// return "没有";
// }
//
// $oDove->$k = $v;
//
// $oDove->save();
//
// return $this->toJson($oDove);
// }
// public function list()
// {
// $oDove = ModelDoveSite::all();
//
// $r = $this->toJson($oDove);
//
// return $r;
// }
// public function class($aParam)
// {
// $sClass = $aParam["aArg"][1] ?? false;
//
// if ($sClass == "?") {
// return "/dove#class <class>";
// }
//
// if (!$sClass) {
// return "需要class";
// }
//
// $aParam["aOption"][1] = "class";
// $aParam["aArg"][1] = $sClass;
//
// return $this->where($aParam);
// }
// public function where($aParam)
// {
// $sField = $aParam["aOption"][1] ?? false;
// $sValue = $aParam["aArg"][1] ?? false;
//
// if ($sValue == "?") {
// return "/dove#where__<firld> <value>";
// }
//
// if (!$sField) {
// return "需要field";
// }
//
// if (!$sValue) {
// return "需要value";
// }
//
// $oDoveSite = ModelDoveSite::where($sField, $sValue)->get();
//
// if ($oDove->isEmpty()) {
// return "没找到这个";
// }
//
// return $this->toJson($oDoveSite);
// }
public function check($aParam)
{
$iSetValue = $aParam['iSetValue'] ?? 1;
$sCode = $aParam['aArg'][1];
$oDoveSite = ModelDoveSite::where("code", $sCode)->first();
if (!$oDoveSite) {
throw new \Exception("没有这个code");
}
$oDoveSite->dove_checked = $iSetValue;
$r = $oDoveSite->save();
if (!$r) {
throw new \Exception("没check上");
}
$r = $this->now();
return $r;
}
public function checkOne($aParam)
{
ModelDoveSite::query()->update(["dove_checked" => 0]);
return $this->check($aParam);
}
public function checkClear()
{
$r = ModelDoveSite::query()->update(["dove_checked" => 0]);
return $this->list();
}
public function checkUn($aParam)
{
$sCode = $aParam["aArg"][1];
$r = ModelDoveSite::where("code", $sCode)->update(["dove_checked" => 0]);
return $this->now();
}
public function now()
{
$aDoveSiteNow = ModelDoveSite::where("dove_checked", 1)->get()->toArray();
// if ($aDoveSiteNow) {
// $r = "";
// foreach ($aDoveSiteNow as $row) {
// $r .= "\n";
// $r .= $row['code']."::".$row['www']."::".$row['class'];
// }
// } else {
// $r = "没有";
// }
if (!$aDoveSiteNow) {
return "没有";
}
$r = $this->toJson($aDoveSiteNow);
return $r;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\DoveSite as ModelDoveSite;
class DoveSite extends Base {
public function add($aParam)
{
$sCode = $aParam['aArg'][1];
$oDoveSite = new ModelDoveSite();
$oDoveSite->code = $sCode;
$r = $oDoveSite->save();
return $r;
}
public function del($aParam)
{
$sCode = $aParam['aArg'][1];
$r = ModelDoveSite::where("code", $sCode)->delete();
return $r;
}
public function limit($aParam)
{
$iLimit = $aParam['aArg'][1];
$oDoveSite = ModelDoveSite::orderBy("code")->limit($iLimit)->get();
$this->eachSendTg($oDoveSite);
}
public function like($aParam)
{
$sField = $aParam['aOption'][1];
$sValue = $aParam['aArg'][1];
$oDoveSiteLike = ModelDoveSite::where($sField, "like", "%".$sValue."%")->get();
$this->eachSendTg($oDoveSiteLike);
}
public function Set($aParam)
{
$sCode = $aParam['aOption'][1];
$sField = $aParam['aOption'][2];
$sValue = $aParam['aArg'][1];
$oDoveSite = ModelDoveSite::where("code", $sCode)->first();
if (!$oDoveSite) {
throw new \Exception("没有这个code");
}
$oDoveSite->$sField = $sValue;
$r = $oDoveSite->save();
return $r;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Services\Tg\Hook\Dog;
//use App\Services\TomTool\Flow;
use App\Services\TomTool\ThrowableHandler;
//use App\Services\Cron\FreenodeMerge as CronFreenodeMerge;
use App\Models\FreenodePool as ModelFreenodePool;
use App\Services\TomTool\HttpV2;
use App\Services\TomTool\Map;
use App\Models\SiteMap as ModelSiteMap;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
class Freenode extends Base {
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelFreenodePool());
$this->oDog->_setDogName("Freenode");
$this->oDog->_setJsonField(["content"]);
// $this->oDog->_setStrField(["content"]);
$this->aUpdate = $aUpdate;
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
public function show($aParam)
{
$sSiteCode = $aParam["aArg"][1] ?? "cfn";
$sClient = $aParam["aOption"][1] ?? "clash";
if ($sSiteCode === "?") {
return "/show#__[client:clash] [siteCodeShort]";
}
$oSite = ModelSiteMap::where("code_short", $sSiteCode)->first();
if (!$oSite) {
return "没有这个site";
}
$sClientSfx = Map::fnClientToSfx($sClient);
$sFileName = date("Ymd")."-".$sClient.".".$sClientSfx;
$sFileUrl = "https://".$oSite->url."/sub/".$sFileName;
// $sFileUrl = "https://clashv2rayfree.com/sub/".$sFileName; // test
$oFlow = TeleSlave::start("admin", "admin")
->enableSlaveQueue(false)
->enableAsync(false)
->enableDetail(false)
->setFileUrl($sFileUrl, $sSiteCode."".$sClient.".txt")
->send();
if ($oFlow->isFail()) {
var_dump($oFlow->getResult());
exit;
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Services\Tg\Hook\Dog;
use App\Services\TomTool\Flow;
use App\Services\TomTool\ThrowableHandler;
//use App\Services\Cron\FreenodeMerge as CronFreenodeMerge;
//use App\Models\FreenodePool as ModelFreenodePool;
use App\Models\CrawlFreenode as ModelCrawlFreenode;
use App\Services\TomTool\HttpV2;
class FreenodeFrom extends Base {
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelCrawlFreenode());
$this->oDog->_setDogName("crawlFreenode");
$this->oDog->_setJsonField(["url_file", "file_real"]);
// $this->oDog->_setStrField(["content"]);
$this->aUpdate = $aUpdate;
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,48 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\Hook as ModelHook;
//use App\Models\SiteList as ModelSiteList;
//use App\Services\TomTool\Http;
class Hook extends Base {
public function set($aParam, $aData)
{
$sKey = $aParam['aArg'][1];
$iFromId = $aData["message"]["from"]["id"];
ModelHook::where("tg_user_id", $iFromId)->truncate();
$oModelHook = new ModelHook();
$oModelHook->key = $sKey;
$oModelHook->tg_user_id = $iFromId;
$r = $oModelHook->save();
return $this->now($aParam, $aData);
}
public function un($aParam, $aData)
{
$iFromId = $aData["message"]["from"]["id"];
ModelHook::where("tg_user_id", $iFromId)->truncate();
return $this->now($aParam, $aData);
}
public function now($aParam, $aData)
{
$iFromId = $aData["message"]["from"]["id"];
$sHookKey = ModelHook::where("tg_user_id", $iFromId)->first()?->key;
if ($sHookKey) {
$r = $sHookKey;
} else {
$r = "hook未挂载";
}
return $r;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Services\Tg\Hook\Dog;
use App\Models\HttpQueue as ModelHttpQueue;
class HttpQueue extends Base {
private $oDog;
// private $aUpdate;
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelHttpQueue());
$this->oDog->_setDogName("HttpQueue");
// $this->oDog->_setPrimaryKey("code"); // 可选默认id
// $this->oDog->_setJsonField(["config"]);
// $this->oDog->_setStrField(["content"]);
// $this->aUpdate = $aUpdate;
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\Option as ModelOption;
use App\Services\Tg\Hook\Dog;
class Option extends Base {
private $oDog;
public function __construct($aUpdate = [])
{
$this->oDog = new Dog(new ModelOption());
$this->oDog->_setPrimaryKey("k"); // 可选默认id
$this->oDog->_setDogName("option"); // 必须
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
// public function Pause($aParam)
// {
// $v = $aParam['aArg'][1];
//
// // 获取第一个匹配的选项
// $oOption = ModelOption::where("type", "pause")->first();
//
// // 检查是否找到了选项
// if ($oOption) {
// $oOption->value = $v;
//
// // 保存并返回结果
// return json_encode($oOption->save());
// }
//
// // 如果没有找到选项,返回一个错误信息
// return json_encode(['error' => 'Option not found']);
// }
//
// public function List()
// {
// $aOptionList = ModelOption::all()->toArray();
//
// return json_encode($aOptionList, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
// }
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Services\Tg\Hook\Dog;
use App\Models\SiteMap as ModelSiteMap;
class SiteMap extends Base {
private $oDog;
// private $aUpdate;
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelSiteMap());
$this->oDog->_setDogName("siteMap");
$this->oDog->_setPrimaryKey("code"); // 可选默认id
$this->oDog->_setJsonField(["config", "api_path"]);
// $this->oDog->_setStrField(["content"]);
// $this->aUpdate = $aUpdate;
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\SitePathMap as ModelSitePathMap;
use App\Services\Tg\Hook\Dog;
class SitePathMap extends Base {
private $oDog;
// private $aUpdate;
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelSitePathMap());
$this->oDog->_setDogName("sitePathMap");
// $this->oDog->_setPrimaryKey("code"); // 可选默认id
// $this->oDog->_setJsonField(["use_from"]);
// $this->oDog->_setStrField(["content"]);
// $this->aUpdate = $aUpdate;
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\TelegramKey as ModelTelegramKey;
//use App\Models\ArticleCache as ModelArticleCache;
//use App\Models\ArticleCacheTime as ModelArticleCacheTime;
use App\Services\Tg\Hook\Dog;
//use App\Services\Article as ServiceArticle;
//use App\Services\TomTool\Flow;
//use App\Services\HttpQueue;
//use App\Services\TomTool\TelegramVdb;
class TelegramKey extends Base {
private $oDog;
public function __construct($aUpdate)
{
$this->oDog = new Dog(new ModelTelegramKey());
$this->oDog->_setDogName("telegram_key"); // 必须me用正常就是类名只不过后期没准改名所以就这里写死要在最前面
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
}

108
app/Services/Tg/Http.php Executable file
View File

@ -0,0 +1,108 @@
<?php
namespace App\Services\Tg;
use App\Services\TomTool\Http as TomHttp;
class Http {
public $sApiToken;
public $sApiChatId;
function __construct()
{
$this->sApiToken = env('telegram_token');
$this->sApiChatId = env('telegram_chatid');
}
public function send($sMsg = "msg空", $aUpdate = [])
{
$bSendTg = true;
$sShell = $aUpdate["shell"] ?? false;
$bIsDove = $aUpdate["is_dove"] ?? false;
if ($sShell == "cmd") {
$bSendTg = false;
}
if ($bIsDove == true) {
$bSendTg = false;
}
if (env("is_loc") == true) {
$bSendTg = false;
}
if (is_array($sMsg)) {
if ($bSendTg) {
foreach ($sMsg as $sMsgRow) {
$this->sendTg($sMsgRow);
}
} else {
echo json_encode($sMsg, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
} else {
if ($bSendTg) {
$this->sendTg($sMsg);
} else {
echo $sMsg;
}
}
if ($bSendTg) {
TomHttp::response(200);
} else {
exit;
}
}
private function sendTg($sMsg)
{
$oHttp = new TomHttp();
$oHttp->sMethod = "post";
$oHttp->sToUrl = "https://api.telegram.org/bot$this->sApiToken/sendMessage";
// $oHttp->bIsJson = true;
$oHttp->aData = [
"chat_id" => $this->sApiChatId,
"text" => $sMsg
];
$oHttp->send();
}
public function sendToTG($sMsg, $aUpdate = [])
{
if (isset($aUpdate["shell"]) && $aUpdate["shell"] == "cmd") {
echo $sMsg;
} else if (isset($aUpdate["is_dove"]) && $aUpdate["is_dove"] == true) {
echo $sMsg;
} else {
if (env("is_loc") == true) {
echo $sMsg;
} else {
$oHttp = new TomHttp();
$oHttp->sMethod = "post";
$oHttp->sToUrl = "https://api.telegram.org/bot$this->sApiToken/sendMessage";
// $oHttp->bIsJson = true;
$oHttp->aData = [
"chat_id" => $this->sApiChatId,
"text" => $sMsg
];
$oHttp->send();
}
}
}
}

415
app/Services/TomTool/Flow.php Executable file
View File

@ -0,0 +1,415 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
use App\Services\TomTool\TelegramVdb;
/**
默认:
name:none
isDone:false
new:
$oResult = Flow::make("name");
快速:
Flow::make("name")->fail("kkkkkkk");
进阶:
Flow::make("name")->noDone()->msg("哟哟哟哟哟哟")->sendTg()->fetch();
业务:
step()
msg()
data()
aData()
jData()
sData()
code()
put()
merge()
toDone()
noDone()
step("start")
action:
reset()
done(?$msg)
fail(?$msg, ?$aParentResult)
sendTg() 伪action
fetch() or get()
fetchAll() or geAll()
set
enableTg();
parent($aParentResult);
with:
withTraceId();
withTimestamp();
withStep();
判断:
::isDone($aResult);
::isFail($aResult);
*/
class Flow
{
protected array $aStep = [];
protected array $aParent = [];
protected bool $isToTg = false;
protected bool $isToTgOne = false;
protected array $aResult = [
'isDone' => false,
];
protected string $sName;
private bool $isChain = false;
public function __construct(string $name = 'none')
{
$this->sName = $name;
$this->aStep[] = "{$this->sName}::start";
}
// new
public static function make(string $name = 'none'): self
{
return new self($name);
}
// new
public static function start($sName = 'none')
{
$oInstance = new self($sName);
$oInstance->isChain = true;
return $oInstance;
}
// option
public function enableTg(bool $flag = true): self
{
$this->isToTg = $flag;
return $this;
}
// option
public function enableTgOne(bool $flag = true): self
{
$this->enableTg(true);
$this->isToTgOne = $flag;
return $this;
}
// option
public function parent(array $aParentResult): self
{
$this->aParent = $aParentResult;
return $this;
}
// data
public function step(string $step): self
{
$this->aStep[] = "{$this->sName}::{$step}";
return $this;
}
// data
public function msg($msg = "msg未设置"): self
{
if (is_array($msg)) {
$msg = json_encode($msg);
}
$this->put('sMsg', $msg);
return $this;
}
// data
public function aData(array $data): self
{
$this->put('aData', $data);
return $this;
}
// data
public function aDataPut($k, $v)
{
$this->aResult["aData"][$k] = $v;
return $this;
}
// data
public function sData(string $data): self
{
$this->put('sData', $data);
return $this;
}
// data
public function code(string $code): self
{
$this->put('sCode', $code);
return $this;
}
// data
public function put(string $key, mixed $value, $sDataKey = ''): self
{
if ($sDataKey) {
$this->aResult[$sDataKey][$key] = $value;
} else {
$this->aResult[$key] = $value;
}
return $this;
}
// data
public function merge(array $data): self
{
foreach ($data as $k => $v) {
$this->put($k, $v);
}
return $this;
}
// data
public function setDone(bool $b = true): self
{
$this->aResult['isDone'] = $b;
return $this;
}
// data
public function setFail(string $step = ''): self
{
if ($step) {
$this->step($step);
}
$this->aResult['isDone'] = false;
return $this;
}
// with
public function withTraceId(string $id = null): self
{
$this->aResult['sTraceId'] = $id ?? uniqid('trace_', true);
return $this;
}
// with
public function withTimestamp(): self
{
$this->aResult['sTimestamp'] = (new \DateTime())->format(\DateTime::ATOM);
return $this;
}
// with
public function withStep(bool $flag = true): self
{
if ($flag) {
$this->withParentStep();
$this->aResult['aStep'] = $this->aStep;
} else {
unset($this->aResult['aStep']);
}
return $this;
}
// action
public function reset(): self
{
$this->aStep = [$this->sName . '::start'];
$this->aParent = [];
$this->isToTg = false;
$this->isToTgOne = false;
$this->aResult = ['isDone' => false];
return $this;
}
// action
public function done(mixed $msg = null)
{
$this->setDone();
if ($msg) {
$this->msg($msg);
}
return $this->get();
}
// action
public function fail(mixed $msg = null, array $aParentResult = [])
{
$this->setFail('fail');
if ($aParentResult) {
$this->aParent = $aParentResult;
}
if ($msg) {
$this->msg($msg);
}
return $this->getFull();
}
// action
// public function sendTg() // 伪装成非set
// {
// $this->enableTgOne();
// return $this;
// }
// action
public function sendTg($sBotCode = 'base', $sChatCode = 'base')
{
}
public function getDataA()
{
return $this->aResult["aData"] ?? [];
}
public function getDataS()
{
return (string) $this->aResult["sData"] ?? '';
}
public function getDataI()
{
return (int) $this->aResult["iData"] ?? 0;
}
public function getDataB()
{
return (bool) $this->aResult["bData"] ?? false;
}
public function getDataX()
{
return $this->aResult["xData"] ?? '';
}
public function setDataA(array $aData): self
{
$this->aData($aData);
return $this;
}
public function setDataB(bool $bData): self
{
$this->aResult["bData"] = $bData;
return $this;
}
public function setDataS(string $sData): self
{
$this->aResult["sData"] = $sData;
return $this;
}
public function setDataI(int $iData): self
{
$this->aResult["iData"] = $iData;
return $this;
}
public function setDataX($xData): self
{
$this->aResult["xData"] = $xData;
return $this;
}
public function getResult(): array
{
return $this->aResult;
}
// action
public function get()
{
if ($this->isToTg) {
$this->_sendTg();
}
if ($this->isToTgOne) {
$this->isToTg = false;
$this->isToTgOne = false;
}
if ($this->isChain) {
return $this;
}
return $this->aResult;
}
// action
public function getFull()
{
$this->withStep();
return $this->get();
}
// action
public function toArray()
{
return $this->aResult;
}
// helper
public function isDone()
{
return $this->aResult["isDone"] ?? false;
}
// helper
public function isFail()
{
return !$this->isDone();
}
// helper
public static function isDoneResult(array $aResult): bool
{
return $aResult['isDone'] ?? false;
}
// helper
public static function isFailResult(array $aResult): bool
{
return !self::isDoneResult($aResult);
}
// shell
public function fetch()
{
return $this->get();
}
// shell
public function fetchAll()
{
return $this->getFull();
}
private function _sendTg()
{
TelegramVdb::make()->dataArr($this->aResult)->save();
}
protected function addStepParent(): void
{
$this->aStep[] = $this->aParent;
}
protected function withParentStep(): void
{
if ($this->aParent) {
$this->addStepParent();
}
}
}

93
app/Services/TomTool/Http.php Executable file
View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
class Http
{
public $sMethod = 'get';
public $aData = [];
public $sToUrl; // 必须
public $bIsJson = false;
public function send($sUrl = false): string
{
// 两种传递方式
if ($sUrl) {
$this->sToUrl = $sUrl;
}
// 初始化 cURL 会话
$ch = curl_init();
// 设置请求 URL
curl_setopt($ch, CURLOPT_URL, $this->sToUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回响应而不是输出
// 根据请求方法设置 cURL 选项
switch (strtolower($this->sMethod)) {
case 'post':
curl_setopt($ch, CURLOPT_POST, true); // 设置为 POST 请求
if ($this->bIsJson) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->aData)); // 设置 POST 数据为 JSON
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json', // 设置内容类型为 JSON
'Content-Length: ' . strlen(json_encode($this->aData)) // 设置内容长度
]);
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->aData)); // 设置 POST 数据为普通表单格式
}
break;
case 'put':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); // 设置为 PUT 请求
if ($this->bIsJson) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->aData)); // 设置 PUT 数据为 JSON
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json', // 设置内容类型为 JSON
'Content-Length: ' . strlen(json_encode($this->aData)) // 设置内容长度
]);
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->aData)); // 设置 PUT 数据为普通表单格式
}
break;
case 'get':
// 默认使用 GET 请求
curl_setopt($ch, CURLOPT_HTTPGET, true); // 设置为 GET 请求
curl_setopt($ch, CURLOPT_URL, $this->sToUrl . '?' . http_build_query($this->aData)); // 设置请求 URL
if ($this->bIsJson) {
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json' // 设置接受的内容类型为 JSON
]);
}
break;
}
// 禁用 SSL 验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证对等证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证主机名
// 执行 cURL 请求
$response = curl_exec($ch);
// 检查是否有错误
if (curl_errno($ch)) {
return 'Error: ' . curl_error($ch);
}
// 关闭 cURL 会话
curl_close($ch);
// 返回响应
return $response;
}
public static function response($iCode, $sMsg = '')
{
http_response_code($iCode);
echo $sMsg;
exit;
}
}

261
app/Services/TomTool/HttpV2.php Executable file
View File

@ -0,0 +1,261 @@
<?php
// 版本25-12-12
declare(strict_types=1);
namespace App\Services\TomTool;
use App\Services\TomTool\Flow;
use Exception;
/**
默认:
method:post
new:
HttpV2::make($url)
基本:
HttpV2::make($url)->send()
进阶:
HttpV2::make($url, "get")->setDataA($aData)->enableJsonAs()->enableAsync()->optMethod("get")->send();
action:
send()
data:
xData()
sData()
jData()
sData()
set:
optMethod()
enableJsonAs()
enableJsonSend()
enableJsonReturn()
@note:
默认关闭 SSL 验证(适用于开发环境),生产环境建议开启
异步模式下不会返回响应内容,仅快速触发请求
*/
class HttpV2
{
protected string $method;
protected $data = [];
protected string $url;
protected bool $sendJson = false;
protected bool $returnJson = false;
protected bool $async = false;
// private bool $isChain = false;
// private array $aResult = [
// "isDone" = false
// ];
public static function make(string $url, string $method = 'post'): self
{
$instance = new self();
$instance->url = $url;
$instance->optMethod($method);
return $instance;
}
// public static function start(string $sUrl, string $sMethod = 'post'): self
// {
// $oInstance = new self();
// $oInstance->url = $sUrl;
// $oInstance->method = $sMethod;
// $oInstance->isChain = true;
// return $oInstance;
// }
//
// public function isDone()
// {
// return $this->aResult["isDone"] ?? false;
// }
//
// public function isFail()
// {
// return !$this->isDone();
// }
// public function getResult()
// {
// return $this->aResult;
// }
public function optMethod(string $method): self
{
$this->method = strtolower($method);
return $this;
}
public function setDataX($xData)
{
$this->setData($xData);
return $this;
}
public function setDataA($aData)
{
$this->setData($aData);
return $this;
}
public function setDataS($sData)
{
$this->setData($sData);
return $this;
}
public function setData($xData)
{
// if (is_array($xData)) {
// json_encode($xData)
// }
$this->data = $xData;
return $this;
}
// public function aData($aData = [])
// {
// $this->data($aData);
// return $this;
// }
//
// public function sData($sData = '')
// {
// $this->data($sData);
// return $this;
// }
//
// public function jData($jData = '')
// {
// $this->data($jData);
// return $this;
// }
//
// public function xData($xData = '')
// {
// $this->data($xData);
// return $this;
// }
//
// public function data($data = ''): self
// {
// $this->data = $data;
// return $this;
// }
public function enableJsonSend(bool $flag = true): self
{
$this->sendJson = $flag;
return $this;
}
public function enableJsonReturn(bool $flag = true): self
{
$this->returnJson = $flag;
return $this;
}
public function enableJsonAs(): self
{
$this->sendJson = true;
$this->returnJson = true;
return $this;
}
public function enableAsync(bool $flag = true): self
{
$this->async = $flag;
return $this;
}
public function send(): string
{
$oFlow = Flow::make("HttpV2::send");
$ch = curl_init();
$headers = [];
// 构建 URL 和请求体
if ($this->method === 'get') {
$url = $this->url . '?' . http_build_query($this->data);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPGET, true);
} else {
curl_setopt($ch, CURLOPT_URL, $this->url);
$payload = $this->sendJson ? json_encode($this->data) : http_build_query($this->data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
if ($this->method === 'post') {
curl_setopt($ch, CURLOPT_POST, true);
} else {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method));
}
if ($this->sendJson) {
$headers[] = 'Content-Type: application/json';
$headers[] = 'Content-Length: ' . strlen($payload);
}
}
if ($this->returnJson) {
$headers[] = 'Accept: application/json';
}
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
// SSL 设置
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 异步发送
if ($this->async) {
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 200);
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_exec($ch);
curl_close($ch);
return 'sent';
}
// 阻塞执行
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: {$error}");
}
curl_close($ch);
return $response;
// $this->aResult = $oFlow->sData($response)->done();
//
// if ($this->isChain) {
// return $this;
// }
//
// return $this->aResult;
}
// public static function response(int $code, string $message = ''): void
// {
// http_response_code($code);
// echo $message;
// exit;
// }
}

26
app/Services/TomTool/Map.php Executable file
View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
class Map
{
public static function fnClientToSfx($sClient)
{
$sfx = "";
switch ($sClient) {
case "clash":
$sfx = "yaml";
break;
case "v2ray":
$sfx = "txt";
break;
default:
break;
}
return $sfx;
}
}

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool\Telegram;
use App\Services\HttpQueue as ServiceHttpQueue;
use App\Services\TomTool\Flow;
use App\Services\TomTool\Telegram\Sdk as ServiceTelegramSdk;
use App\Models\TelegramKey as ModelTelegramKey;
/**
注意是master服务器端专用的这里会对code找mapslave不会。
new:
start(?$sBodeCode, ?$sChatCode)
set:
setBotCode()
setChatCode()
setBotToken()
setChatId()
setMsgS()
action:
send()
*/
final class Master
{
public string $sBotCode = "base";
public string $sChatCode = "base";
public string $sBotToken = '';
public int $iChatId = 0;
public string $sMsg = '';
public string $sPhotoUrl = '';
public int $iPhotoWidth = 0;
public int $iPhotoHeight = 0;
public string $sFileUrl = '';
public string $sFileName = '';
public string $sType = '';
public bool $enableQueue = false; // 应该用不到了
public bool $bKeyBegin = false;
public static function start($sBotCode = 'base', $sChatCode = 'base'): self
{
$oInstance = new self();
$oInstance->sBotCode = $sBotCode;
$oInstance->sChatCode = $sChatCode;
return $oInstance;
}
public function setBotCode(string $sBotCode): self
{
$this->sBotCode = $sBotCode;
return $this;
}
public function setChatCode(string $sChatCode): self
{
$this->sChatCode = $sChatCode;
return $this;
}
public function setBotToken(string $sBotToken): self
{
$this->sBotToken = $sBotToken;
return $this;
}
public function setChatId(int $iChatId): self
{
$this->iChatId = $iChatId;
return $this;
}
public function setMsgS(string $sMsg): self
{
$this->sMsg = $sMsg;
return $this;
}
public function setPhotoUrl(string $sPhotoUrl, int $iPhotoWidth = 0, int $iPhotoHeight = 0): self
{
$this->sPhotoUrl = $sPhotoUrl;
$this->iPhotoWidth = $iPhotoWidth;
$this->iPhotoHeight = $iPhotoHeight;
return $this;
}
public function setType(string $sType): self
{
$this->sType = $sType;
return $this;
}
public function setFileUrl(string $sFileUrl = '', string $sFileName = ''): self
{
$this->sFileUrl = $sFileUrl;
$this->sFileName = $sFileName;
return $this;
}
public function send($sMsg = ''): Flow
{
if ($sMsg) {
$this->sMsg = $sMsg;
}
if (!$this->sMsg) {
// throw new \Exception("未设置msg");
}
$this->keyBegin();
if ($this->enableQueue) {
$aData = $this->buildDataTom();
$oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
} else {
$oFlow = ServiceTelegramSdk::start()
->setBotToken($this->sBotToken)
->setChatId($this->iChatId)
->setMsgS($this->sMsg)
->setPhotoUrl($this->sPhotoUrl, $this->iPhotoWidth, $this->iPhotoHeight)
->setFileUrl($this->sFileUrl, $this->sFileName)
->setType($this->sType)
->send();
}
return $oFlow;
}
private function keyBegin()
{
if (!$this->bKeyBegin) {
if (!$this->sBotToken) {
$this->sBotToken = ModelTelegramKey::botTokenByCode($this->sBotCode);
}
if (!$this->iChatId) {
$this->iChatId = ModelTelegramKey::chatIdByCode($this->sChatCode);
}
if (!$this->sBotToken || !$this->iChatId) {
throw new \RuntimeException("botToken或chatId没找到botCode={$this->sBotCode}chatCode={$this->sChatCode}");
}
$this->bKeyBegin = true;
}
}
private function buildDataTom(): array
{
return [
"sBotCode" => $this->sBotCode ?? '',
"sChatCode" => $this->sChatCode ?? '',
"sBotToken" => $this->sBotToken ?? '',
"iChatId" => $this->iChatId ?? '',
"sPhotoUrl" => $this->sPhotoUrl ?? '',
"iPhotoWidth" => $this->iPhotoWidth,
"iPhotoHeight" => $this->iPhotoHeight,
"sFileUrl" => $this->sFileUrl,
"sFileName" => $this->sFileName,
"sMsg" => $this->sMsg ?? '',
"sType" => $this->sType ?? '',
];
}
}

View File

@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool\Telegram;
use Telegram\Bot\Api;
use Telegram\Bot\FileUpload\InputFile;
use Telegram\Bot\Exceptions\TelegramSDKException;
use App\Services\TomTool\Flow;
//use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
use App\Services\TomTool\HttpV2;
class Sdk
{
public $sMsgFormat = [];
public $sMsgFormatType = 'none';
public $sBotToken = '';
public $iChatId = 0;
public $sPhotoUrl = '';
public $iPhotoWidth = 0;
public $iPhotoHeight = 0;
public $sFileUrl = '';
public $sFileName = '';
public $sMsg;
public $oApi;
private const ALLOWED_FORMATS = ['none', 'html', 'markdown', 'markdownV2'];
public static function start()
{
$oInstance = new self();
return $oInstance;
}
public function setBotToken($sBotToken)
{
$this->oApi = new Api($sBotToken);
$this->sBotToken = $sBotToken;
return $this;
}
public function setChatId($iChatId)
{
$this->iChatId = $iChatId;
return $this;
}
public function setMsgS($sMsg = '')
{
$this->sMsg = $sMsg;
return $this;
}
public function setPhotoUrl($sUrl = '', $iPhotoWidth = 0, $iPhotoHeight = 0)
{
$this->sPhotoUrl = $sUrl;
$this->iPhotoWidth = $iPhotoWidth;
$this->iPhotoHeight = $iPhotoHeight;
return $this;
}
public function setFileUrl($sFileUrl = '', $sFileName = '')
{
$this->sFileUrl = $sFileUrl;
$this->sFileName = $sFileName;
return $this;
}
public function setType($sType)
{
if ($sType) {
$this->sMsgFormatType = $sType;
}
return $this;
}
public function send()
{
$oFlow = Flow::start("tg_sdk_send");
$this->msgFormat();
// try {
if ($this->sPhotoUrl) {
$r = $this->oApi->sendPhoto($this->sMsgFormat);
} else if ($this->sFileUrl) {
$r = $this->oApi->sendDocument($this->sMsgFormat);
} else {
$r = $this->oApi->sendMessage($this->sMsgFormat);
}
if (!($r && $r->getMessageId())) {
return $oFlow->fail($r);
}
// } catch (TelegramSDKException $e) {
// return $oFlow->fail(['error' => $e->getMessage()]);
// }
return $oFlow->done();
}
public function getMsgFormat()
{
$this->msgFormat();
return $this->sMsgFormat;
}
public function msgFormat()
{
if (!in_array($this->sMsgFormatType, self::ALLOWED_FORMATS, true)) {
throw new \RuntimeException("不支持的格式类型: {$this->sMsgFormatType}");
}
$method = 'msgFormat_' . $this->sMsgFormatType;
$this->sMsgFormat = $this->$method();
if ($this->sPhotoUrl) {
$oManager = new ImageManager(new Driver());
$sTempPath = $_ENV['dir_base'].'public/upload';
$sTempPath .= "/tg_output.png";
$sImg = file_get_contents($this->sPhotoUrl);
$oImage = $oManager->read($sImg);
if ($this->iPhotoWidth && $this->iPhotoHeight) {
$oImage = $oImage->cover($this->iPhotoWidth, $this->iPhotoHeight);
}
$oImage->toJpg(80)->save($sTempPath);
$photo = InputFile::create($sTempPath, date("Y-m-d").'.jpg');
$this->sMsgFormat["photo"] = $photo;
$this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
unset($this->sMsgFormat["text"]);
} else if ($this->sFileUrl) {
if (!$this->sFileName) {
$sFileName = date("Y-m-d H:i:s").'.txt';
} else {
$sFileName = $this->sFileName;
}
$sFile = InputFile::create($this->sFileUrl, $sFileName);
$this->sMsgFormat["document"] = $sFile;
$this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
unset($this->sMsgFormat["text"]);
}
return $this;
}
private function msgFormat_none()
{
return [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
private function msgFormat_html()
{
return [
'chat_id' => $this->iChatId,
'text' => strip_tags($this->sMsg, [
'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'span', 'tg-spoiler', 'a', 'tg-emoji',
'code', 'pre',
]),
'parse_mode' => 'HTML',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
private function msgFormat_markdown()
{
return [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => 'Markdown',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
private function msgFormat_markdownV2()
{
return [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => 'MarkdownV2',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
}

View File

@ -0,0 +1,361 @@
<?php
// 版本 25/12/12
declare(strict_types=1);
namespace App\Services\TomTool\Telegram;
use App\Services\TomTool\HttpV2;
use App\Services\TomTool\Telegram\Sdk as ServiceTelegramSdk;
use App\Services\TomTool\Flow;
use App\Services\HttpQueue as ServiceHttpQueue;
/**
提醒:
master自身已经没有队列了收到slave消息直接转发
默认:
走本地队列发送到master
走异步
new:
start()
log()
warn()
fail()
notify()
opt:
enableMaster(:true)
enableMasterQueue(:true)
enableSlaveQueue(:true)
enableDetail(:true)
set:
setMsgS()
setMsgA()
setBotCode()
setChatCode()
setBotToken()
setChatId()
action:
send()
sendMaster()
sendLocal()
*/
class Slave
{
public string $sBotCode;
public string $sChatCode;
public string $sBotToken = '';
public int $iChatId = 0;
public string $sMsg = '';
public string $sPhotoUrl = '';
public int $iPhotoWidth = 0;
public int $iPhotoHeight = 0;
public string $sFileUrl = '';
public string $sFileName = '';
public string $sType = '';
public bool $enableMaster = true;
public bool $enableMasterQueue = false; // 这里之前想错了不会有master队列只在slave有master转发就行了
public bool $enableSlaveQueue = false;
public bool $enableDetail = true;
public bool $enableAsync = true;
private bool $enableSend = true;
// new
public static function start(string $sBotCode = 'base', string $sChatCode = 'base'): self
{
$oInstance = new self;
$oInstance->sBotCode = $sBotCode;
$oInstance->sChatCode = $sChatCode;
$oInstance->enableSend = filter_var($_ENV["tg_send_enable"] ?? true, FILTER_VALIDATE_BOOLEAN);
return $oInstance;
}
// new
public static function log(string $sChatCode = 'admin'): self
{
return self::start("log", $sChatCode);
}
// new
public static function warn(string $sChatCode = 'admin'): self
{
return self::start("warn", $sChatCode);
}
// new
public static function fail(string $sChatCode = 'admin'): self
{
return self::start("fail", $sChatCode);
}
// new
public static function notify(string $sChatCode = 'admin'): self
{
return self::start("fail", $sChatCode);
}
// opt
public function enableAsync(bool $b = true): self
{
$this->enableAsync = $b;
return $this;
}
// opt
public function enableDetail(bool $b = true): self
{
$this->enableDetail = $b;
return $this;
}
// opt
public function enableSlaveQueue(bool $b = true): self
{
$this->enableSlaveQueue = $b;
return $this;
}
// opt
public function enableMaster(bool $b = true): self
{
$this->enableMaster = $b;
return $this;
}
// opt
public function enableMasterQueue(bool $b = true): self
{
$this->enableMasterQueue = $b;
return $this;
}
// set
public function setPhotoUrl(string $sPhotoUrl, int $iWidth = 0, int $iHeight = 0): self
{
$this->sPhotoUrl = $sPhotoUrl;
$this->iPhotoWidth = $iWidth;
$this->iPhotoHeight = $iHeight;
return $this;
}
// set
public function setFileUrl(string $sFileUrl, string $sFileName = ''): self
{
$this->sFileUrl = $sFileUrl;
$this->sFileName = $sFileName;
return $this;
}
// set
public function setType(string $sType): self
{
$this->sType = $sType;
return $this;
}
// set
public function setMsgS(string $sMsg = ''): self
{
if ($this->enableDetail) {
$sMsg = "[ ".$this->getEnvSiteCode()." | ".date("d:H:i:s")." ] ".$sMsg;
}
$this->sMsg = $sMsg;
return $this;
}
// set
public function setMsgA(array $aMsg = []): self
{
$aMsgNew = $aMsg;
if ($this->enableDetail) {
$aMsgNew = [];
$aMsgNew["site_code"] = $this->getEnvSiteCode();
$aMsgNew["date"] = date("Y-m-d H:i:s");
$aMsgNew["data"] = $aMsg;
}
$this->sMsg = json_encode($aMsgNew, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
return $this;
}
// set
public function setBotCode(string $sBotCode): self
{
$this->sBotCode = $sBotCode;
return $this;
}
// set
public function setChatCode(string $sChatCode): self
{
$this->sChatCode = $sChatCode;
return $this;
}
// set
public function setBotToken(string $sBotToken): self
{
$this->sBotToken = $sBotToken;
return $this;
}
// set
public function setChatId(int $iChatId): self
{
$this->iChatId = $iChatId;
return $this;
}
// action
public function send($xMsg = ''): Flow
{
if ($this->enableSend == false) {
$oFlow = Flow::start();
return $oFlow->done("不发送");
}
if ($xMsg) {
if (is_array($xMsg)) {
$this->setMsgA($xMsg);
} else {
$this->setMsgS($xMsg);
}
}
if (!$this->sMsg) {
// throw new \Exception("未设置msg");
}
if ($this->enableMaster) {
$oFlow = $this->_send_master();
} else {
$oFlow = $this->_send_local();
}
return $oFlow;
}
// action
public function sendMaster($xMsg = ''): Flow
{
$this->enableMaster(true);
return $this->send($xMsg);
}
// action
public function sendLocal($xMsg = ''): Flow
{
$this->enableMaster(false);
return $this->send($xMsg);
}
private function _send_master(): Flow
{
$aData = $this->buildDataTom();
if ($this->enableSlaveQueue) {
$oFlow = ServiceHttpQueue::start("telegram_master")->arg($aData)->save();
} else {
$oFlow = Flow::start("_send_master");
$sUrlMaSterBase = $_ENV["url_master_base"] ?? "";
if (!$sUrlMaSterBase) {
throw new \Exception("未设置env的url_master_base");
}
$sUrlMasterPost = "https://".$sUrlMaSterBase."/api/telegram/post";
try {
$oHttpV2 = HttpV2::make($sUrlMasterPost);
if ($this->enableAsync) {
$oHttpV2 = $oHttpV2->enableAsync();
}
$r = $oHttpV2->enableJsonAs()->setData($aData)->send();
if ($this->enableAsync) {
return $oFlow->sData("async")->done("async");
} else {
$a = json_decode($r, true);
$isDone = $a["isDone"] ?? false;
if ($isDone) {
return $oFlow->done();
} else {
return $oFlow->fail($r);
}
}
} catch (\Throwable $e) {
$oFlow = $oFlow->fail($e->getMessage());
}
}
return $oFlow;
}
// 直接从本地发到tg几乎用不上都走master了
private function _send_local(): Flow
{
if ($this->enableSlaveQueue) {
$aData = $this->buildDataTom();
$oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
} else {
$oFlow = ServiceTelegramSdk::start($this->sBotToken, $this->iChatId)->setMsgS($this->sMsg)->send();
}
return $oFlow;
}
private function buildDataTom(): array
{
return [
"sBotCode" => $this->sBotCode ?? '',
"sChatCode" => $this->sChatCode ?? '',
"sBotToken" => $this->sBotToken ?? '',
"iChatId" => $this->iChatId ?? '',
"enableMasterQueue" => $this->enableMasterQueue ?? '',
"enableMaster" => $this->enableMaster,
"sMsg" => $this->sMsg ?? '',
"sPhotoUrl" => $this->sPhotoUrl ?? '',
"iPhotoWidth" => $this->iPhotoWidth ?? 0,
"iPhotoHeight" => $this->iPhotoHeight ?? 0,
"sType" => $this->sType ?? '',
"sFileUrl" => $this->sFileUrl ?? '',
"sFileName" => $this->sFileName ?? '',
];
}
private function getEnvSiteCode(): string
{
$sEnvSiteCode = $_ENV["site_code"] ?? "未设置env的site_code";
return $sEnvSiteCode;
}
}

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace App\Services\IM;
use App\Models\Config;
use Telegram\Bot\Api;
use Telegram\Bot\Exceptions\TelegramSDKException;
use function strip_tags;
// 没有必须默认使用base配置
final class TelegramV2
{
private Api $bot;
public $iTo;
public $sToken;
public $sMsgType = false;
public function __construct($sToken = "base", $sTo = "base")
{
$this->setToken($sToken);
$this->setTo($sTo);
// $this->bot = new Api($this->sToken);
}
public function setMsgType($sMsgType)
{
$this->sMsgType = $sMsgType;
}
public function setTo($xTo, $sType = "code")
{
$iChatId = $xTo;
if ($sType == "code") {
// $sEnvKey = "tgChat_".$xTo;
$iChatId = $_ENV['tgChat'][$iChatId];
}
$this->iTo = $iChatId;
}
public function setToken($xToken, $sType = "code")
{
$sToken = $xToken;
if ($sType == "code") {
// $sEnvKey = "tgToken_".$xToken;
$sToken = $_ENV['tgToken'][$sToken];
}
$this->sToken = $sToken;
$this->bot = new Api($this->sToken);
}
public function send($sMsg, $sMsgType = 'str')
{
if ($sMsgType == "arr") {
$sMsg = json_encode($sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
if ($this->sMsgType === false) {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else if ($this->sMsgType === "html") {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => strip_tags(
$sMsg,
['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike','del', 'span','tg-spoiler', 'a', 'tg-emoji',
'code', 'pre',
]
),
'parse_mode' => 'HTML',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else if ($this->sMsgType === "markdown") {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => 'Markdown',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else if ($this->sMsgType === "markdownV2") {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => 'MarkdownV2',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
return $this->bot->sendMessage($sendMessage);
}
}

View File

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
use Throwable as ThrowableBase;
use Illuminate\Database\QueryException;
use App\Services\TomTool\TelegramVdb;
/**
new:
$oThrowableHandler = ThrowableHandler::make($e);
快速:
$aResult = ThrowableHandler::make($e)->toArr();
action
fetch() or get()
set:
enableTg()
enableTrace()
setMaxDepth()
*/
class ThrowableHandler
{
protected bool $tgEnable = false;
protected bool $showTrace = false;
protected int $maxDepth = 3;
protected ThrowableBase $e;
public static function make(ThrowableBase $e): self
{
$oInstance = new self();
$oInstance->e = $e;
return $oInstance;
}
public function fetch()
{
return $this->get();
}
public function get(): array
{
$data = $this->buildExceptionData($this->e, 0);
if ($this->tgEnable) {
$this->sendTelegram($data);
}
return $data;
}
public function toArr(): array
{
return $this->get();
}
public function toJson(): string
{
return json_encode($this->get(), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
public function enableTg(bool $enable = true): self
{
$this->tgEnable = $enable;
return $this;
}
public function enableTrace(bool $enable = true): self
{
$this->showTrace = $enable;
return $this;
}
public function setMaxDepth(int $depth): self
{
$this->maxDepth = max(1, $depth);
return $this;
}
protected function buildExceptionData(ThrowableBase $e, int $depth): array
{
$data = [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
];
$code = $e->getCode();
if ($code !== null && $code !== 0) {
$data['code'] = $code;
}
if ($this->showTrace) {
$filteredTrace = array_filter($e->getTrace(), function ($frame) {
return empty($frame['file']) || !str_contains($frame['file'], '/vendor/');
});
$traceLines = [];
foreach ($filteredTrace as $frame) {
$file = $frame['file'] ?? '[internal]';
$line = $frame['line'] ?? '';
$func = isset($frame['class'], $frame['function'])
? "{$frame['class']}{$frame['type']}{$frame['function']}"
: ($frame['function'] ?? '[unknown]');
$traceLines[] = "{$file}:{$line} {$func}()";
}
$data['trace'] = implode("\n", $traceLines);
}
if ($depth < $this->maxDepth && $e->getPrevious() instanceof ThrowableBase) {
$data['previous'] = $this->buildExceptionData($e->getPrevious(), $depth + 1);
}
return $data;
}
protected function sendTelegram(array $data): void
{
TelegramVdb::make()->aData($data)->save();
}
}

21
app/Services/TomTool/Tom.php Executable file
View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
class Tom
{
public static function toJson($aData)
{
return json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
public static function toJsonBr($aData)
{
$sData = self::toJson($aData);
$sData = str_replace("\\n", "<br>", $sData);
return $sData;
}
}

15
artisan Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
$status = (require_once __DIR__.'/bootstrap/app.php')
->handleCommand(new ArgvInput);
exit($status);

24
bootstrap/app.php Executable file
View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
//'test' => \App\Http\Middleware\Test::class,
]);
$middleware->validateCsrfTokens(except: [
'api/*',
'cmd/*',
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

2
bootstrap/cache/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

5
bootstrap/providers.php Executable file
View File

@ -0,0 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
];

76
composer.json Executable file
View File

@ -0,0 +1,76 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"guzzlehttp/guzzle": "^7.9",
"intervention/image": "^3.11",
"irazasyed/telegram-bot-sdk": "^3.15",
"laravel/framework": "^11.31",
"laravel/tinker": "^2.9",
"symfony/yaml": "^7.4",
"voku/anti-xss": "^4.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.1",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^11.0.1"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

8704
composer.lock generated Executable file

File diff suppressed because it is too large Load Diff

126
config/app.php Executable file
View File

@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => env('APP_TIMEZONE', 'UTC'),
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

115
config/auth.php Executable file
View File

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

108
config/cache.php Executable file
View File

@ -0,0 +1,108 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

173
config/database.php Executable file
View File

@ -0,0 +1,173 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

77
config/filesystems.php Executable file
View File

@ -0,0 +1,77 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

132
config/logging.php Executable file
View File

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

116
config/mail.php Executable file
View File

@ -0,0 +1,116 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

112
config/queue.php Executable file
View File

@ -0,0 +1,112 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

38
config/services.php Executable file
View File

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'resend' => [
'key' => env('RESEND_KEY'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

217
config/session.php Executable file
View File

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain and all subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

1
database/.gitignore vendored Executable file
View File

@ -0,0 +1 @@
*.sqlite*

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
}
}

17
package.json Executable file
View File

@ -0,0 +1,17 @@
{
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"vite": "^6.0"
}
}

33
phpunit.xml Executable file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>

6
postcss.config.js Executable file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

29
public/index.php Executable file
View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
function tomd($aData, $iType = 0) {
if (env("is_loc") == 1) {
echo "<pre>";
var_dump($aData);
if ($iType == 1) {
exit;
}
}
}
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
(require_once __DIR__.'/../bootstrap/app.php')
->handleRequest(Request::capture());

2
public/js/jquery.min.js vendored Executable file

File diff suppressed because one or more lines are too long

3
resources/css/app.css Executable file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

1
resources/js/app.js Executable file
View File

@ -0,0 +1 @@
import './bootstrap';

4
resources/js/bootstrap.js vendored Executable file
View File

@ -0,0 +1,4 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

8
routes/console.php Executable file
View File

@ -0,0 +1,8 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote')->hourly();

44
routes/web.php Executable file
View File

@ -0,0 +1,44 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Article\ListController as ArticleListController;
use App\Http\Controllers\Article\SingleController as ArticleSingleController;
use App\Http\Controllers\ExecController;
use App\Http\Controllers\ClientController;
use App\Http\Controllers\HelperController;
use App\Http\Controllers\HtmlController;
use App\Http\Controllers\SakaiController;
use App\Http\Controllers\CronController;
use App\Http\Controllers\CmdController;
use App\Http\Controllers\Api\ShipController;
//use App\Http\Controllers\Api\GithubController;
use App\Http\Controllers\Api\Tg\HookController;
use App\Http\Controllers\Api\CountryController;
use App\Http\Controllers\Api\GoodController;
use App\Http\Controllers\Api\GithubController;
use App\Services\OpenApi\Telegram as ServiceOpenApiTelegram;
use App\Services\Article\Schema as ServiceArticleSchema;
use App\Http\Controllers\Api\FreenodeController as ApiFreenodeController;
Route::any('/api/telegram/post', [ServiceOpenApiTelegram::class, 'post']);
Route::any('/api/article/schema/build', [ServiceArticleSchema::class, 'api_buildArticle']);
Route::any('/api/article/schema/build/receive', [ServiceArticleSchema::class, 'api_build_receive']);
Route::get('/sub/{siteCode}/{client}/{date?}', [ApiFreenodeController::class, "sub"]);
Route::get('/cmd', [CmdController::class, 'index']);
Route::post('/cmd/send', [CmdController::class, 'ajaxSend']);
Route::get('/cmd/image/upload', [CmdController::class, 'imageUpload']);
Route::post('/cmd/image/upload/exec', [CmdController::class, 'imageUploadExec']);
Route::post('/api/open/telegram/queue/add', [ServiceTelegram::class, 'queueAdd']);
Route::post('/api/github/get', [GithubController::class, 'get']);
Route::get('/api/good/rand/{class}', [GoodController::class, 'rand']);
Route::get('/api/freenode/country/rand/{case?}', [CountryController::class, 'rand']);
Route::get('/api/ship/get/{shipCode}/{clientClass}/{clientCode?}', [ShipController::class, 'getShipByCode']);
Route::get('/cron', [CronController::class, 'index']);
Route::any('/api/tg/hook', [HookController::class, 'cmd']);
Route::any('/api/tg/hook/cmd_ffq_article', [HookController::class, 'cmd_ffq_article']);
Route::get('/tool', [SakaiController::class, 'tool']);

14
scripts/cron.php Executable file
View File

@ -0,0 +1,14 @@
<?php
error_reporting(E_ALL & ~E_DEPRECATED);
require __DIR__ . '/../vendor/autoload.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$service = $app->make(App\Services\Cron::class);
//$service->setTestI(5);
$result = $service->run();
print_r($result);

4
storage/app/.gitignore vendored Executable file
View File

@ -0,0 +1,4 @@
*
!private/
!public/
!.gitignore

2
storage/app/private/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/app/public/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

9
storage/framework/.gitignore vendored Executable file
View File

@ -0,0 +1,9 @@
compiled.php
config.php
down
events.scanned.php
maintenance.php
routes.php
routes.scanned.php
schedule-*
services.json

3
storage/framework/cache/.gitignore vendored Executable file
View File

@ -0,0 +1,3 @@
*
!data/
!.gitignore

2
storage/framework/cache/data/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/sessions/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/testing/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/views/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/logs/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

20
tailwind.config.js Executable file
View File

@ -0,0 +1,20 @@
import defaultTheme from 'tailwindcss/defaultTheme';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
'./storage/framework/views/*.php',
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
],
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [],
};

19
tests/Feature/ExampleTest.php Executable file
View File

@ -0,0 +1,19 @@
<?php
namespace Tests\Feature;
// use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}

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