447 lines
16 KiB
PHP
447 lines
16 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Services\Article;
|
||
|
||
use App\Models\ArticleCache as ModelArticleCache;
|
||
use App\Models\ArticleSchema as ModelArticleSchema;
|
||
use App\Models\ArticleCacheTime as ModelArticleCacheTime;
|
||
use App\Models\TelegramKey as ModelTelegramKey;
|
||
use App\Services\TomTool\Flow;
|
||
use App\Services\SlaveMap as ServiceSlaveMap;
|
||
use App\Services\Article\Cmd as ServiceArticleCmd;
|
||
|
||
final class CacheTg
|
||
{
|
||
|
||
public static function start()
|
||
{
|
||
return new self();
|
||
}
|
||
|
||
public function add($sGroupCode, $sContent)
|
||
{
|
||
$oResult = Flow::make("cacheAdd");
|
||
|
||
$oModelArticleCache = new ModelArticleCache();
|
||
$oModelArticleCache->content = $sContent;
|
||
$oModelArticleCache->group_code = $sGroupCode;
|
||
$oModelArticleCache->created_at = time();
|
||
$r = $oModelArticleCache->save();
|
||
if (!$r) {
|
||
return $oResult->fail("写入缓存失败");
|
||
}
|
||
|
||
$oModelArticleCacheTime = ModelArticleCacheTime::first();
|
||
$oModelArticleCacheTime->time = time();
|
||
$r = $oModelArticleCacheTime->save();
|
||
if (!$r) {
|
||
return $oResult->fail("更新cacheTime失败");
|
||
}
|
||
|
||
return $sGroupCode."写入一条缓存";
|
||
}
|
||
|
||
public function saveToSchema(): Flow
|
||
{
|
||
$oFlow = Flow::start("save");
|
||
|
||
$oArticleCache = ModelArticleCache::where("status", 1)->get();
|
||
|
||
// 第一步,格式化通用tgSchema
|
||
$aDataMix["data"] = [];
|
||
foreach ($oArticleCache as $oArticleCacheRow) {
|
||
|
||
if (!isset($sFromPaichu)) { // 等于只是第一次each会赋值
|
||
$sFromPaichu = $oArticleCacheRow->group_code;
|
||
}
|
||
|
||
if ($oArticleCacheRow->group_code !== $sFromPaichu) {
|
||
continue; // 实现一次只save一种group_code
|
||
}
|
||
|
||
$aContent = json_decode($oArticleCacheRow->content, true);
|
||
$aReturn = $this->_schemaByCache_common($aContent, $oArticleCacheRow->group_code);
|
||
|
||
foreach ($aReturn as $aData) {
|
||
$aDataMix["data"][] = $aData;
|
||
}
|
||
|
||
$sGroupCode = $oArticleCacheRow->group_code; // 这里等于直接提取后一个group_code,不算完美,但没大问题
|
||
}
|
||
|
||
// 第二步,进一步格式化为对应form的Schema
|
||
$method = '_schemaWithFrom_' . $oArticleCacheRow->group_code;
|
||
if (method_exists($this, $method)) {
|
||
$aDataMix = $this->$method($aDataMix, $sGroupCode);
|
||
} else {
|
||
// 没有对应直接不处理
|
||
}
|
||
|
||
$y = date("Y");
|
||
$sYqian = substr($y, 1, 2);
|
||
$sYhou = substr($y, 3, 1);
|
||
|
||
if ($oArticleCacheRow->code) {
|
||
$sArticleCode = $oArticleCacheRow->code;
|
||
} else {
|
||
$sArticleCode = $sGroupCode."-".$sYqian.date("mdHis").$sYhou;
|
||
}
|
||
|
||
$oArticleSchema = ModelArticleSchema::where("code", $sArticleCode)->first();
|
||
|
||
if (!$oArticleSchema) {
|
||
$oArticleSchema = new ModelArticleSchema();
|
||
}
|
||
|
||
$oArticleSchema->schema = json_encode($aDataMix["data"]);
|
||
$oArticleSchema->code = $sArticleCode;
|
||
$oArticleSchema->group_code = $sGroupCode;
|
||
$oArticleSchema->name = $aDataMix["name"] ?? "none";
|
||
$oArticleSchema->save();
|
||
|
||
ModelArticleCache::where("status", 1)->update([
|
||
"status" => 0, // 上线改0
|
||
"code" => $sArticleCode,
|
||
]);
|
||
|
||
ServiceArticleCmd::startWithO($oArticleSchema)->syncOpen()->save("new");
|
||
|
||
return $oFlow->done();
|
||
}
|
||
|
||
private function _schemaByCache_common($aContent, $sGroupCode)
|
||
{
|
||
$aDataMix = [];
|
||
|
||
if (isset($aContent["message"]["document"]["mime_type"])) { // 包含文档
|
||
|
||
$sDocumentType = $aContent["message"]["document"]["mime_type"];
|
||
|
||
$aDataTmp = []; // new data init
|
||
$aDataTmp["type"] = "str";
|
||
$aDataTmp["name"] = "str";
|
||
$aDataTmp["content"]["base"] = "";
|
||
|
||
if (strpos($sDocumentType, "image") !== false) { // 包含图片
|
||
|
||
$sDocumentName = $aContent["message"]["document"]["file_name"];
|
||
|
||
// img data init
|
||
$aDataTmp["type"] = "img";
|
||
$aDataTmp["name"] = "img_cesu";
|
||
$aDataTmp["content"]["base"] = $sDocumentName;
|
||
$aDataTmp["content"]["img"] = [
|
||
"title" => $sDocumentName,
|
||
"base" => '',
|
||
"thumb" => '',
|
||
];
|
||
|
||
$aCacheImgId = [
|
||
"base" => $aContent["message"]["document"]["file_id"],
|
||
"thumb" => $aContent["message"]["document"]["thumb"]["file_id"],
|
||
];
|
||
|
||
$sBotToken = ModelTelegramKey::botTokenByCode($sGroupCode);
|
||
|
||
foreach ($aCacheImgId as $k => $aImgIdRow) {
|
||
// 第一步:获取 file_path
|
||
$getFileUrl = "https://api.telegram.org/bot".$sBotToken."/getFile?file_id=".$aImgIdRow;
|
||
$response = file_get_contents($getFileUrl);
|
||
$data = json_decode($response, true);
|
||
|
||
if (!$data['ok']) {
|
||
$aDataTmp["content"]["img"][$k] = "获取图片途径失败";
|
||
continue;
|
||
}
|
||
|
||
$filePath = $data['result']['file_path'];
|
||
|
||
// 第二步:下载文件内容
|
||
$downloadUrl = "https://api.telegram.org/file/bot".$sBotToken."/".$filePath;
|
||
$imageData = file_get_contents($downloadUrl);
|
||
|
||
if ($imageData === false) {
|
||
$aDataTmp["content"]["img"][$k] = "下载文件失败";
|
||
continue;
|
||
}
|
||
|
||
// 第三步:保存到服务器
|
||
$savePath = 'upload/';
|
||
if (!is_dir($savePath)) {
|
||
mkdir($savePath, 0777, true);
|
||
}
|
||
|
||
$fileName = basename($filePath);
|
||
$fileName = uniqid('ffq_tg-'.$k.'-'.$fileName.'-'.time().'-'); // 加前缀并提高熵值
|
||
file_put_contents($savePath . $fileName, $imageData);
|
||
|
||
$sSavePath = $_ENV["APP_URL"].'/'.$savePath.$fileName;
|
||
|
||
$aDataTmp["content"]["img"][$k] = $sSavePath;
|
||
|
||
}
|
||
|
||
} else { // 包含图片 end
|
||
$aDataTmp["content"]["base"] = "未处理类型{$sDocumentType}";
|
||
}
|
||
|
||
$aDataMix[] = $aDataTmp;
|
||
|
||
} // 包含文档 end
|
||
|
||
// 包含文本
|
||
if (isset($aContent["message"]["caption"])) {
|
||
|
||
$sStr = $aContent["message"]["caption"];
|
||
|
||
$aDataTmp = [];
|
||
$aDataTmp["type"] = "str";
|
||
$aDataTmp["name"] = "str";
|
||
$aDataTmp["content"]["base"] = $sStr;
|
||
$aDataMix[] = $aDataTmp;
|
||
|
||
}
|
||
|
||
if (count($aDataMix) == 0) {
|
||
$aDataTmp = [];
|
||
$aDataTmp["type"] = "loss";
|
||
$aDataTmp["name"] = "loss";
|
||
$aDataTmp["content"]["base"] = "提取失败:".json_encode($aContent);
|
||
$aDataMix[] = $aDataTmp;
|
||
}
|
||
|
||
return $aDataMix;
|
||
}
|
||
|
||
private function _schemaWithFrom_ffq_tg_a($aDataMix, $sGroupCode)
|
||
{
|
||
|
||
$aDataMixNew = [];
|
||
$aDataMixNew["name"] = '';
|
||
$aDataMixNew["title"] = '';
|
||
$aDataMixNew["data"] = [];
|
||
|
||
foreach ($aDataMix["data"] as $aDataMixRow) {
|
||
$sContent = $aDataMixRow["content"]["base"] ?? "⚠️ 没有content?";
|
||
$sType = $aDataMixRow['type'] ?? "str";
|
||
if ($sType == 'str') {
|
||
$sRowName = 'str';
|
||
|
||
// $sContent = str_replace("<br />", " ", $sContent);
|
||
$aContent = $this->ffq_tg_str_fetch($sContent);
|
||
$sArticleName = $aContent["name"] ?? '';
|
||
$sStrTiqu = $aContent["tiqu"] ?? '';
|
||
$sStrBaoliu = $aContent["baoliu"] ?? '';
|
||
$sStrBaoliu = str_replace("翻翻墙", "{{sAuthorName}}", $sStrBaoliu);
|
||
$sStrBaoliu = str_replace("😀", "{{sIconText}}", $sStrBaoliu);
|
||
$sStrBaoliu = str_replace("📊", "{{sIconText}}", $sStrBaoliu);
|
||
|
||
if (!$sStrTiqu) {
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "tag_tiqu",
|
||
"content" => [
|
||
"base" => $sStrTiqu,
|
||
],
|
||
];
|
||
}
|
||
|
||
if ($sArticleName) {
|
||
$aDataMixNew["name"] = $sArticleName;
|
||
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "schema_name",
|
||
"content" => [
|
||
"base" => $sArticleName,
|
||
],
|
||
];
|
||
}
|
||
|
||
|
||
$sStrBaoliu = nl2br($sStrBaoliu);
|
||
$sStrBaoliu = str_replace(array("\r", "\n"), '', $sStrBaoliu);
|
||
|
||
$tmp = "<br \/><br \/>";
|
||
|
||
if (preg_match('/机场自介(.*?)'.$tmp.'/', $sStrBaoliu, $matches)) {
|
||
$sStr = $matches[1];
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "jichangzijie",
|
||
"content" => [
|
||
"title" => "机场自介",
|
||
"base" => $sStr
|
||
],
|
||
];
|
||
}
|
||
|
||
if (preg_match('/链接导航(.*?)'.$tmp.'/', $sStrBaoliu, $matches)) {
|
||
$sStr = $matches[1];
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "lianjiedaohang",
|
||
"content" => [
|
||
"title" => "链接导航",
|
||
"base" => $sStr,
|
||
],
|
||
];
|
||
}
|
||
|
||
if (preg_match('/主观点评(.*?)'.$tmp.'/', $sStrBaoliu, $matches)) {
|
||
$sStr = $matches[1];
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "zhuguandianping",
|
||
"content" => [
|
||
"title" => "{{sAuthorName}}主观点评",
|
||
"base" => $sStr
|
||
],
|
||
];
|
||
}
|
||
|
||
if (preg_match('/主观评分(.*?)'.$tmp.'/', $sStrBaoliu, $matches)) {
|
||
$sStr = $matches[1];
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "zhuguanpingfen",
|
||
"content" => [
|
||
"title" => "{{sAuthorName}}主观评分",
|
||
"base" => $sStr
|
||
],
|
||
];
|
||
}
|
||
|
||
// if (preg_match('/提醒您(.*?)'.$tmp.'/', $sStrBaoliu, $matches)) {
|
||
// $sStr = $matches[1];
|
||
// $aDataMixNew["data"][] = [
|
||
// "type" => "str",
|
||
// "name" => "tixingnin",
|
||
// "content" => [
|
||
// "title" => "提醒",
|
||
// "base" => $sStr
|
||
// ],
|
||
// ];
|
||
// }
|
||
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => "tixingnin",
|
||
"content" => [
|
||
"title" => "提醒",
|
||
"base" => "任何机场都有跑路可能,建议仅月付,以降低风险。(不针对任何机场)",
|
||
],
|
||
];
|
||
|
||
preg_match('/论坛[::]\s*(https?:\/\/[^\s]*?\.html)/u', $sStrBaoliu, $matches);
|
||
if (isset($matches[1])) {
|
||
$sUrl = $matches[1];
|
||
$aReturn = $this->getTaocan_ffq_tg($sUrl);
|
||
if (Flow::isFailResult($aReturn)) {
|
||
$tmp = "⚠️ 套餐table获取失败?";
|
||
}
|
||
$tmp = $aReturn["sData"];
|
||
|
||
$aDataTmp = [];
|
||
$aDataTmp["type"] = "str";
|
||
$aDataTmp["name"] = "table_taocan";
|
||
$aDataTmp["content"]["base"] = $tmp;
|
||
$aDataMixNew["data"][] = $aDataTmp;
|
||
}
|
||
|
||
if (str_contains($sStrBaoliu, "评分")) {
|
||
$sRowName = "str_jieshao";
|
||
}
|
||
|
||
$aDataMixNew["data"][] = [
|
||
"type" => "str",
|
||
"name" => $sRowName,
|
||
"content" => [
|
||
"base" => $sStrBaoliu,
|
||
],
|
||
];
|
||
} else if ($sType == 'img') {
|
||
// $aDataMixNew["images"][] = $aDataMixRow;
|
||
$aDataMixNew["data"][] = $aDataMixRow;
|
||
} else {
|
||
$aDataMixNew["data"][] = $aDataMixRow;
|
||
}
|
||
|
||
}
|
||
|
||
return $aDataMixNew;
|
||
}
|
||
|
||
private function ffq_tg_str_fetch($str) {
|
||
$arr = [
|
||
"tiqu" => '',
|
||
"baoliu" => $str,
|
||
"name" => '',
|
||
];
|
||
|
||
if (preg_match('/#([^📊]+)📊/u', $str, $matches)) {
|
||
$arr["tiqu"] = $matches[1]; // 提取内容(不含 # 和 📊)
|
||
$arr["baoliu"] = preg_replace('/#([^📊]+)📊/u', '', $str); // 去掉提取部分
|
||
|
||
$tmp = explode("#", $arr["tiqu"]);
|
||
$arr["name"] = $tmp[0] ?? '';
|
||
$arr["name"] = trim($arr["name"]);
|
||
}
|
||
|
||
return $arr;
|
||
}
|
||
|
||
private function getTaocan_ffq_tg($url)
|
||
{
|
||
$oResult = Flow::make("getTaocan_ffq_tg");
|
||
|
||
// 初始化 cURL
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_FOLLOWLOCATION => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/115.0 Safari/537.36',
|
||
]);
|
||
|
||
$html = curl_exec($ch);
|
||
curl_close($ch);
|
||
|
||
if ($html === false) {
|
||
return $oResult->step("html=false")->sData("⚠️ 无法读取网页内容")->done();
|
||
}
|
||
|
||
// 解析 HTML
|
||
libxml_use_internal_errors(true);
|
||
$dom = new \DOMDocument();
|
||
$dom->loadHTML($html);
|
||
libxml_clear_errors();
|
||
|
||
$xpath = new \DOMXPath($dom);
|
||
|
||
// 找到第一个 class 为 markdown-body 的 div
|
||
$markdownDiv = $xpath->query('//div[contains(@class, "markdown-body")]')->item(0);
|
||
if (!$markdownDiv) {
|
||
return $oResult->step("div loss")->sData("⚠️ 未找到 class 为 'markdown-body' 的 div")->done();
|
||
}
|
||
|
||
// 找到该 div 中的第一个 table
|
||
$table = null;
|
||
foreach ($markdownDiv->getElementsByTagName('table') as $t) {
|
||
$table = $t;
|
||
break;
|
||
}
|
||
|
||
if (!$table) {
|
||
return $oResult->step("table loss")->sData("⚠️ 未找到 table 标签")->done();
|
||
}
|
||
|
||
$sHtml = $dom->saveHTML($table);
|
||
|
||
return $oResult->sData($sHtml)->done();
|
||
}
|
||
|
||
}
|