要提交的变更: 修改: README.md 新文件: README_zh-CN.md 新文件: assets/CHANGELOG.md 新文件: assets/Parsedown.php 新文件: assets/css/login.css 新文件: assets/css/manage.css 新文件: assets/defaultConfig.json 新文件: assets/defaultIconList.json 新文件: assets/html/favicon.ico 新文件: assets/html/login.html 新文件: assets/html/manage.html 新文件: assets/js/console.js 新文件: assets/js/manage.js 新文件: assets/opencc/composer.json 新文件: assets/opencc/composer.lock 新文件: assets/opencc/vendor/autoload.php 新文件: assets/opencc/vendor/bin/opencc 新文件: assets/opencc/vendor/composer/ClassLoader.php 新文件: assets/opencc/vendor/composer/InstalledVersions.php 新文件: assets/opencc/vendor/composer/LICENSE 新文件: assets/opencc/vendor/composer/autoload_classmap.php 新文件: assets/opencc/vendor/composer/autoload_files.php 新文件: assets/opencc/vendor/composer/autoload_namespaces.php 新文件: assets/opencc/vendor/composer/autoload_psr4.php 新文件: assets/opencc/vendor/composer/autoload_real.php 新文件: assets/opencc/vendor/composer/autoload_static.php 新文件: assets/opencc/vendor/composer/installed.json 新文件: assets/opencc/vendor/composer/installed.php 新文件: assets/opencc/vendor/composer/platform_check.php 新文件: assets/opencc/vendor/overtrue/php-opencc/.editorconfig 新文件: assets/opencc/vendor/overtrue/php-opencc/.github/FUNDING.yml 新文件: assets/opencc/vendor/overtrue/php-opencc/.github/workflows/test.yml 新文件: assets/opencc/vendor/overtrue/php-opencc/LICENSE 新文件: assets/opencc/vendor/overtrue/php-opencc/README.md 新文件: assets/opencc/vendor/overtrue/php-opencc/bin/build 新文件: assets/opencc/vendor/overtrue/php-opencc/bin/opencc 新文件: assets/opencc/vendor/overtrue/php-opencc/composer.json 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/HKVariants.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/HKVariantsRevPhrases.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/JPShinjitaiCharacters.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/JPShinjitaiPhrases.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/JPVariants.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/README.md 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/STCharacters.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/STPhrases.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TSCharacters.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TSPhrases.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TWPhrasesIT.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TWPhrasesName.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TWPhrasesOther.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TWVariants.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/dictionary/TWVariantsRevPhrases.txt 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/HKVariants.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/HKVariantsRev.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/HKVariantsRevPhrases.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/JPShinjitaiCharacters.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/JPShinjitaiPhrases.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/JPVariants.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/JPVariantsRev.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/STCharacters.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/STPhrases.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TSCharacters.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TSPhrases.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWPhrases.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWPhrasesIT.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWPhrasesName.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWPhrasesOther.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWPhrasesRev.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWVariants.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWVariantsRev.php 新文件: assets/opencc/vendor/overtrue/php-opencc/data/parsed/TWVariantsRevPhrases.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/Console/BuildCommand.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/Console/ConvertCommand.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/Contracts/ConverterInterface.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/Converter.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/Dictionary.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/OpenCC.php 新文件: assets/opencc/vendor/overtrue/php-opencc/src/Strategy.php 新文件: assets/opencc/vendor/psr/container/.gitignore 新文件: assets/opencc/vendor/psr/container/LICENSE 新文件: assets/opencc/vendor/psr/container/README.md 新文件: assets/opencc/vendor/psr/container/composer.json 新文件: assets/opencc/vendor/psr/container/src/ContainerExceptionInterface.php 新文件: assets/opencc/vendor/psr/container/src/ContainerInterface.php 新文件: assets/opencc/vendor/psr/container/src/NotFoundExceptionInterface.php 新文件: assets/opencc/vendor/symfony/console/Application.php 新文件: assets/opencc/vendor/symfony/console/Attribute/AsCommand.php 新文件: assets/opencc/vendor/symfony/console/CHANGELOG.md 新文件: assets/opencc/vendor/symfony/console/CI/GithubActionReporter.php 新文件: assets/opencc/vendor/symfony/console/Color.php 新文件: assets/opencc/vendor/symfony/console/Command/Command.php 新文件: assets/opencc/vendor/symfony/console/Command/CompleteCommand.php 新文件: assets/opencc/vendor/symfony/console/Command/DumpCompletionCommand.php 新文件: assets/opencc/vendor/symfony/console/Command/HelpCommand.php 新文件: assets/opencc/vendor/symfony/console/Command/LazyCommand.php 新文件: assets/opencc/vendor/symfony/console/Command/ListCommand.php 新文件: assets/opencc/vendor/symfony/console/Command/LockableTrait.php 新文件: assets/opencc/vendor/symfony/console/Command/SignalableCommandInterface.php 新文件: assets/opencc/vendor/symfony/console/Command/TraceableCommand.php 新文件: assets/opencc/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php 新文件: assets/opencc/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php 新文件: assets/opencc/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php 新文件: assets/opencc/vendor/symfony/console/Completion/CompletionInput.php 新文件: assets/opencc/vendor/symfony/console/Completion/CompletionSuggestions.php 新文件: assets/opencc/vendor/symfony/console/Completion/Output/BashCompletionOutput.php 新文件: assets/opencc/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php 新文件: assets/opencc/vendor/symfony/console/Completion/Output/FishCompletionOutput.php 新文件: assets/opencc/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php 新文件: assets/opencc/vendor/symfony/console/Completion/Suggestion.php 新文件: assets/opencc/vendor/symfony/console/ConsoleEvents.php 新文件: assets/opencc/vendor/symfony/console/Cursor.php 新文件: assets/opencc/vendor/symfony/console/DataCollector/CommandDataCollector.php 新文件: assets/opencc/vendor/symfony/console/Debug/CliRequest.php 新文件: assets/opencc/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/ApplicationDescription.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/Descriptor.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/DescriptorInterface.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/JsonDescriptor.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/MarkdownDescriptor.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/TextDescriptor.php 新文件: assets/opencc/vendor/symfony/console/Descriptor/XmlDescriptor.php 新文件: assets/opencc/vendor/symfony/console/Event/ConsoleCommandEvent.php 新文件: assets/opencc/vendor/symfony/console/Event/ConsoleErrorEvent.php 新文件: assets/opencc/vendor/symfony/console/Event/ConsoleEvent.php 新文件: assets/opencc/vendor/symfony/console/Event/ConsoleSignalEvent.php 新文件: assets/opencc/vendor/symfony/console/Event/ConsoleTerminateEvent.php 新文件: assets/opencc/vendor/symfony/console/EventListener/ErrorListener.php 新文件: assets/opencc/vendor/symfony/console/Exception/CommandNotFoundException.php 新文件: assets/opencc/vendor/symfony/console/Exception/ExceptionInterface.php 新文件: assets/opencc/vendor/symfony/console/Exception/InvalidArgumentException.php 新文件: assets/opencc/vendor/symfony/console/Exception/InvalidOptionException.php 新文件: assets/opencc/vendor/symfony/console/Exception/LogicException.php 新文件: assets/opencc/vendor/symfony/console/Exception/MissingInputException.php 新文件: assets/opencc/vendor/symfony/console/Exception/NamespaceNotFoundException.php 新文件: assets/opencc/vendor/symfony/console/Exception/RunCommandFailedException.php 新文件: assets/opencc/vendor/symfony/console/Exception/RuntimeException.php 新文件: assets/opencc/vendor/symfony/console/Formatter/NullOutputFormatter.php 新文件: assets/opencc/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php 新文件: assets/opencc/vendor/symfony/console/Formatter/OutputFormatter.php 新文件: assets/opencc/vendor/symfony/console/Formatter/OutputFormatterInterface.php 新文件: assets/opencc/vendor/symfony/console/Formatter/OutputFormatterStyle.php 新文件: assets/opencc/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php 新文件: assets/opencc/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php 新文件: assets/opencc/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php 新文件: assets/opencc/vendor/symfony/console/Helper/DebugFormatterHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/DescriptorHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/Dumper.php 新文件: assets/opencc/vendor/symfony/console/Helper/FormatterHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/Helper.php 新文件: assets/opencc/vendor/symfony/console/Helper/HelperInterface.php 新文件: assets/opencc/vendor/symfony/console/Helper/HelperSet.php 新文件: assets/opencc/vendor/symfony/console/Helper/InputAwareHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/OutputWrapper.php 新文件: assets/opencc/vendor/symfony/console/Helper/ProcessHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/ProgressBar.php 新文件: assets/opencc/vendor/symfony/console/Helper/ProgressIndicator.php 新文件: assets/opencc/vendor/symfony/console/Helper/QuestionHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/SymfonyQuestionHelper.php 新文件: assets/opencc/vendor/symfony/console/Helper/Table.php 新文件: assets/opencc/vendor/symfony/console/Helper/TableCell.php 新文件: assets/opencc/vendor/symfony/console/Helper/TableCellStyle.php 新文件: assets/opencc/vendor/symfony/console/Helper/TableRows.php 新文件: assets/opencc/vendor/symfony/console/Helper/TableSeparator.php 新文件: assets/opencc/vendor/symfony/console/Helper/TableStyle.php 新文件: assets/opencc/vendor/symfony/console/Input/ArgvInput.php 新文件: assets/opencc/vendor/symfony/console/Input/ArrayInput.php 新文件: assets/opencc/vendor/symfony/console/Input/Input.php 新文件: assets/opencc/vendor/symfony/console/Input/InputArgument.php 新文件: assets/opencc/vendor/symfony/console/Input/InputAwareInterface.php 新文件: assets/opencc/vendor/symfony/console/Input/InputDefinition.php 新文件: assets/opencc/vendor/symfony/console/Input/InputInterface.php 新文件: assets/opencc/vendor/symfony/console/Input/InputOption.php 新文件: assets/opencc/vendor/symfony/console/Input/StreamableInputInterface.php 新文件: assets/opencc/vendor/symfony/console/Input/StringInput.php 新文件: assets/opencc/vendor/symfony/console/LICENSE 新文件: assets/opencc/vendor/symfony/console/Logger/ConsoleLogger.php 新文件: assets/opencc/vendor/symfony/console/Messenger/RunCommandContext.php 新文件: assets/opencc/vendor/symfony/console/Messenger/RunCommandMessage.php 新文件: assets/opencc/vendor/symfony/console/Messenger/RunCommandMessageHandler.php 新文件: assets/opencc/vendor/symfony/console/Output/AnsiColorMode.php 新文件: assets/opencc/vendor/symfony/console/Output/BufferedOutput.php 新文件: assets/opencc/vendor/symfony/console/Output/ConsoleOutput.php 新文件: assets/opencc/vendor/symfony/console/Output/ConsoleOutputInterface.php 新文件: assets/opencc/vendor/symfony/console/Output/ConsoleSectionOutput.php 新文件: assets/opencc/vendor/symfony/console/Output/NullOutput.php 新文件: assets/opencc/vendor/symfony/console/Output/Output.php 新文件: assets/opencc/vendor/symfony/console/Output/OutputInterface.php 新文件: assets/opencc/vendor/symfony/console/Output/StreamOutput.php 新文件: assets/opencc/vendor/symfony/console/Output/TrimmedBufferOutput.php 新文件: assets/opencc/vendor/symfony/console/Question/ChoiceQuestion.php 新文件: assets/opencc/vendor/symfony/console/Question/ConfirmationQuestion.php 新文件: assets/opencc/vendor/symfony/console/Question/Question.php 新文件: assets/opencc/vendor/symfony/console/README.md 新文件: assets/opencc/vendor/symfony/console/Resources/bin/hiddeninput.exe 新文件: assets/opencc/vendor/symfony/console/Resources/completion.bash 新文件: assets/opencc/vendor/symfony/console/Resources/completion.fish 新文件: assets/opencc/vendor/symfony/console/Resources/completion.zsh 新文件: assets/opencc/vendor/symfony/console/SignalRegistry/SignalMap.php 新文件: assets/opencc/vendor/symfony/console/SignalRegistry/SignalRegistry.php 新文件: assets/opencc/vendor/symfony/console/SingleCommandApplication.php 新文件: assets/opencc/vendor/symfony/console/Style/OutputStyle.php 新文件: assets/opencc/vendor/symfony/console/Style/StyleInterface.php 新文件: assets/opencc/vendor/symfony/console/Style/SymfonyStyle.php 新文件: assets/opencc/vendor/symfony/console/Terminal.php 新文件: assets/opencc/vendor/symfony/console/Tester/ApplicationTester.php 新文件: assets/opencc/vendor/symfony/console/Tester/CommandCompletionTester.php 新文件: assets/opencc/vendor/symfony/console/Tester/CommandTester.php 新文件: assets/opencc/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php 新文件: assets/opencc/vendor/symfony/console/Tester/TesterTrait.php 新文件: assets/opencc/vendor/symfony/console/composer.json 新文件: assets/opencc/vendor/symfony/deprecation-contracts/CHANGELOG.md 新文件: assets/opencc/vendor/symfony/deprecation-contracts/LICENSE 新文件: assets/opencc/vendor/symfony/deprecation-contracts/README.md 新文件: assets/opencc/vendor/symfony/deprecation-contracts/composer.json 新文件: assets/opencc/vendor/symfony/deprecation-contracts/function.php 新文件: assets/opencc/vendor/symfony/polyfill-ctype/Ctype.php 新文件: assets/opencc/vendor/symfony/polyfill-ctype/LICENSE 新文件: assets/opencc/vendor/symfony/polyfill-ctype/README.md 新文件: assets/opencc/vendor/symfony/polyfill-ctype/bootstrap.php 新文件: assets/opencc/vendor/symfony/polyfill-ctype/bootstrap80.php 新文件: assets/opencc/vendor/symfony/polyfill-ctype/composer.json 新文件: assets/opencc/vendor/symfony/polyfill-intl-grapheme/Grapheme.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-grapheme/LICENSE 新文件: assets/opencc/vendor/symfony/polyfill-intl-grapheme/README.md 新文件: assets/opencc/vendor/symfony/polyfill-intl-grapheme/bootstrap.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-grapheme/composer.json 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/LICENSE 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/Normalizer.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/README.md 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/bootstrap.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php 新文件: assets/opencc/vendor/symfony/polyfill-intl-normalizer/composer.json 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/LICENSE 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/Mbstring.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/README.md 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/bootstrap.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/bootstrap80.php 新文件: assets/opencc/vendor/symfony/polyfill-mbstring/composer.json 新文件: assets/opencc/vendor/symfony/process/CHANGELOG.md 新文件: assets/opencc/vendor/symfony/process/Exception/ExceptionInterface.php 新文件: assets/opencc/vendor/symfony/process/Exception/InvalidArgumentException.php 新文件: assets/opencc/vendor/symfony/process/Exception/LogicException.php 新文件: assets/opencc/vendor/symfony/process/Exception/ProcessFailedException.php 新文件: assets/opencc/vendor/symfony/process/Exception/ProcessSignaledException.php 新文件: assets/opencc/vendor/symfony/process/Exception/ProcessTimedOutException.php 新文件: assets/opencc/vendor/symfony/process/Exception/RunProcessFailedException.php 新文件: assets/opencc/vendor/symfony/process/Exception/RuntimeException.php 新文件: assets/opencc/vendor/symfony/process/ExecutableFinder.php 新文件: assets/opencc/vendor/symfony/process/InputStream.php 新文件: assets/opencc/vendor/symfony/process/LICENSE 新文件: assets/opencc/vendor/symfony/process/Messenger/RunProcessContext.php 新文件: assets/opencc/vendor/symfony/process/Messenger/RunProcessMessage.php 新文件: assets/opencc/vendor/symfony/process/Messenger/RunProcessMessageHandler.php 新文件: assets/opencc/vendor/symfony/process/PhpExecutableFinder.php 新文件: assets/opencc/vendor/symfony/process/PhpProcess.php 新文件: assets/opencc/vendor/symfony/process/PhpSubprocess.php 新文件: assets/opencc/vendor/symfony/process/Pipes/AbstractPipes.php 新文件: assets/opencc/vendor/symfony/process/Pipes/PipesInterface.php 新文件: assets/opencc/vendor/symfony/process/Pipes/UnixPipes.php 新文件: assets/opencc/vendor/symfony/process/Pipes/WindowsPipes.php 新文件: assets/opencc/vendor/symfony/process/Process.php 新文件: assets/opencc/vendor/symfony/process/ProcessUtils.php 新文件: assets/opencc/vendor/symfony/process/README.md 新文件: assets/opencc/vendor/symfony/process/composer.json 新文件: assets/opencc/vendor/symfony/service-contracts/Attribute/Required.php 新文件: assets/opencc/vendor/symfony/service-contracts/Attribute/SubscribedService.php 新文件: assets/opencc/vendor/symfony/service-contracts/CHANGELOG.md 新文件: assets/opencc/vendor/symfony/service-contracts/LICENSE 新文件: assets/opencc/vendor/symfony/service-contracts/README.md 新文件: assets/opencc/vendor/symfony/service-contracts/ResetInterface.php 新文件: assets/opencc/vendor/symfony/service-contracts/ServiceCollectionInterface.php 新文件: assets/opencc/vendor/symfony/service-contracts/ServiceLocatorTrait.php 新文件: assets/opencc/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php 新文件: assets/opencc/vendor/symfony/service-contracts/ServiceProviderInterface.php 新文件: assets/opencc/vendor/symfony/service-contracts/ServiceSubscriberInterface.php 新文件: assets/opencc/vendor/symfony/service-contracts/ServiceSubscriberTrait.php 新文件: assets/opencc/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php 新文件: assets/opencc/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php 新文件: assets/opencc/vendor/symfony/service-contracts/composer.json 新文件: assets/opencc/vendor/symfony/string/AbstractString.php 新文件: assets/opencc/vendor/symfony/string/AbstractUnicodeString.php 新文件: assets/opencc/vendor/symfony/string/ByteString.php 新文件: assets/opencc/vendor/symfony/string/CHANGELOG.md 新文件: assets/opencc/vendor/symfony/string/CodePointString.php 新文件: assets/opencc/vendor/symfony/string/Exception/ExceptionInterface.php 新文件: assets/opencc/vendor/symfony/string/Exception/InvalidArgumentException.php 新文件: assets/opencc/vendor/symfony/string/Exception/RuntimeException.php 新文件: assets/opencc/vendor/symfony/string/Inflector/EnglishInflector.php 新文件: assets/opencc/vendor/symfony/string/Inflector/FrenchInflector.php 新文件: assets/opencc/vendor/symfony/string/Inflector/InflectorInterface.php 新文件: assets/opencc/vendor/symfony/string/LICENSE 新文件: assets/opencc/vendor/symfony/string/LazyString.php 新文件: assets/opencc/vendor/symfony/string/README.md 新文件: assets/opencc/vendor/symfony/string/Resources/data/wcswidth_table_wide.php 新文件: assets/opencc/vendor/symfony/string/Resources/data/wcswidth_table_zero.php 新文件: assets/opencc/vendor/symfony/string/Resources/functions.php 新文件: assets/opencc/vendor/symfony/string/Slugger/AsciiSlugger.php 新文件: assets/opencc/vendor/symfony/string/Slugger/SluggerInterface.php 新文件: assets/opencc/vendor/symfony/string/UnicodeString.php 新文件: assets/opencc/vendor/symfony/string/composer.json 新文件: assets/phpliteadmin.php 新文件: config/Caddyfile 新文件: config/nginx.conf 新文件: cron.php 新文件: cron/requirements.txt 新文件: cron/update.py 新文件: index.php 新文件: manage.php 新文件: public.php 新文件: update.php
588 lines
22 KiB
PHP
588 lines
22 KiB
PHP
<?php
|
||
/**
|
||
* @file update.php
|
||
* @brief 更新脚本
|
||
*
|
||
* 该脚本用于定期从配置的 XML 源下载节目数据,并将其存入 SQLite 数据库中。
|
||
*
|
||
* 作者: Tak
|
||
* GitHub: https://github.com/taksssss/EPG-Server
|
||
* 二次开发: mxdabc
|
||
* Github: https://github.com/mxdabc/epgphp
|
||
*/
|
||
|
||
// 禁用 PHP 输出缓冲
|
||
ob_implicit_flush(true);
|
||
@ob_end_flush();
|
||
|
||
// 设置 header,防止浏览器缓存输出
|
||
header('X-Accel-Buffering: no');
|
||
|
||
// 显示 favicon
|
||
echo '<link rel="icon" href="assets/html/favicon.ico" type="image/x-icon">';
|
||
echo '<title>更新数据</title>';
|
||
|
||
// 引入公共脚本
|
||
require_once 'public.php';
|
||
|
||
ini_set('memory_limit', '256M');
|
||
|
||
// 设置超时时间为20分钟
|
||
set_time_limit(20*60);
|
||
|
||
// 删除过期数据和日志
|
||
function deleteOldData($db, &$log_messages) {
|
||
global $Config, $thresholdDate;
|
||
|
||
// 删除 t.xml 和 t.xml.gz 文件
|
||
@unlink(__DIR__ . '/t.xml');
|
||
@unlink(__DIR__ . '/t.xml.gz');
|
||
|
||
logMessage($log_messages, "[Start] 开始生成数据...");
|
||
|
||
echo "<strong>请注意:在生成数据期间无法访问XMLTV文件和API接口</strong>";
|
||
echo "<br>";
|
||
|
||
// 循环清理过期数据
|
||
$tables = [
|
||
'epg_data' => ['date', '清理EPG数据'],
|
||
'update_log' => ['timestamp', '清理更新日志'],
|
||
'cron_log' => ['timestamp', '清理定时日志']
|
||
];
|
||
foreach ($tables as $table => $values) {
|
||
list($column, $logMessage) = $values;
|
||
$stmt = $db->prepare("DELETE FROM $table WHERE $column < :thresholdDate");
|
||
$stmt->bindValue(':thresholdDate', $thresholdDate, PDO::PARAM_STR);
|
||
$stmt->execute();
|
||
logMessage($log_messages, "[{$logMessage}] 共 {$stmt->rowCount()} 条。");
|
||
}
|
||
|
||
// 清理 memcached 数据
|
||
if (class_exists('Memcached')) {
|
||
$memcached = new Memcached();
|
||
if ($memcached->addServer('127.0.0.1', 11211)) {
|
||
$memcached->flush();
|
||
logMessage($log_messages, "[Memcached] 已清空。");
|
||
} else {
|
||
logMessage($log_messages, "[Memcached] 状态异常。");
|
||
}
|
||
} else {
|
||
logMessage($log_messages, "[Memcached] 未安装。");
|
||
}
|
||
|
||
// 清理 redis 数据
|
||
if (class_exists('Redis')) {
|
||
$redis = new Redis();
|
||
try {
|
||
$redis->connect('127.0.0.1', 6379);
|
||
if (!empty($Config['redis_password'])) {
|
||
$redis->auth($Config['redis_password']);
|
||
}
|
||
$redis->flushAll();
|
||
logMessage($log_messages, "[Redis] 已清空。");
|
||
} catch (Exception $e) {
|
||
logMessage($log_messages, "[Redis] 状态异常:" . $e->getMessage());
|
||
}
|
||
} else {
|
||
logMessage($log_messages, "[Redis] 未安装。");
|
||
}
|
||
|
||
echo "<br>";
|
||
}
|
||
|
||
// 格式化时间函数,同时转化为 UTC+8 时间
|
||
function getFormatTime($time, $overwrite_time_zone) {
|
||
if (empty($time)) return ['', ''];
|
||
$time = $overwrite_time_zone ? substr($time, 0, -5) . $overwrite_time_zone : $time;
|
||
$time = str_replace(' ', '', $time);
|
||
$datetime = DateTime::createFromFormat('YmdHisO', $time);
|
||
if (!$datetime) return [null, null];
|
||
$datetime->setTimezone(new DateTimeZone('+0800'));
|
||
return [$datetime->format('Y-m-d'), $datetime->format('H:i')];
|
||
}
|
||
|
||
// 辅助函数:将日期和时间格式化为 XMLTV 格式
|
||
function formatTime($date, $time) {
|
||
return date('YmdHis O', strtotime("$date $time"));
|
||
}
|
||
|
||
// 获取限定频道列表及映射关系
|
||
function getGenList($db) {
|
||
global $Config;
|
||
$channels = $db->query("SELECT channel FROM gen_list")->fetchAll(PDO::FETCH_COLUMN);
|
||
if (empty($channels)) {
|
||
return ['gen_list_mapping' => [], 'gen_list' => []];
|
||
}
|
||
|
||
$channelsSimplified = explode("\n", t2s(implode("\n", $channels)));
|
||
$allEpgChannels = $db->query("SELECT DISTINCT channel FROM epg_data WHERE date = DATE('now')")
|
||
->fetchAll(PDO::FETCH_COLUMN); // 避免匹配只有历史 EPG 的频道
|
||
|
||
$gen_list_mapping = [];
|
||
$cleanedChannels = array_map('cleanChannelName', $channelsSimplified);
|
||
|
||
foreach ($cleanedChannels as $index => $cleanedChannel) {
|
||
$bestMatch = $cleanedChannel; // 默认使用清理后的频道名
|
||
$bestMatchLength = 0; // 初始为0,表示未找到任何匹配
|
||
|
||
foreach ($allEpgChannels as $epgChannel) {
|
||
if (strcasecmp($cleanedChannel, $epgChannel) === 0) {
|
||
$bestMatch = $epgChannel;
|
||
break; // 精确匹配,立即跳出循环
|
||
}
|
||
|
||
// 模糊匹配并选择最长的频道名称
|
||
if ((stripos($epgChannel, $cleanedChannel) === 0 || stripos($cleanedChannel, $epgChannel) !== false)
|
||
&& strlen($epgChannel) > $bestMatchLength) {
|
||
$bestMatch = $epgChannel;
|
||
$bestMatchLength = strlen($epgChannel); // 更新为更长的匹配
|
||
}
|
||
}
|
||
|
||
// 将原始频道名称添加到映射数组中
|
||
$gen_list_mapping[$bestMatch][] = $channels[$index];
|
||
}
|
||
|
||
return [
|
||
'gen_list_mapping' => $gen_list_mapping,
|
||
'gen_list' => array_unique($cleanedChannels)
|
||
];
|
||
}
|
||
|
||
// 获取频道指定 EPG 关系
|
||
function getChannelBindEPG() {
|
||
global $Config;
|
||
$channelBindEPG = [];
|
||
foreach ($Config['channel_bind_epg'] ?? [] as $epg_src => $channels) {
|
||
foreach (array_map('trim', explode(',', $channels)) as $channel) {
|
||
$channelBindEPG[$channel][] = $epg_src;
|
||
}
|
||
}
|
||
return $channelBindEPG;
|
||
}
|
||
|
||
// 下载 XML 数据并存入数据库
|
||
function downloadXmlData($xml_url, $userAgent, $db, &$log_messages, $gen_list) {
|
||
global $Config;
|
||
$xml_data = downloadData($xml_url, $userAgent);
|
||
if ($xml_data !== false && stripos($xml_data, 'not found') === false) {
|
||
if (substr($xml_data, 0, 2) === "\x1F\x8B") { // 通过魔数判断 .gz 文件
|
||
$xml_data = gzdecode($xml_data);
|
||
if ($xml_data === false) {
|
||
logMessage($log_messages, ' [解压缩失败!!!]');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 获取文件大小(字节)并转换为 KB/MB
|
||
$fileSize = strlen($xml_data);
|
||
$fileSizeReadable = $fileSize >= 1048576
|
||
? round($fileSize / 1048576, 2) . ' MB'
|
||
: round($fileSize / 1024, 2) . ' KB';
|
||
logMessage($log_messages, "[下载] 成功: xml 文件 {$fileSizeReadable}");
|
||
|
||
$xml_data = preg_replace('/[\x00-\x1F]/u', ' ', $xml_data); // 清除所有控制字符
|
||
if (isset($Config['all_chs']) && $Config['all_chs']) { $xml_data = t2s($xml_data); }
|
||
$db->beginTransaction();
|
||
try {
|
||
$processCount = processXmlData($xml_url, $xml_data, $db, $gen_list);
|
||
$db->commit();
|
||
logMessage($log_messages, "[更新] 成功:共 {$processCount} 条");
|
||
} catch (Exception $e) {
|
||
$db->rollBack();
|
||
logMessage($log_messages, "[处理数据出错], 错误原因: " . $e->getMessage());
|
||
}
|
||
} else {
|
||
logMessage($log_messages, "[下载EPG数据] 失败!!!");
|
||
}
|
||
echo "<br>";
|
||
}
|
||
|
||
// 处理 XML 数据并逐步存入数据库
|
||
function processXmlData($xml_url, $xml_data, $db, $gen_list) {
|
||
global $Config, $processedRecords, $channel_bind_epg, $thresholdDate;
|
||
|
||
// 统计处理数据量
|
||
$processCount = 0;
|
||
|
||
$reader = new XMLReader();
|
||
if (!$reader->XML($xml_data)) {
|
||
throw new Exception("无法解析 XML 数据");
|
||
}
|
||
|
||
$cleanChannelNames = [];
|
||
|
||
// 读取频道数据
|
||
while ($reader->read() && $reader->name !== 'channel');
|
||
while ($reader->name === 'channel') {
|
||
$channel = new SimpleXMLElement($reader->readOuterXML());
|
||
$channelId = (string)$channel['id'];
|
||
$cleanChannelNames[$channelId] = cleanChannelName((string)$channel->{'display-name'});
|
||
$reader->next('channel');
|
||
}
|
||
|
||
// 繁简转换和频道筛选
|
||
$simplifiedChannelNames = (isset($Config['all_chs']) && $Config['all_chs']) ?
|
||
$cleanChannelNames : explode("\n", t2s(implode("\n", $cleanChannelNames)));
|
||
$channelNamesMap = [];
|
||
foreach ($cleanChannelNames as $channelId => $channelName) {
|
||
$channelNameSimplified = array_shift($simplifiedChannelNames);
|
||
|
||
// 假如 channel_bind_epg 存在且频道在其中有记录,且不为当前 xml_url,直接跳过
|
||
if (!empty($channel_bind_epg) &&
|
||
isset($channel_bind_epg[$channelNameSimplified]) &&
|
||
!in_array($xml_url, $channel_bind_epg[$channelNameSimplified])
|
||
) {
|
||
continue; // 跳过当前循环,继续处理下一个
|
||
}
|
||
|
||
// 当 gen_list_enable 为 0 时,插入所有数据
|
||
if (empty($Config['gen_list_enable'])) {
|
||
$channelNamesMap[$channelId] = $channelNameSimplified;
|
||
continue;
|
||
}
|
||
$matchFound = false;
|
||
foreach ($gen_list as $item) {
|
||
if (stripos($channelNameSimplified, $item) !== false ||
|
||
stripos($item, $channelNameSimplified) !== false) {
|
||
$matchFound = true;
|
||
break;
|
||
}
|
||
}
|
||
if ($matchFound) {
|
||
$channelNamesMap[$channelId] = $channelNameSimplified;
|
||
}
|
||
}
|
||
|
||
$reader->close();
|
||
$reader->XML($xml_data); // 重置 XMLReader
|
||
while ($reader->read() && $reader->name !== 'programme');
|
||
|
||
$currentChannelProgrammes = [];
|
||
$crossDayProgrammes = []; // 保存跨天的节目数据
|
||
|
||
// 修正 epg.pw 时区错误
|
||
$overwrite_time_zone = strpos($xml_data, 'epg.pw') !== false ? '+0800' : '';
|
||
|
||
while ($reader->name === 'programme') {
|
||
$programme = new SimpleXMLElement($reader->readOuterXML());
|
||
[$startDate, $startTime] = getFormatTime((string)$programme['start'], $overwrite_time_zone);
|
||
[$endDate, $endTime] = getFormatTime((string)$programme['stop'], $overwrite_time_zone);
|
||
|
||
// 判断数据是否符合设定期限
|
||
if (empty($startDate) || $startDate < $thresholdDate || empty($endDate)) {
|
||
$reader->next('programme');
|
||
continue;
|
||
}
|
||
|
||
$channelId = (string)$programme['channel'];
|
||
$channelName = $channelNamesMap[$channelId] ?? null;
|
||
$recordKey = $channelName . '-' . $startDate;
|
||
|
||
// 优先处理跨天数据
|
||
if (isset($crossDayProgrammes[$channelId][$startDate]) && !isset($processedRecords[$recordKey])) {
|
||
$currentChannelProgrammes[$channelId]['diyp_data'][$startDate] = array_merge(
|
||
$currentChannelProgrammes[$channelId]['diyp_data'][$startDate] ?? [],
|
||
$crossDayProgrammes[$channelId][$startDate]
|
||
);
|
||
$currentChannelProgrammes[$channelId]['channel_name'] = $channelName;
|
||
unset($crossDayProgrammes[$channelId][$startDate]);
|
||
}
|
||
|
||
if ($channelName && !isset($processedRecords[$recordKey])) {
|
||
$programmeData = [
|
||
'start' => $startTime,
|
||
'end' => $startDate === $endDate ? $endTime : '00:00',
|
||
'title' => (string)$programme->title,
|
||
'desc' => isset($programme->desc) ? (string)$programme->desc : ''
|
||
];
|
||
|
||
$currentChannelProgrammes[$channelId]['diyp_data'][$startDate][] = $programmeData;
|
||
|
||
// 保存跨天的节目数据
|
||
if ($startDate !== $endDate && $endTime !== '00:00') {
|
||
$crossDayProgrammes[$channelId][$endDate][] = [
|
||
'start' => '00:00',
|
||
'end' => $endTime,
|
||
'title' => $programmeData['title'],
|
||
'desc' => $programmeData['desc']
|
||
];
|
||
}
|
||
|
||
$currentChannelProgrammes[$channelId]['channel_name'] = $channelName;
|
||
|
||
// 每次达到 50 时,插入数据并保留最后一条
|
||
if (count($currentChannelProgrammes) >= 50) {
|
||
$lastProgramme = array_pop($currentChannelProgrammes); // 取出最后一条
|
||
insertDataToDatabase($currentChannelProgrammes, $db, $xml_url); // 插入前 49 条
|
||
$currentChannelProgrammes = [$channelId => $lastProgramme]; // 清空并重新赋值最后一条
|
||
}
|
||
}
|
||
|
||
$processCount++;
|
||
$reader->next('programme');
|
||
}
|
||
|
||
// 插入剩余的数据
|
||
if ($currentChannelProgrammes) {
|
||
insertDataToDatabase($currentChannelProgrammes, $db, $xml_url);
|
||
}
|
||
|
||
$reader->close();
|
||
|
||
return $processCount;
|
||
}
|
||
|
||
// 从 epg_data 读取数据,生成 iconList.json 及 xmltv 文件
|
||
function processIconListAndXmltv($db, $gen_list_mapping, &$log_messages) {
|
||
global $Config, $iconList, $iconListPath;
|
||
|
||
$currentDate = date('Y-m-d'); // 获取当前日期
|
||
$dateCondition = $Config['include_future_only'] ? "WHERE date >= '$currentDate'" : '';
|
||
|
||
// 合并查询
|
||
$query = "SELECT date, channel, epg_diyp FROM epg_data $dateCondition ORDER BY channel ASC, date ASC";
|
||
$stmt = $db->query($query);
|
||
|
||
|
||
// 存储节目数据以按频道分组
|
||
$channelData = [];
|
||
|
||
while ($program = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||
$channelName = $program['channel'];
|
||
$iconUrl = iconUrlMatch($channelName, $getDefault = false);
|
||
|
||
if ($iconUrl) {
|
||
$iconList[strtoupper($channelName)] = $iconUrl;
|
||
$program['icon'] = $iconUrl;
|
||
}
|
||
|
||
// gen_list_enable 为 0 或存在映射,则处理频道数据
|
||
if (empty($Config['gen_list_enable']) || isset($gen_list_mapping[$channelName])) {
|
||
$channelData[$channelName][] = $program;
|
||
}
|
||
}
|
||
|
||
// 更新 iconList.json 文件中的数据
|
||
if (file_put_contents($iconListPath,
|
||
json_encode($iconList, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) === false) {
|
||
logMessage($log_messages, "[台标列表] 更新 iconList.json 时发生错误!!!");
|
||
} else {
|
||
logMessage($log_messages, "[台标列表] 已更新 iconList.json");
|
||
}
|
||
|
||
logMessage($log_messages, "[XMLTV] 开始生成并写入XMLTV文件...");
|
||
|
||
// 判断是否生成 xmltv 文件
|
||
if (empty($Config['gen_xml'])) {
|
||
return;
|
||
}
|
||
|
||
// 创建 XMLWriter 实例
|
||
$xmlFilePath = __DIR__ . '/t.xml';
|
||
$xmlWriter = new XMLWriter();
|
||
$xmlWriter->openUri($xmlFilePath);
|
||
$xmlWriter->startDocument('1.0', 'UTF-8');
|
||
$xmlWriter->startElement('tv');
|
||
$xmlWriter->writeAttribute('generator-info-name', 'CrestekkEPG');
|
||
$xmlWriter->writeAttribute('generator-info-url', 'https://github.com/mxdabc/epgphp');
|
||
$xmlWriter->setIndent(true);
|
||
$xmlWriter->setIndentString(' '); // 设置缩进
|
||
|
||
// 将 $Config['channel_mappings'] 中的映射值转换为数组
|
||
$channelMappings = array_map(function($mapped) {
|
||
return strpos($mapped, 'regex:') === 0 ? [$mapped] : array_map('trim', explode(',', $mapped));
|
||
}, $Config['channel_mappings']);
|
||
|
||
// 逐个频道处理
|
||
foreach ($channelData as $channelName => $programs) {
|
||
// 写入频道信息
|
||
$xmlWriter->startElement('channel');
|
||
$xmlWriter->writeAttribute('id', htmlspecialchars($channelName, ENT_XML1, 'UTF-8'));
|
||
|
||
// 为该频道生成多个 display-name ,包括原频道名、限定频道列表、频道别名
|
||
$displayNames = array_unique(array_merge(
|
||
[$channelName],
|
||
$gen_list_mapping[$channelName] ?? [],
|
||
$channelMappings[$channelName] ?? []
|
||
));
|
||
foreach ($displayNames as $displayName) {
|
||
$xmlWriter->startElement('display-name');
|
||
$xmlWriter->writeAttribute('lang', 'zh');
|
||
$xmlWriter->text(htmlspecialchars($displayName, ENT_XML1, 'UTF-8'));
|
||
$xmlWriter->endElement(); // display-name
|
||
}
|
||
|
||
$iconUrl = $programs[0]['icon'] ?? '';
|
||
|
||
if ($iconUrl) {
|
||
$xmlWriter->startElement('icon');
|
||
$xmlWriter->writeAttribute('src', $iconUrl);
|
||
$xmlWriter->endElement(); // icon
|
||
}
|
||
|
||
$xmlWriter->endElement(); // channel
|
||
|
||
// 写入该频道的所有节目数据
|
||
foreach ($programs as $programIndex => &$program) {
|
||
$data = json_decode($program['epg_diyp'], true);
|
||
$dataCount = count($data['epg_data']);
|
||
$end_date = $program['date'];
|
||
|
||
for ($index = 0; $index < $dataCount; $index++) {
|
||
$item = $data['epg_data'][$index];
|
||
$end_time = $item['end'];
|
||
|
||
// 如果结束时间为 00:00,切换到第二天的日期
|
||
if ($end_time == '00:00') {
|
||
$end_date = date('Ymd', strtotime($end_date . ' +1 day')); // 切换日期
|
||
|
||
// 合并下一个节目
|
||
if (isset($programs[$programIndex + 1])) {
|
||
$nextData = json_decode($programs[$programIndex + 1]['epg_diyp'], true);
|
||
$nextItem = $nextData['epg_data'][0] ?? null;
|
||
|
||
if ($nextItem && $nextItem['title'] == $item['title']) {
|
||
$end_time = $nextItem['end'];
|
||
array_splice($nextData['epg_data'], 0, 1); // 删除下一个节目的第一个项目
|
||
$programs[$programIndex + 1]['epg_diyp'] = json_encode($nextData);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 写入当前节目
|
||
$xmlWriter->startElement('programme');
|
||
$xmlWriter->writeAttribute('channel', htmlspecialchars($channelName, ENT_XML1, 'UTF-8'));
|
||
$xmlWriter->writeAttribute('start', formatTime($program['date'], $item['start']));
|
||
$xmlWriter->writeAttribute('stop', formatTime($end_date, $end_time));
|
||
$xmlWriter->startElement('title');
|
||
$xmlWriter->writeAttribute('lang', 'zh');
|
||
$xmlWriter->text(htmlspecialchars($item['title'], ENT_XML1, 'UTF-8'));
|
||
$xmlWriter->endElement(); // title
|
||
if (!empty($item['desc'])) {
|
||
$xmlWriter->startElement('desc');
|
||
$xmlWriter->writeAttribute('lang', 'zh');
|
||
$xmlWriter->text(htmlspecialchars($item['desc'], ENT_XML1, 'UTF-8'));
|
||
$xmlWriter->endElement(); // desc
|
||
}
|
||
$xmlWriter->endElement(); // programme
|
||
}
|
||
}
|
||
}
|
||
|
||
// 结束 XML 文档
|
||
$xmlWriter->endElement(); // tv
|
||
$xmlWriter->endDocument();
|
||
$xmlWriter->flush();
|
||
|
||
logMessage($log_messages, "[XMLTV] 开始生成GZ压缩文件...");
|
||
|
||
// 所有频道数据写入完成后,生成 t.xml.gz 文件
|
||
compressXmlFile($xmlFilePath);
|
||
|
||
logMessage($log_messages, "[XMLTV] GZ压缩文件压缩成功");
|
||
|
||
logMessage($log_messages, "[XMLTV] 已生成 t.xml 和 t.xml.gz");
|
||
}
|
||
|
||
// 生成 t.xml.gz 压缩文件
|
||
function compressXmlFile($xmlFilePath) {
|
||
$gzFilePath = $xmlFilePath . '.gz';
|
||
|
||
// 打开原文件和压缩文件
|
||
$file = fopen($xmlFilePath, 'rb');
|
||
$gzFile = gzopen($gzFilePath, 'wb9'); // 最高压缩等级
|
||
|
||
// 将文件内容写入到压缩文件中
|
||
while (!feof($file)) {
|
||
gzwrite($gzFile, fread($file, 1024 * 512));
|
||
}
|
||
|
||
// 关闭文件
|
||
fclose($file);
|
||
gzclose($gzFile);
|
||
}
|
||
|
||
// 记录开始时间
|
||
$startTime = microtime(true);
|
||
|
||
// 统计更新前数据条数
|
||
$initialCount = $db->query("SELECT COUNT(*) FROM epg_data")->fetchColumn();
|
||
|
||
// 删除过期数据
|
||
$thresholdDate = date('Y-m-d', strtotime("-{$Config['days_to_keep']} days +1 day"));
|
||
deleteOldData($db, $log_messages);
|
||
|
||
// 获取限定频道列表及映射关系
|
||
$gen_res = getGenList($db);
|
||
$gen_list = $gen_res['gen_list'];
|
||
$gen_list_mapping = $gen_res['gen_list_mapping'];
|
||
|
||
// 获取频道指定 EPG 关系
|
||
$channel_bind_epg = getChannelBindEPG();
|
||
|
||
// 全局变量,用于记录已处理的记录
|
||
$processedRecords = [];
|
||
|
||
// 更新数据
|
||
foreach ($Config['xml_urls'] as $xml_url) {
|
||
// 去掉空白字符,忽略空行和以 # 开头的 URL
|
||
$xml_url = trim($xml_url);
|
||
if (empty($xml_url) || strpos($xml_url, '#') === 0) {
|
||
continue;
|
||
} elseif (preg_match('/^(tvmao|cntv)/i', $xml_url, $matches)) {
|
||
$data_source = strtolower($matches[0]);
|
||
downloadJSONData($data_source, $xml_url, $db, $log_messages);
|
||
continue;
|
||
}
|
||
|
||
// 更新 XML 数据
|
||
list($xml_url_str, , $userAgent) = explode('#', $xml_url) + [1 => '', 2 => ''];
|
||
$userAgent = trim($userAgent);
|
||
$cleaned_url = trim(strpos($xml_url_str, '=>') !== false ? explode('=>', $xml_url_str)[1] : $xml_url_str);
|
||
logMessage($log_messages, "[地址] $cleaned_url");
|
||
|
||
// 判断是否有限定频道列表并下载数据
|
||
if (strpos($xml_url_str, '=>') !== false) {
|
||
$tmp_gen_list = array_map('trim', explode(",", explode('=>', $xml_url_str)[0]));
|
||
logMessage($log_messages, "[临时] 限定频道:" . implode(", ", $tmp_gen_list));
|
||
downloadXmlData($cleaned_url, $userAgent, $db, $log_messages, $tmp_gen_list, 1);
|
||
} else {
|
||
downloadXmlData($cleaned_url, $userAgent, $db, $log_messages, $gen_list);
|
||
}
|
||
}
|
||
|
||
// 更新 iconList.json 及生成 xmltv 文件
|
||
processIconListAndXmltv($db, $gen_list_mapping, $log_messages);
|
||
|
||
// 判断是否同步更新直播源
|
||
if (isset($Config['live_source_auto_sync']) && $Config['live_source_auto_sync'] == 1) {
|
||
$parseResult = doParseSourceInfo();
|
||
if ($parseResult !== true) {
|
||
logMessage($log_messages, "[直播文件] 部分更新异常:" . rtrim(str_replace('<br>', '、', $parseResult), '、'));
|
||
} else {
|
||
logMessage($log_messages, "[直播文件] 已同步更新");
|
||
}
|
||
}
|
||
|
||
// 统计更新后数据条数
|
||
$finalCount = $db->query("SELECT COUNT(*) FROM epg_data")->fetchColumn();
|
||
$dif = $finalCount - $initialCount;
|
||
$msg = $dif != 0 ? ($dif > 0 ? " 增加 $dif 。" : " 减少 " . abs($dif) . " 。") : "";
|
||
// 记录结束时间
|
||
$endTime = microtime(true);
|
||
// 计算运行时间(以秒为单位)
|
||
$executionTime = round($endTime - $startTime, 1);
|
||
echo "<br>";
|
||
logMessage($log_messages, "[更新完成] {$executionTime} 秒。节目天数:更新前 {$initialCount} ,更新后 {$finalCount} 。" . $msg);
|
||
|
||
// 将日志信息写入数据库
|
||
$log_message_str = implode("<br>", $log_messages);
|
||
$timestamp = date('Y-m-d H:i:s'); // 使用设定的时区时间
|
||
$stmt = $db->prepare('INSERT INTO update_log (timestamp, log_message) VALUES (:timestamp, :log_message)');
|
||
$stmt->bindValue(':timestamp', $timestamp, PDO::PARAM_STR);
|
||
$stmt->bindValue(':log_message', $log_message_str, PDO::PARAM_STR);
|
||
$stmt->execute();
|
||
|
||
?>
|