887 lines
39 KiB
PHP
887 lines
39 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Typecho-Fans/Plugins专用脚本:自动化更新插件信息文档及zip包
|
||
* (GitHub Actions工作流调用,每次提交自动运行,修改务必谨慎!)
|
||
* 反馈:https://github.com/typecho-fans/plugins/issues
|
||
*/
|
||
|
||
date_default_timezone_set('Asia/Shanghai');
|
||
$authKey = $argv[1];
|
||
$requestUrl = $argv[2] ?? '';
|
||
|
||
//提取最近变更信息
|
||
$urls = $requestUrl ? [] : ['cron'];
|
||
if (str_contains($requestUrl, '.diff')) {
|
||
$record = file_get_contents($requestUrl);
|
||
$diffs = explode(PHP_EOL, $record);
|
||
|
||
//查找有关文档变更
|
||
$begin = array_search('+++ b/README.md', $diffs) ?: array_search('+++ b/TESTORE.md', $diffs) ?: 0;
|
||
foreach ($diffs as $line => $diff) {
|
||
if ($line > $begin) {
|
||
//匹配变更行repo信息
|
||
if (str_starts_with($diff, '+[')) {
|
||
preg_match_all('/(?<=\()[^\)]+/', $diff, $links);
|
||
if ($links && str_contains($diff, '](')) {
|
||
$urls[] = trim($links[0][0]); //取第一个链接内容
|
||
}
|
||
}
|
||
//至非文档部分跳出
|
||
if (preg_match('/^diff --git a\/(?!README\.md|TESTORE\.md).*/', $diff)) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
//指定插件信息情况
|
||
} else {
|
||
$urls = explode(',', $requestUrl);
|
||
}
|
||
|
||
//检测文档执行更新
|
||
$movable = [];
|
||
if (file_exists('README.md')) {
|
||
$movable = updatePlugins('README.md', $urls, $authKey);
|
||
} else {
|
||
throw new RuntimeException('README.md is missing!');
|
||
}
|
||
if (file_exists('TESTORE.md')) {
|
||
$movable = updatePlugins('TESTORE.md', $urls, $authKey, $movable);
|
||
if ($movable) {
|
||
updatePlugins('README.md', $urls, 'rec', $movable); //rec情况递归
|
||
}
|
||
} else {
|
||
throw new RuntimeException('TESTORE.md is missing!');
|
||
}
|
||
|
||
/**
|
||
* 循环每行检测更新并重组文档
|
||
*
|
||
* @param string $tableFile MD文档路径
|
||
* @param array $requested 需求更新repo信息
|
||
* @param string $token Key或递归处理情况
|
||
* @param array $added 转移条目或zip名表
|
||
* @return array
|
||
*/
|
||
function updatePlugins(string $tableFile, array $requested, string $token = '', array $added = []): array
|
||
{
|
||
//预设出循环变量
|
||
$logs = '-------' . $tableFile . '-------' . PHP_EOL . date('Y-m-d', time()) . PHP_EOL;
|
||
$descriptions = [];
|
||
$tf = $tableFile == 'README.md';
|
||
$all = 0;
|
||
$revise = 0;
|
||
$creat = 0;
|
||
$update = 0;
|
||
$renew = 0;
|
||
$release = 0;
|
||
$done = 0;
|
||
$nameList = 'ZIP_CDN/NAME_LIST.log';
|
||
$listConent = file_exists($nameList)
|
||
? explode('README.md ALL' . PHP_EOL, trim(file_get_contents($nameList)))
|
||
: [];
|
||
$listNames = $listConent ? explode(PHP_EOL, $listConent[0]) : [];
|
||
$movable = [];
|
||
$allNames = $tf ? ['README.md ALL'] : (isset($listConent[1]) ? explode(PHP_EOL, $listConent[1]) : []);
|
||
$tables = [];
|
||
$normal = $token && $token !== 'rec';
|
||
|
||
//创建临时文件夹
|
||
$tmpDir = realpath('../') . '/TMP';
|
||
if (!is_dir($tmpDir)) {
|
||
mkdir($tmpDir, 0777, true);
|
||
}
|
||
$tmpNew = realpath('../') . '/NEW';
|
||
if (!is_dir($tmpNew)) {
|
||
mkdir($tmpNew, 0777, true);
|
||
}
|
||
|
||
//分割文档准备开始循环
|
||
$source = file_get_contents($tableFile);
|
||
$lines = explode(PHP_EOL, trim($source));
|
||
$tableLine = 0;
|
||
foreach ($lines as $line => $column) {
|
||
if (str_contains($column, '| :----:')) {
|
||
$tableLine = $line; //定位表格行
|
||
break;
|
||
}
|
||
}
|
||
if ($tableLine) {
|
||
$counts = count($lines);
|
||
foreach ($lines as $line => $column) {
|
||
//说明部分更新收录总数
|
||
if ($line < $tableLine + 1) {
|
||
if ($line == $tableLine - 8) {
|
||
preg_match('/(?<=\()[^\)]*/', $column, $total);
|
||
if ($total) {
|
||
$column = str_replace($total[0], $counts - ($tableLine + 1), $column);
|
||
} else {
|
||
$logs .= 'Error: Cannot match the total number in "' . $tableFile . '"!' . PHP_EOL;
|
||
}
|
||
}
|
||
$descriptions[] = $column;
|
||
//表格部分匹配repo信息
|
||
} elseif ($column) {
|
||
$metas = explode(' | ', $column);
|
||
if (count($metas) == 5 && $token !== 'rec') {
|
||
$nameMeta = $metas[0];
|
||
preg_match('/(?<=\[)[^\]]*/', $nameMeta, $names);
|
||
$name = trim($names ? $names[0] : $nameMeta); //取第一个栏位(链接)文本
|
||
if ($name) {
|
||
preg_match_all('/(?<=\()[^)]*/', $column, $links);
|
||
$url = $links && str_contains($nameMeta, '](') ? trim($links[0][0]) : ''; //取第一个栏位链接内容
|
||
$host = parse_url($url, PHP_URL_HOST);
|
||
$github = $host == 'github.com';
|
||
$condition = false;
|
||
//定期处理全GitHub源插件
|
||
if ($requested == ['cron']) {
|
||
$condition = $github;
|
||
//处理文档变更或指定插件
|
||
} elseif ($requested = array_filter($requested)) {
|
||
$condition = in_array($url, $requested) || in_array($name, $requested);
|
||
}
|
||
|
||
//处理表格作者名
|
||
$authorMeta = $metas[3];
|
||
$authorCode = html_entity_decode(trim($authorMeta));
|
||
preg_match('/[\t ]*(,|&|,)[ \t]*/', $authorCode, $separators); //匹配分隔符
|
||
//多作者情况
|
||
$separator = '';
|
||
if ($separators) {
|
||
$separator = $separators[0];
|
||
$authors = explode($separator, $authorCode);
|
||
$authorNames = [];
|
||
$authorMDfix = [];
|
||
foreach ($authors as $author) {
|
||
preg_match('/(?<=\[)[^\]]*/', $author, $authorName); //匹配链接文字
|
||
$authorText = trim($authorName ? $authorName[0] : $author);
|
||
$authorNames[] = $authorText;
|
||
$authorMDfix[] = str_replace(['_', '*'], ['_', '*'], $authorText); //Markdown转义
|
||
}
|
||
//单作者情况
|
||
} else {
|
||
preg_match('/(?<=\[)[^\]]*/', $authorCode, $authorName);
|
||
$author = trim($authorName ? $authorName[0] : $authorCode);
|
||
$authorMD = str_replace(['_', '*'], ['_', '*'], $author);
|
||
}
|
||
$authorTable = $separator ? implode($separator, $authorNames) : $author;
|
||
//使*和_正常显示
|
||
$column = str_replace(
|
||
$authorMeta,
|
||
$separator
|
||
? str_replace($authorNames, $authorMDfix, $authorMeta)
|
||
: str_replace($author, $authorMD, $authorMeta),
|
||
$column
|
||
);
|
||
//作者名转文件名
|
||
$zipName =
|
||
$name .
|
||
'_' .
|
||
str_replace(
|
||
[':', '"', '/', '\\', '|', '?', '*'],
|
||
'',
|
||
preg_replace('/[\t ]*(,|&|,)[ \t]*/', '_', $authorTable)
|
||
) .
|
||
'.zip';
|
||
|
||
$isUrl = str_starts_with($url, 'http://') || str_starts_with($url, 'https://');
|
||
$isLocal = is_dir($url);
|
||
$zipMeta = end($metas);
|
||
$latest = $token ? array_slice($listNames, 0, 20) : $added; //zip名表分割或引入前20
|
||
preg_match('/(?<=\[)[^\]]*/', $zipMeta, $zipText);
|
||
$mark = $zipText ? trim($zipText[0]) : ($tf ? 'Download' : '下载'); //取最后一个栏位链接文本
|
||
if ($condition && $token) {
|
||
++$all; //记录检测次数
|
||
|
||
$isPlugin = str_ends_with($url, 'Plugin.php') || str_ends_with($url, $name . '.php'); //直链文件情况
|
||
$plugin = $isPlugin ? str_replace('/blob/', '/raw/', $url) : '';
|
||
$branch = '';
|
||
$api = '';
|
||
$datas = [];
|
||
$infos = [];
|
||
//本地定位主文件路径
|
||
if ($tf && $isLocal) {
|
||
$plugin = pluginRoute($url, $name);
|
||
//远程定位主文件地址
|
||
} elseif ($isUrl) {
|
||
//提取子目录(分支名)
|
||
$paths = $isPlugin
|
||
? preg_split('/\/(?:blob|raw)\/([^\/]+)\//', $url, 2, PREG_SPLIT_DELIM_CAPTURE)
|
||
: preg_split('/\/tree\/([^\/]+)\//', $url, 2, PREG_SPLIT_DELIM_CAPTURE);
|
||
$url = $paths[0];
|
||
$branch = $paths[1] ?? $branch;
|
||
$folder = !empty($paths[2])
|
||
? ($isPlugin
|
||
? str_replace(basename($paths[2]), '', $paths[2])
|
||
: rtrim($paths[2], '/') . '/')
|
||
: '';
|
||
|
||
$gitHosts = $github || $host == 'gitee.com';
|
||
$apiUrl = str_replace(
|
||
['/github.com/', '/gitee.com/'],
|
||
['/api.github.com/repos/', '/api.gitee.com/api/v5/repos/'],
|
||
$url
|
||
);
|
||
//API查询分支名
|
||
if (!$branch) {
|
||
$branch = 'master';
|
||
if ($gitHosts) {
|
||
$api = @file_get_contents(
|
||
$apiUrl,
|
||
0,
|
||
stream_context_create([
|
||
'http' => [
|
||
'header' => ['User-Agent: PHP', 'Authorization: token ' . $token]
|
||
]
|
||
])
|
||
);
|
||
if ($api) {
|
||
$branch = json_decode($api, true)['default_branch'];
|
||
}
|
||
}
|
||
}
|
||
|
||
$path = '';
|
||
$pluginUri = $url . '/raw/' . $branch . '/';
|
||
//API查询repo文件树
|
||
if (!$isPlugin) {
|
||
if ($gitHosts) {
|
||
$api = @file_get_contents(
|
||
$apiUrl . '/git/trees/' . $branch . '?recursive=1',
|
||
0,
|
||
stream_context_create([
|
||
'http' => [
|
||
'header' => ['User-Agent: PHP', 'Authorization: token ' . $token]
|
||
]
|
||
])
|
||
);
|
||
}
|
||
if ($api) {
|
||
$datas = array_column(
|
||
array_filter(
|
||
json_decode($api, true)['tree'],
|
||
fn($item) => $item['type'] === 'blob' //排除目录
|
||
),
|
||
'path'
|
||
);
|
||
//定位主文件路径
|
||
$path = pluginRoute($datas, $name);
|
||
}
|
||
$plugin = $path ? $pluginUri . $path : $pluginUri . $folder . 'Plugin.php'; //盲试目录型
|
||
}
|
||
}
|
||
|
||
//通过表格repo信息读取主文件
|
||
if ($plugin) {
|
||
$infos = parseInfo($plugin);
|
||
//无API盲试单文件
|
||
if (!$infos['version'] && $isUrl && !$path) {
|
||
$plugin = $pluginUri . $folder . $name . '.php';
|
||
$infos = parseInfo($plugin);
|
||
}
|
||
}
|
||
|
||
$noPlugin = empty($infos['version']); //表格repo信息无效
|
||
$gitIsh = !$noPlugin && !$api && !$tf; //有效但无API(盲试)
|
||
$zip = str_contains($zipMeta, '](') ? trim(end($links[0])) : ''; //取最后一个栏位链接地址
|
||
$tmpSub = $tmpDir . '/' . $all . '_' . $name;
|
||
$pluginZip = '';
|
||
//通过解压缩zip包读取主文件
|
||
if ($noPlugin || $gitIsh) {
|
||
$download = @file_get_contents($zip);
|
||
if ($download) {
|
||
$tmpZip = $tmpSub . '_origin.zip';
|
||
file_put_contents($tmpZip, $download);
|
||
$phpZip = new ZipArchive();
|
||
if ($phpZip->open($tmpZip) !== true) {
|
||
//宽松校验
|
||
$logs .= 'Error: Table zip - "' . $zip . '" is not valid!' . PHP_EOL;
|
||
} else {
|
||
mkdir($tmpSub, 0777, true);
|
||
$phpZip->extractTo($tmpSub);
|
||
//定位主文件路径
|
||
$pluginZip = pluginRoute($tmpSub, $name);
|
||
if ($pluginZip && !$gitIsh) {
|
||
$infos = parseInfo($pluginZip);
|
||
}
|
||
}
|
||
} else {
|
||
$logs .= 'Error: Table zip - "' . $zip . '" cannot be downloaded!' . PHP_EOL;
|
||
}
|
||
}
|
||
|
||
//有主文件头信息即修正
|
||
if (!empty($infos['version'])) {
|
||
++$revise; //记录修正次数
|
||
$fixed = '';
|
||
$updated = '';
|
||
|
||
//修正表格插件名与链接
|
||
if ($pluginFile = $pluginZip ?: $plugin) {
|
||
$nameData = workingName($pluginFile);
|
||
}
|
||
$nameFile = $nameData[0] ?? '';
|
||
if ($nameFile) {
|
||
if ($noPlugin) {
|
||
$logs .=
|
||
'Warning: "' .
|
||
($plugin ?: $url) .
|
||
'" is not valid, using "' .
|
||
$zip .
|
||
'" to read info.' .
|
||
PHP_EOL;
|
||
if (!$isUrl && !$tf && !$isLocal) {
|
||
$column = str_replace(
|
||
$nameMeta,
|
||
'[' . $nameFile . '](' . $infos['homepage'] . ')',
|
||
$column
|
||
);
|
||
$fixed .= ' / Table Repo Masked'; //TeStore不显示无文档链接插件
|
||
}
|
||
} elseif ($name !== $nameFile) {
|
||
$logs .=
|
||
'Warning: "' .
|
||
$name .
|
||
'" in table does not match "' .
|
||
$nameFile .
|
||
'" in file.' .
|
||
PHP_EOL;
|
||
$column = str_replace(
|
||
$nameMeta,
|
||
str_replace($name . '](', $nameFile . '](', $nameMeta),
|
||
$column
|
||
);
|
||
$fixed .= ' / Table Name Fixed';
|
||
}
|
||
$name = $nameFile;
|
||
}
|
||
|
||
//处理repo作者名
|
||
$authorInfo = trim(strip_tags($infos['author']));
|
||
preg_match('/[\t ]*(,|&|,)[ \t]*/', $authorInfo, $seps);
|
||
$sep = '';
|
||
if ($seps) {
|
||
$sep = $seps[0];
|
||
$authors = array_map(
|
||
fn($id) => '[' . trim($id) . '](' . $infos['homepage'] . ')',
|
||
explode($sep, $authorInfo)
|
||
);
|
||
}
|
||
|
||
//修正表格作者名与链接
|
||
if ($authorTable !== $authorInfo) {
|
||
$logs .=
|
||
'Warning: "' .
|
||
$authorTable .
|
||
'" in table does not match "' .
|
||
$authorInfo .
|
||
'" in file.' .
|
||
PHP_EOL;
|
||
$column = str_replace(
|
||
$authorMeta,
|
||
$sep
|
||
? implode($sep, $authors)
|
||
: '[' . $authorInfo . '](' . $infos['homepage'] . ')',
|
||
$column
|
||
);
|
||
$fixed .= ' / Table Author Fixed';
|
||
}
|
||
|
||
//创建加速文件夹用zip
|
||
$zipName =
|
||
$name .
|
||
'_' .
|
||
str_replace(
|
||
[':', '"', '/', '\\', '|', '?', '*'],
|
||
'',
|
||
preg_replace('/[\t ]*(,|&|,)[ \t]*/', '_', $authorInfo)
|
||
) .
|
||
'.zip';
|
||
$cdn = 'ZIP_CDN/' . $zipName;
|
||
$params = [
|
||
$tableFile,
|
||
!$noPlugin,
|
||
$url,
|
||
$name,
|
||
$datas,
|
||
$branch,
|
||
$plugin,
|
||
$pluginZip,
|
||
$cdn,
|
||
$zip,
|
||
$all,
|
||
$logs
|
||
];
|
||
$newCdn = false;
|
||
if (!file_exists($cdn)) {
|
||
$newCdn = true;
|
||
$logs = dispatchZips(...$params);
|
||
++$creat;
|
||
$fixed .= ' / CDN Zip Created';
|
||
}
|
||
|
||
//表格版本落后则更新(或强制更新)
|
||
$version = trim($metas[2]); //取第三个栏位文本
|
||
if (version_compare(trim($infos['version']), $version, '>') || !empty($requested)) {
|
||
++$update; //记录更新次数
|
||
|
||
//更新加速文件夹用zip
|
||
if (!$newCdn) {
|
||
dispatchZips(...$params);
|
||
++$renew;
|
||
$fixed .= ' / CDN Zip Renewed';
|
||
}
|
||
|
||
//复制到release发布用
|
||
$isRelease = str_contains($zip, 'typecho-fans/plugins/releases/download');
|
||
if ($isRelease && file_exists($cdn)) {
|
||
copy($cdn, $tmpNew . '/' . basename($zip));
|
||
++$release;
|
||
}
|
||
|
||
//更新表格版本号
|
||
$column = str_replace($version, trim($infos['version']), $column);
|
||
|
||
//更新表格下载标记(用于TeStore筛选)
|
||
if ($mark == 'Download' || $mark == '下载') {
|
||
$newOr = 'Lat';
|
||
$orNew = '近';
|
||
//标记新版写法
|
||
if (!empty($nameData[1])) {
|
||
$newOr = 'New';
|
||
$orNew = '新';
|
||
$fixed .= ' / Marked as 1.2.1+';
|
||
}
|
||
//标记最近更新
|
||
$column = str_replace(
|
||
$zipMeta,
|
||
str_replace($mark, $tf ? $newOr . 'est' : '最' . $orNew, $zipMeta),
|
||
$column
|
||
);
|
||
}
|
||
|
||
$updated = '& Updated';
|
||
++$done; //记录完成次数
|
||
}
|
||
|
||
if ($fixed || $updated) {
|
||
//置顶排序zip名表
|
||
if (in_array($zipName, $listNames)) {
|
||
array_splice($listNames, array_search($zipName, $listNames), 1);
|
||
}
|
||
array_unshift($listNames, $zipName);
|
||
$outName = $latest[19] ?? '';
|
||
$latest = array_slice($listNames, 0, 20);
|
||
//原末位移除标记
|
||
if (
|
||
$outName &&
|
||
!in_array($outName, $latest) &&
|
||
!in_array(explode('_', $outName)[0], $requested)
|
||
) {
|
||
updatePlugins($tableFile, [$outName], '', $latest); //空token情况递归
|
||
updatePlugins($tf ? 'TESTORE.md' : 'README.md', [$outName], '', $latest);
|
||
}
|
||
|
||
//记录插件改动明细
|
||
$logs .=
|
||
$name .
|
||
' By ' .
|
||
$authorInfo .
|
||
' - ' .
|
||
date('Y-m-d H:i', time()) .
|
||
' - Revised ' .
|
||
$updated .
|
||
$fixed .
|
||
PHP_EOL;
|
||
}
|
||
} else {
|
||
$logs .=
|
||
'Error: Table info - "' .
|
||
$url .
|
||
'" & "' .
|
||
$zip .
|
||
'" both invalid, removal is advised!' .
|
||
PHP_EOL;
|
||
}
|
||
}
|
||
|
||
//非前20移除标记
|
||
$latestMark = ['Latest', 'Newest', '最近', '最新'];
|
||
if (in_array($mark, $latestMark) && $latest && !in_array($zipName, $latest)) {
|
||
$column = str_replace(
|
||
$zipMeta,
|
||
str_replace($latestMark, ['Download', 'NewVer', '下载', '新版'], $zipMeta),
|
||
$column
|
||
);
|
||
}
|
||
|
||
if ($normal) {
|
||
//筛出需跨文档转移错位条目
|
||
$tfMark = ['Download', 'N/A', 'Special', 'NewVer', 'Latest', 'Newest'];
|
||
$teMark = ['下载', '不可用', '特殊', '新版', '最近', '最新'];
|
||
if ($tf && $isUrl) {
|
||
$column = str_replace($zipMeta, str_replace($tfMark, $teMark, $zipMeta), $column);
|
||
$movable[] = $column;
|
||
if (is_dir($name)) {
|
||
$logs .=
|
||
'Warning: "' .
|
||
$name .
|
||
'" is local but table info "' .
|
||
$url .
|
||
'" is external.' .
|
||
PHP_EOL;
|
||
}
|
||
} elseif (!$tf && $isLocal) {
|
||
$column = str_replace($zipMeta, str_replace($teMark, $tfMark, $zipMeta), $column);
|
||
$movable[] = $column;
|
||
}
|
||
//收集全部zip名检测重复条目
|
||
$allNames[] = $zipName;
|
||
}
|
||
} else {
|
||
$logs .= 'Error: Line ' . $line . ' matches no plugin name!' . PHP_EOL;
|
||
}
|
||
} else {
|
||
$logs .= $token == 'rec' ? '' : 'Error: Line ' . ($line + 1) . ' matches wrong columns!' . PHP_EOL;
|
||
}
|
||
|
||
$tables[] = $column;
|
||
}
|
||
}
|
||
|
||
//合并所有行排序后重建文档
|
||
$tables = array_unique(array_merge(array_diff($tables, $movable), $token ? $added : []));
|
||
sort($tables);
|
||
file_put_contents($tableFile, implode(PHP_EOL, $descriptions) . PHP_EOL . implode(PHP_EOL, $tables) . PHP_EOL);
|
||
} else {
|
||
$logs .= 'Error: "' . $tableFile . '" matches no table!' . PHP_EOL;
|
||
}
|
||
|
||
if ($normal) {
|
||
//清空临时目录(保留updates.log)
|
||
exec('find "' . $tmpDir . '" -mindepth 1 ! -name "updates.log" -exec rm -rf {} +');
|
||
|
||
//保存zip名表记录
|
||
$listNames = array_filter(array_unique($listNames));
|
||
$allNames = array_filter($allNames);
|
||
if ($tf) {
|
||
$listNames = array_merge($listNames, $allNames); //临时记录全表
|
||
} else {
|
||
if ($outNames = array_diff($listNames, $allNames)) {
|
||
$logs .=
|
||
'Warning: Table info about "' .
|
||
implode(' / ', $outNames) .
|
||
'" will be removed from NAME_LIST.log.' .
|
||
PHP_EOL;
|
||
$listNames = array_intersect($listNames, $allNames); //清理移除条目
|
||
}
|
||
}
|
||
file_put_contents($nameList, implode(PHP_EOL, $listNames));
|
||
|
||
if ($allNames) {
|
||
//检查重复项
|
||
$duplicates = array_keys(array_filter(array_count_values($allNames), fn($count) => $count > 1));
|
||
if ($duplicates) {
|
||
$logs .=
|
||
'Warning: Table info about "' .
|
||
implode(' / ', $duplicates) .
|
||
'" may be added repeatedly.' .
|
||
PHP_EOL;
|
||
}
|
||
//清除冗余zip
|
||
if (!$tf) {
|
||
$allNames = array_merge($allNames, ['NAME_LIST.log', 'README.md']);
|
||
$api = @file_get_contents(
|
||
'https://api.github.com/repositories/14101953/contents/ZIP_CDN',
|
||
0,
|
||
stream_context_create([
|
||
'http' => ['header' => ['User-Agent: PHP', 'Authorization: token ' . $token]]
|
||
])
|
||
);
|
||
if ($api) {
|
||
$datas = json_decode($api, true);
|
||
$extras = array_diff(array_column($datas, 'name'), $allNames);
|
||
if ($extras) {
|
||
$logs .=
|
||
'Warning: These zip files do not match the "name_authors.zip" pattern based on table info and will be deleted: "' .
|
||
implode(' / ', $extras) .
|
||
'"' .
|
||
PHP_EOL;
|
||
foreach ($extras as $extra) {
|
||
if (file_exists('ZIP_CDN/' . $extra)) {
|
||
unlink('ZIP_CDN/' . $extra);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//生成完整的操作日志
|
||
$logFile = $tmpDir . '/updates.log';
|
||
$logs .=
|
||
'SCANED: ' .
|
||
$all .
|
||
PHP_EOL .
|
||
'REVISED: ' .
|
||
$revise .
|
||
PHP_EOL .
|
||
'NEED UPDATE: ' .
|
||
$update .
|
||
PHP_EOL .
|
||
'DONE: ' .
|
||
$done .
|
||
PHP_EOL .
|
||
'ZIPS: Created-' .
|
||
$creat .
|
||
', Renewed-' .
|
||
$renew .
|
||
', Released-' .
|
||
$release .
|
||
PHP_EOL;
|
||
file_put_contents($logFile, $logs, FILE_APPEND | LOCK_EX);
|
||
}
|
||
|
||
return $movable;
|
||
}
|
||
|
||
/**
|
||
* 获取插件主文件路径或文件树数据
|
||
*
|
||
* @param string|array $pluginData 文件夹路径或文件树数据
|
||
* @param string $name 表格插件名
|
||
* @param boolean $needTree 是否返回文件树数据
|
||
* @return string|array
|
||
*/
|
||
function pluginRoute(string|array $pluginData, string $name, bool $needTree = false): string|array
|
||
{
|
||
$plugin = '';
|
||
$routes = is_array($pluginData) ? $pluginData : [];
|
||
|
||
//遍历获取文件树
|
||
if (!$routes) {
|
||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pluginData)) as $files) {
|
||
if (!$files->isDir() && !str_contains($files, '/.git/') && !str_contains($files, '/.github/')) {
|
||
$routes[] = $files->getRealPath();
|
||
}
|
||
}
|
||
}
|
||
//定位主文件路径
|
||
$maxPriority = 0;
|
||
foreach ($routes as $route) {
|
||
//带路径目录型优先
|
||
$priority = match (true) {
|
||
stripos($route, $name . '/Plugin.php') !== false => 4, //兼容下小写
|
||
str_contains($route, $name . '/' . $name . '.php') => 3,
|
||
stripos($route, 'Plugin.php') !== false => 2,
|
||
str_contains($route, $name . '.php') => 1,
|
||
default => 0
|
||
};
|
||
if ($priority > $maxPriority) {
|
||
$maxPriority = $priority;
|
||
$plugin = $route;
|
||
if ($priority == 4) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $needTree ? $routes : $plugin;
|
||
}
|
||
|
||
/**
|
||
* 获取插件文件的头信息 (Typecho)
|
||
*
|
||
* @param string $pluginFile 主文件路径或地址
|
||
* @return array
|
||
*/
|
||
function parseInfo(string $pluginFile): array
|
||
{
|
||
$codes = @file_get_contents($pluginFile);
|
||
$tokens = $codes ? token_get_all($codes) : [];
|
||
|
||
/** 初始信息 */
|
||
$info = [
|
||
'title' => '',
|
||
'author' => '',
|
||
'homepage' => '',
|
||
'version' => '',
|
||
'since' => ''
|
||
];
|
||
|
||
$map = [
|
||
'package' => 'title',
|
||
'author' => 'author',
|
||
'link' => 'homepage',
|
||
'since' => 'since',
|
||
'version' => 'version'
|
||
];
|
||
|
||
foreach ($tokens as $token) {
|
||
/** 获取doc comment */
|
||
if (is_array($token) && T_DOC_COMMENT == $token[0]) {
|
||
/** 分行读取 */
|
||
$lines = preg_split('/(\r|\n)/', $token[1]);
|
||
foreach ($lines as $line) {
|
||
$line = trim($line);
|
||
if (!empty($line) && '*' == $line[0]) {
|
||
$line = trim(substr($line, 1));
|
||
|
||
if (!empty($line) && '@' == $line[0]) {
|
||
$line = trim(substr($line, 1));
|
||
$args = explode(' ', $line);
|
||
$key = array_shift($args);
|
||
|
||
if (isset($map[$key])) {
|
||
$info[$map[$key]] = trim(implode(' ', $args));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $info;
|
||
}
|
||
|
||
/**
|
||
* 获取插件文件的有效命名
|
||
*
|
||
* @param string $pluginFile 主文件路径或地址
|
||
* @return array
|
||
*/
|
||
function workingName(string $pluginFile): array
|
||
{
|
||
$codes = @file_get_contents($pluginFile);
|
||
$tokens = $codes ? token_get_all($codes) : [];
|
||
$count = count($tokens);
|
||
|
||
$namespace = '';
|
||
$classes = '';
|
||
if ($tokens) {
|
||
for ($i = 0; $i < $count; $i++) {
|
||
if ($tokens[$i][0] === T_NAMESPACE) {
|
||
for ($j = $i + 1; $j < $count; ++$j) {
|
||
if ($tokens[$j][0] === T_NAME_QUALIFIED) {
|
||
$namespace = substr($tokens[$j][1], 14);
|
||
} elseif ($tokens[$j] === '{' || $tokens[$j] === ';') {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if ($tokens[$i][0] === T_CLASS) {
|
||
for ($j = $i + 1; $j < $count; ++$j) {
|
||
if ($tokens[$j] === '{') {
|
||
$classes = $namespace . (!$namespace ? $tokens[$i + 2][1] : '');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return [str_replace('_Plugin', '', $classes), !empty($namespace)];
|
||
}
|
||
|
||
/**
|
||
* 下载或重新打包加速用zip
|
||
*
|
||
* @param string $md Markdown文档路径
|
||
* @param boolean $bingo 表格repo信息有效
|
||
* @param string $url 表格repo链接
|
||
* @param string $name 插件有效命名
|
||
* @param array $datas API文件树数据(Gitee)
|
||
* @param string $branch repo有效分支名
|
||
* @param string $plugin repo有效主文件地址
|
||
* @param string $pluginZip 解包有效主文件路径
|
||
* @param string $cdn 加速用zip文件路径
|
||
* @param string $zip 表格zip地址
|
||
* @param integer $index 循环次数序号
|
||
* @param string $logs 已记录日志
|
||
* @return string
|
||
*/
|
||
function dispatchZips(
|
||
string $md,
|
||
bool $bingo,
|
||
string $url,
|
||
string $name,
|
||
array $datas,
|
||
string $branch,
|
||
string $plugin,
|
||
string $pluginZip,
|
||
string $cdn,
|
||
string $zip,
|
||
int $index,
|
||
string $logs
|
||
): string {
|
||
$host = parse_url($url, PHP_URL_HOST);
|
||
$github = $host == 'github.com';
|
||
$folder = realpath('../') . '/TMP/' . $index . '_' . $name;
|
||
$tf = $md == 'README.md';
|
||
$tfLocal = $tf && is_dir($url);
|
||
if (!is_dir($folder) && !$tfLocal) {
|
||
mkdir($folder, 0777, true);
|
||
}
|
||
|
||
//重新打包到加速文件夹
|
||
if ($bingo && !$github) {
|
||
if ($host == 'gitee.com') {
|
||
if (count($datas) <= 30) {
|
||
foreach ($datas as $data) {
|
||
if (!str_contains($data, '.gitignore') && !str_contains($data, '/.github/')) {
|
||
$plugin = $url . '/raw/' . $branch . '/' . $data;
|
||
$download = @file_get_contents($plugin);
|
||
if ($download) {
|
||
$path = $folder . '/' . $data;
|
||
if (!is_dir(dirname($path))) {
|
||
mkdir(dirname($path), 0777, true);
|
||
}
|
||
file_put_contents($path, $download); //逐一抓取文件
|
||
} else {
|
||
$logs .= 'Error: Plugin file - "' . $plugin . '" cannot be downloaded!' . PHP_EOL;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
$logs .= 'Error: Gitee API - Too many files, please upload the zip manually!' . PHP_EOL;
|
||
}
|
||
//即$gitIsh已解包
|
||
} elseif (!$datas && !$tf) {
|
||
$download = @file_get_contents($plugin); //只能取主文件
|
||
$path = $pluginZip ?: $folder . '/' . basename($plugin);
|
||
if (!is_dir(dirname($path))) {
|
||
mkdir(dirname($path), 0777, true);
|
||
}
|
||
if ($download) {
|
||
file_put_contents($path, $download); //覆盖解包位置
|
||
}
|
||
//社区版就地打包
|
||
} elseif ($tfLocal) {
|
||
$folder = realpath($url);
|
||
}
|
||
$phpZip = new ZipArchive();
|
||
if ($phpZip->open($cdn, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||
$logs .= 'Error: Packing zip - "' . $cdn . '" failed to create files!' . PHP_EOL;
|
||
} else {
|
||
$filePaths = pluginRoute($folder, $name, true);
|
||
foreach ($filePaths as $filePath) {
|
||
$phpZip->addFile($filePath, $name . substr($filePath, strlen($folder)));
|
||
}
|
||
$phpZip->close();
|
||
}
|
||
//直接下载到加速文件夹
|
||
} else {
|
||
$zip = $bingo && $github ? $url . '/archive/' . $branch . '.zip' : $zip;
|
||
$download = @file_get_contents($zip);
|
||
if ($download) {
|
||
file_put_contents($cdn, $download);
|
||
} else {
|
||
$logs .= 'Error: Source zip - "' . $zip . '" cannot be downloaded!' . PHP_EOL;
|
||
}
|
||
if ($tf) {
|
||
$logs .= 'Warning: Local "' . $url . '" is not valid, using "' . $zip . '" for download.' . PHP_EOL;
|
||
}
|
||
}
|
||
|
||
return $logs;
|
||
}
|