koishi-plugin-media-luna 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/README.md +558 -0
- package/client/api.ts +256 -0
- package/client/components/ChannelConfigDialog.vue +1113 -0
- package/client/components/ChannelsView.vue +698 -0
- package/client/components/ConfigRenderer.vue +164 -0
- package/client/components/EmptyState.vue +87 -0
- package/client/components/GenerateView.vue +778 -0
- package/client/components/HistoryGallery.vue +590 -0
- package/client/components/ImageLightbox.vue +510 -0
- package/client/components/ImageUpload.vue +298 -0
- package/client/components/JsonEditor.vue +68 -0
- package/client/components/LoadingState.vue +73 -0
- package/client/components/PresetsView.vue +800 -0
- package/client/components/ScrollContainer.vue +45 -0
- package/client/components/SettingsView.vue +231 -0
- package/client/components/StatusBadge.vue +55 -0
- package/client/components/TagFilter.vue +139 -0
- package/client/components/TagInput.vue +63 -0
- package/client/components/TasksView.vue +1074 -0
- package/client/components/ViewModeSwitch.vue +78 -0
- package/client/components/settings/CachePanel.vue +142 -0
- package/client/components/settings/MiddlewaresPanel.vue +480 -0
- package/client/components/settings/PluginsPanel.vue +493 -0
- package/client/index.ts +13 -0
- package/client/pages/index.vue +177 -0
- package/client/styles/shared.css +258 -0
- package/client/types.ts +158 -0
- package/client/utils/format.ts +72 -0
- package/dist/index.js +1 -0
- package/dist/style.css +1 -0
- package/lib/api/README.md +643 -0
- package/lib/api/cache-api.d.ts +6 -0
- package/lib/api/cache-api.d.ts.map +1 -0
- package/lib/api/cache-api.js +147 -0
- package/lib/api/cache-api.js.map +1 -0
- package/lib/api/channel-api.d.ts +6 -0
- package/lib/api/channel-api.d.ts.map +1 -0
- package/lib/api/channel-api.js +148 -0
- package/lib/api/channel-api.js.map +1 -0
- package/lib/api/connector-api.d.ts +6 -0
- package/lib/api/connector-api.d.ts.map +1 -0
- package/lib/api/connector-api.js +63 -0
- package/lib/api/connector-api.js.map +1 -0
- package/lib/api/generate-api.d.ts +6 -0
- package/lib/api/generate-api.d.ts.map +1 -0
- package/lib/api/generate-api.js +123 -0
- package/lib/api/generate-api.js.map +1 -0
- package/lib/api/index.d.ts +15 -0
- package/lib/api/index.d.ts.map +1 -0
- package/lib/api/index.js +34 -0
- package/lib/api/index.js.map +1 -0
- package/lib/api/middleware-api.d.ts +6 -0
- package/lib/api/middleware-api.d.ts.map +1 -0
- package/lib/api/middleware-api.js +212 -0
- package/lib/api/middleware-api.js.map +1 -0
- package/lib/api/plugin-api.d.ts +6 -0
- package/lib/api/plugin-api.d.ts.map +1 -0
- package/lib/api/plugin-api.js +78 -0
- package/lib/api/plugin-api.js.map +1 -0
- package/lib/api/preset-api.d.ts +6 -0
- package/lib/api/preset-api.d.ts.map +1 -0
- package/lib/api/preset-api.js +256 -0
- package/lib/api/preset-api.js.map +1 -0
- package/lib/api/settings-api.d.ts +6 -0
- package/lib/api/settings-api.d.ts.map +1 -0
- package/lib/api/settings-api.js +118 -0
- package/lib/api/settings-api.js.map +1 -0
- package/lib/api/task-api.d.ts +6 -0
- package/lib/api/task-api.d.ts.map +1 -0
- package/lib/api/task-api.js +148 -0
- package/lib/api/task-api.js.map +1 -0
- package/lib/config.d.ts +6 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +8 -0
- package/lib/config.js.map +1 -0
- package/lib/connectors/builtin/chat-api.d.ts +9 -0
- package/lib/connectors/builtin/chat-api.d.ts.map +1 -0
- package/lib/connectors/builtin/chat-api.js +351 -0
- package/lib/connectors/builtin/chat-api.js.map +1 -0
- package/lib/connectors/builtin/dalle.d.ts +6 -0
- package/lib/connectors/builtin/dalle.d.ts.map +1 -0
- package/lib/connectors/builtin/dalle.js +109 -0
- package/lib/connectors/builtin/dalle.js.map +1 -0
- package/lib/connectors/builtin/flux.d.ts +7 -0
- package/lib/connectors/builtin/flux.d.ts.map +1 -0
- package/lib/connectors/builtin/flux.js +199 -0
- package/lib/connectors/builtin/flux.js.map +1 -0
- package/lib/connectors/builtin/index.d.ts +5 -0
- package/lib/connectors/builtin/index.d.ts.map +1 -0
- package/lib/connectors/builtin/index.js +6 -0
- package/lib/connectors/builtin/index.js.map +1 -0
- package/lib/connectors/builtin/sd-webui.d.ts +7 -0
- package/lib/connectors/builtin/sd-webui.d.ts.map +1 -0
- package/lib/connectors/builtin/sd-webui.js +157 -0
- package/lib/connectors/builtin/sd-webui.js.map +1 -0
- package/lib/connectors/connector-registry.d.ts +36 -0
- package/lib/connectors/connector-registry.d.ts.map +1 -0
- package/lib/connectors/connector-registry.js +66 -0
- package/lib/connectors/connector-registry.js.map +1 -0
- package/lib/connectors/index.d.ts +3 -0
- package/lib/connectors/index.d.ts.map +1 -0
- package/lib/connectors/index.js +4 -0
- package/lib/connectors/index.js.map +1 -0
- package/lib/core/api/api-utils.d.ts +46 -0
- package/lib/core/api/api-utils.d.ts.map +1 -0
- package/lib/core/api/api-utils.js +78 -0
- package/lib/core/api/api-utils.js.map +1 -0
- package/lib/core/api/cache-api.d.ts +6 -0
- package/lib/core/api/cache-api.d.ts.map +1 -0
- package/lib/core/api/cache-api.js +147 -0
- package/lib/core/api/cache-api.js.map +1 -0
- package/lib/core/api/channel-api.d.ts +6 -0
- package/lib/core/api/channel-api.d.ts.map +1 -0
- package/lib/core/api/channel-api.js +146 -0
- package/lib/core/api/channel-api.js.map +1 -0
- package/lib/core/api/connector-api.d.ts +6 -0
- package/lib/core/api/connector-api.d.ts.map +1 -0
- package/lib/core/api/connector-api.js +63 -0
- package/lib/core/api/connector-api.js.map +1 -0
- package/lib/core/api/generate-api.d.ts +6 -0
- package/lib/core/api/generate-api.d.ts.map +1 -0
- package/lib/core/api/generate-api.js +124 -0
- package/lib/core/api/generate-api.js.map +1 -0
- package/lib/core/api/index.d.ts +16 -0
- package/lib/core/api/index.d.ts.map +1 -0
- package/lib/core/api/index.js +38 -0
- package/lib/core/api/index.js.map +1 -0
- package/lib/core/api/middleware-api.d.ts +6 -0
- package/lib/core/api/middleware-api.d.ts.map +1 -0
- package/lib/core/api/middleware-api.js +196 -0
- package/lib/core/api/middleware-api.js.map +1 -0
- package/lib/core/api/plugin-api.d.ts +6 -0
- package/lib/core/api/plugin-api.d.ts.map +1 -0
- package/lib/core/api/plugin-api.js +78 -0
- package/lib/core/api/plugin-api.js.map +1 -0
- package/lib/core/api/preset-api.d.ts +6 -0
- package/lib/core/api/preset-api.d.ts.map +1 -0
- package/lib/core/api/preset-api.js +272 -0
- package/lib/core/api/preset-api.js.map +1 -0
- package/lib/core/api/settings-api.d.ts +6 -0
- package/lib/core/api/settings-api.d.ts.map +1 -0
- package/lib/core/api/settings-api.js +118 -0
- package/lib/core/api/settings-api.js.map +1 -0
- package/lib/core/api/task-api.d.ts +6 -0
- package/lib/core/api/task-api.d.ts.map +1 -0
- package/lib/core/api/task-api.js +281 -0
- package/lib/core/api/task-api.js.map +1 -0
- package/lib/core/channel.service.d.ts +44 -0
- package/lib/core/channel.service.d.ts.map +1 -0
- package/lib/core/channel.service.js +146 -0
- package/lib/core/channel.service.js.map +1 -0
- package/lib/core/config/config.service.d.ts +89 -0
- package/lib/core/config/config.service.d.ts.map +1 -0
- package/lib/core/config/config.service.js +216 -0
- package/lib/core/config/config.service.js.map +1 -0
- package/lib/core/config/index.d.ts +3 -0
- package/lib/core/config/index.d.ts.map +1 -0
- package/lib/core/config/index.js +3 -0
- package/lib/core/config/index.js.map +1 -0
- package/lib/core/error.d.ts +73 -0
- package/lib/core/error.d.ts.map +1 -0
- package/lib/core/error.js +137 -0
- package/lib/core/error.js.map +1 -0
- package/lib/core/index.d.ts +12 -0
- package/lib/core/index.d.ts.map +1 -0
- package/lib/core/index.js +23 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/logger.d.ts +18 -0
- package/lib/core/logger.d.ts.map +1 -0
- package/lib/core/logger.js +39 -0
- package/lib/core/logger.js.map +1 -0
- package/lib/core/medialuna.service.d.ts +168 -0
- package/lib/core/medialuna.service.d.ts.map +1 -0
- package/lib/core/medialuna.service.js +423 -0
- package/lib/core/medialuna.service.js.map +1 -0
- package/lib/core/pipeline/dependency-graph.d.ts +43 -0
- package/lib/core/pipeline/dependency-graph.d.ts.map +1 -0
- package/lib/core/pipeline/dependency-graph.js +223 -0
- package/lib/core/pipeline/dependency-graph.js.map +1 -0
- package/lib/core/pipeline/generation-pipeline.d.ts +40 -0
- package/lib/core/pipeline/generation-pipeline.d.ts.map +1 -0
- package/lib/core/pipeline/generation-pipeline.js +216 -0
- package/lib/core/pipeline/generation-pipeline.js.map +1 -0
- package/lib/core/pipeline/index.d.ts +4 -0
- package/lib/core/pipeline/index.d.ts.map +1 -0
- package/lib/core/pipeline/index.js +5 -0
- package/lib/core/pipeline/index.js.map +1 -0
- package/lib/core/pipeline/middleware-registry.d.ts +33 -0
- package/lib/core/pipeline/middleware-registry.d.ts.map +1 -0
- package/lib/core/pipeline/middleware-registry.js +78 -0
- package/lib/core/pipeline/middleware-registry.js.map +1 -0
- package/lib/core/plugin-loader.d.ts +77 -0
- package/lib/core/plugin-loader.d.ts.map +1 -0
- package/lib/core/plugin-loader.js +300 -0
- package/lib/core/plugin-loader.js.map +1 -0
- package/lib/core/registry/connector.registry.d.ts +36 -0
- package/lib/core/registry/connector.registry.d.ts.map +1 -0
- package/lib/core/registry/connector.registry.js +65 -0
- package/lib/core/registry/connector.registry.js.map +1 -0
- package/lib/core/registry/index.d.ts +3 -0
- package/lib/core/registry/index.d.ts.map +1 -0
- package/lib/core/registry/index.js +4 -0
- package/lib/core/registry/index.js.map +1 -0
- package/lib/core/registry/service.registry.d.ts +27 -0
- package/lib/core/registry/service.registry.d.ts.map +1 -0
- package/lib/core/registry/service.registry.js +54 -0
- package/lib/core/registry/service.registry.js.map +1 -0
- package/lib/core/request.service.d.ts +83 -0
- package/lib/core/request.service.d.ts.map +1 -0
- package/lib/core/request.service.js +237 -0
- package/lib/core/request.service.js.map +1 -0
- package/lib/core/types.d.ts +270 -0
- package/lib/core/types.d.ts.map +1 -0
- package/lib/core/types.js +17 -0
- package/lib/core/types.js.map +1 -0
- package/lib/database.d.ts +6 -0
- package/lib/database.d.ts.map +1 -0
- package/lib/database.js +89 -0
- package/lib/database.js.map +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +54 -0
- package/lib/index.js.map +1 -0
- package/lib/middlewares/builtin/billing.d.ts +45 -0
- package/lib/middlewares/builtin/billing.d.ts.map +1 -0
- package/lib/middlewares/builtin/billing.js +362 -0
- package/lib/middlewares/builtin/billing.js.map +1 -0
- package/lib/middlewares/builtin/index.d.ts +6 -0
- package/lib/middlewares/builtin/index.d.ts.map +1 -0
- package/lib/middlewares/builtin/index.js +7 -0
- package/lib/middlewares/builtin/index.js.map +1 -0
- package/lib/middlewares/builtin/preset.d.ts +14 -0
- package/lib/middlewares/builtin/preset.d.ts.map +1 -0
- package/lib/middlewares/builtin/preset.js +156 -0
- package/lib/middlewares/builtin/preset.js.map +1 -0
- package/lib/middlewares/builtin/remote-preset-sync.d.ts +8 -0
- package/lib/middlewares/builtin/remote-preset-sync.d.ts.map +1 -0
- package/lib/middlewares/builtin/remote-preset-sync.js +32 -0
- package/lib/middlewares/builtin/remote-preset-sync.js.map +1 -0
- package/lib/middlewares/builtin/request.d.ts +10 -0
- package/lib/middlewares/builtin/request.d.ts.map +1 -0
- package/lib/middlewares/builtin/request.js +50 -0
- package/lib/middlewares/builtin/request.js.map +1 -0
- package/lib/middlewares/builtin/storage.d.ts +64 -0
- package/lib/middlewares/builtin/storage.d.ts.map +1 -0
- package/lib/middlewares/builtin/storage.js +715 -0
- package/lib/middlewares/builtin/storage.js.map +1 -0
- package/lib/middlewares/builtin/task-recorder.d.ts +20 -0
- package/lib/middlewares/builtin/task-recorder.d.ts.map +1 -0
- package/lib/middlewares/builtin/task-recorder.js +138 -0
- package/lib/middlewares/builtin/task-recorder.js.map +1 -0
- package/lib/middlewares/index.d.ts +2 -0
- package/lib/middlewares/index.d.ts.map +1 -0
- package/lib/middlewares/index.js +3 -0
- package/lib/middlewares/index.js.map +1 -0
- package/lib/pipeline/dependency-graph.d.ts +43 -0
- package/lib/pipeline/dependency-graph.d.ts.map +1 -0
- package/lib/pipeline/dependency-graph.js +240 -0
- package/lib/pipeline/dependency-graph.js.map +1 -0
- package/lib/pipeline/generation-pipeline.d.ts +38 -0
- package/lib/pipeline/generation-pipeline.d.ts.map +1 -0
- package/lib/pipeline/generation-pipeline.js +211 -0
- package/lib/pipeline/generation-pipeline.js.map +1 -0
- package/lib/pipeline/index.d.ts +4 -0
- package/lib/pipeline/index.d.ts.map +1 -0
- package/lib/pipeline/index.js +5 -0
- package/lib/pipeline/index.js.map +1 -0
- package/lib/pipeline/middleware-registry.d.ts +36 -0
- package/lib/pipeline/middleware-registry.d.ts.map +1 -0
- package/lib/pipeline/middleware-registry.js +83 -0
- package/lib/pipeline/middleware-registry.js.map +1 -0
- package/lib/plugins/billing/config.d.ts +19 -0
- package/lib/plugins/billing/config.d.ts.map +1 -0
- package/lib/plugins/billing/config.js +98 -0
- package/lib/plugins/billing/config.js.map +1 -0
- package/lib/plugins/billing/index.d.ts +4 -0
- package/lib/plugins/billing/index.d.ts.map +1 -0
- package/lib/plugins/billing/index.js +20 -0
- package/lib/plugins/billing/index.js.map +1 -0
- package/lib/plugins/billing/middleware.d.ts +12 -0
- package/lib/plugins/billing/middleware.d.ts.map +1 -0
- package/lib/plugins/billing/middleware.js +233 -0
- package/lib/plugins/billing/middleware.js.map +1 -0
- package/lib/plugins/cache/config.d.ts +44 -0
- package/lib/plugins/cache/config.d.ts.map +1 -0
- package/lib/plugins/cache/config.js +187 -0
- package/lib/plugins/cache/config.js.map +1 -0
- package/lib/plugins/cache/index.d.ts +6 -0
- package/lib/plugins/cache/index.d.ts.map +1 -0
- package/lib/plugins/cache/index.js +87 -0
- package/lib/plugins/cache/index.js.map +1 -0
- package/lib/plugins/cache/middleware.d.ts +12 -0
- package/lib/plugins/cache/middleware.d.ts.map +1 -0
- package/lib/plugins/cache/middleware.js +194 -0
- package/lib/plugins/cache/middleware.js.map +1 -0
- package/lib/plugins/cache/service.d.ts +106 -0
- package/lib/plugins/cache/service.d.ts.map +1 -0
- package/lib/plugins/cache/service.js +467 -0
- package/lib/plugins/cache/service.js.map +1 -0
- package/lib/plugins/cache/utils/index.d.ts +4 -0
- package/lib/plugins/cache/utils/index.d.ts.map +1 -0
- package/lib/plugins/cache/utils/index.js +5 -0
- package/lib/plugins/cache/utils/index.js.map +1 -0
- package/lib/plugins/cache/utils/mime.d.ts +9 -0
- package/lib/plugins/cache/utils/mime.d.ts.map +1 -0
- package/lib/plugins/cache/utils/mime.js +65 -0
- package/lib/plugins/cache/utils/mime.js.map +1 -0
- package/lib/plugins/cache/utils/s3.d.ts +19 -0
- package/lib/plugins/cache/utils/s3.d.ts.map +1 -0
- package/lib/plugins/cache/utils/s3.js +147 -0
- package/lib/plugins/cache/utils/s3.js.map +1 -0
- package/lib/plugins/cache/utils/webdav.d.ts +16 -0
- package/lib/plugins/cache/utils/webdav.d.ts.map +1 -0
- package/lib/plugins/cache/utils/webdav.js +69 -0
- package/lib/plugins/cache/utils/webdav.js.map +1 -0
- package/lib/plugins/connector-chat-api/index.d.ts +7 -0
- package/lib/plugins/connector-chat-api/index.d.ts.map +1 -0
- package/lib/plugins/connector-chat-api/index.js +400 -0
- package/lib/plugins/connector-chat-api/index.js.map +1 -0
- package/lib/plugins/connector-dalle/index.d.ts +7 -0
- package/lib/plugins/connector-dalle/index.d.ts.map +1 -0
- package/lib/plugins/connector-dalle/index.js +140 -0
- package/lib/plugins/connector-dalle/index.js.map +1 -0
- package/lib/plugins/connector-flux/index.d.ts +7 -0
- package/lib/plugins/connector-flux/index.d.ts.map +1 -0
- package/lib/plugins/connector-flux/index.js +232 -0
- package/lib/plugins/connector-flux/index.js.map +1 -0
- package/lib/plugins/connector-sd-webui/index.d.ts +7 -0
- package/lib/plugins/connector-sd-webui/index.d.ts.map +1 -0
- package/lib/plugins/connector-sd-webui/index.js +171 -0
- package/lib/plugins/connector-sd-webui/index.js.map +1 -0
- package/lib/plugins/index.d.ts +22 -0
- package/lib/plugins/index.d.ts.map +1 -0
- package/lib/plugins/index.js +21 -0
- package/lib/plugins/index.js.map +1 -0
- package/lib/plugins/preset/config.d.ts +37 -0
- package/lib/plugins/preset/config.d.ts.map +1 -0
- package/lib/plugins/preset/config.js +74 -0
- package/lib/plugins/preset/config.js.map +1 -0
- package/lib/plugins/preset/index.d.ts +6 -0
- package/lib/plugins/preset/index.d.ts.map +1 -0
- package/lib/plugins/preset/index.js +58 -0
- package/lib/plugins/preset/index.js.map +1 -0
- package/lib/plugins/preset/middleware.d.ts +6 -0
- package/lib/plugins/preset/middleware.d.ts.map +1 -0
- package/lib/plugins/preset/middleware.js +126 -0
- package/lib/plugins/preset/middleware.js.map +1 -0
- package/lib/plugins/preset/remote-sync.service.d.ts +84 -0
- package/lib/plugins/preset/remote-sync.service.d.ts.map +1 -0
- package/lib/plugins/preset/remote-sync.service.js +342 -0
- package/lib/plugins/preset/remote-sync.service.js.map +1 -0
- package/lib/plugins/preset/service.d.ts +64 -0
- package/lib/plugins/preset/service.d.ts.map +1 -0
- package/lib/plugins/preset/service.js +196 -0
- package/lib/plugins/preset/service.js.map +1 -0
- package/lib/plugins/prompt-encoding/config.d.ts +25 -0
- package/lib/plugins/prompt-encoding/config.d.ts.map +1 -0
- package/lib/plugins/prompt-encoding/config.js +62 -0
- package/lib/plugins/prompt-encoding/config.js.map +1 -0
- package/lib/plugins/prompt-encoding/index.d.ts +5 -0
- package/lib/plugins/prompt-encoding/index.d.ts.map +1 -0
- package/lib/plugins/prompt-encoding/index.js +21 -0
- package/lib/plugins/prompt-encoding/index.js.map +1 -0
- package/lib/plugins/prompt-encoding/middleware.d.ts +6 -0
- package/lib/plugins/prompt-encoding/middleware.d.ts.map +1 -0
- package/lib/plugins/prompt-encoding/middleware.js +151 -0
- package/lib/plugins/prompt-encoding/middleware.js.map +1 -0
- package/lib/plugins/task/config.d.ts +15 -0
- package/lib/plugins/task/config.d.ts.map +1 -0
- package/lib/plugins/task/config.js +33 -0
- package/lib/plugins/task/config.js.map +1 -0
- package/lib/plugins/task/index.d.ts +5 -0
- package/lib/plugins/task/index.d.ts.map +1 -0
- package/lib/plugins/task/index.js +58 -0
- package/lib/plugins/task/index.js.map +1 -0
- package/lib/plugins/task/middleware.d.ts +14 -0
- package/lib/plugins/task/middleware.d.ts.map +1 -0
- package/lib/plugins/task/middleware.js +123 -0
- package/lib/plugins/task/middleware.js.map +1 -0
- package/lib/plugins/task/service.d.ts +94 -0
- package/lib/plugins/task/service.d.ts.map +1 -0
- package/lib/plugins/task/service.js +226 -0
- package/lib/plugins/task/service.js.map +1 -0
- package/lib/plugins/webui-auth/config.d.ts +12 -0
- package/lib/plugins/webui-auth/config.d.ts.map +1 -0
- package/lib/plugins/webui-auth/config.js +30 -0
- package/lib/plugins/webui-auth/config.js.map +1 -0
- package/lib/plugins/webui-auth/index.d.ts +5 -0
- package/lib/plugins/webui-auth/index.d.ts.map +1 -0
- package/lib/plugins/webui-auth/index.js +99 -0
- package/lib/plugins/webui-auth/index.js.map +1 -0
- package/lib/plugins/webui-auth/service.d.ts +42 -0
- package/lib/plugins/webui-auth/service.d.ts.map +1 -0
- package/lib/plugins/webui-auth/service.js +106 -0
- package/lib/plugins/webui-auth/service.js.map +1 -0
- package/lib/services/asset-cache.service.d.ts +133 -0
- package/lib/services/asset-cache.service.d.ts.map +1 -0
- package/lib/services/asset-cache.service.js +882 -0
- package/lib/services/asset-cache.service.js.map +1 -0
- package/lib/services/cache.service.d.ts +110 -0
- package/lib/services/cache.service.d.ts.map +1 -0
- package/lib/services/cache.service.js +333 -0
- package/lib/services/cache.service.js.map +1 -0
- package/lib/services/channel.service.d.ts +44 -0
- package/lib/services/channel.service.d.ts.map +1 -0
- package/lib/services/channel.service.js +154 -0
- package/lib/services/channel.service.js.map +1 -0
- package/lib/services/config.service.d.ts +73 -0
- package/lib/services/config.service.d.ts.map +1 -0
- package/lib/services/config.service.js +171 -0
- package/lib/services/config.service.js.map +1 -0
- package/lib/services/index.d.ts +8 -0
- package/lib/services/index.d.ts.map +1 -0
- package/lib/services/index.js +9 -0
- package/lib/services/index.js.map +1 -0
- package/lib/services/medialuna.service.d.ts +173 -0
- package/lib/services/medialuna.service.d.ts.map +1 -0
- package/lib/services/medialuna.service.js +455 -0
- package/lib/services/medialuna.service.js.map +1 -0
- package/lib/services/preset.service.d.ts +69 -0
- package/lib/services/preset.service.d.ts.map +1 -0
- package/lib/services/preset.service.js +202 -0
- package/lib/services/preset.service.js.map +1 -0
- package/lib/services/remote-preset.service.d.ts +97 -0
- package/lib/services/remote-preset.service.d.ts.map +1 -0
- package/lib/services/remote-preset.service.js +212 -0
- package/lib/services/remote-preset.service.js.map +1 -0
- package/lib/services/task.service.d.ts +57 -0
- package/lib/services/task.service.d.ts.map +1 -0
- package/lib/services/task.service.js +138 -0
- package/lib/services/task.service.js.map +1 -0
- package/lib/types/index.d.ts +357 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +21 -0
- package/lib/types/index.js.map +1 -0
- package/package.json +66 -0
- package/readme.md +326 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
// Asset Cache Service - 通用资源缓存服务
|
|
2
|
+
// 使用数据库追踪缓存记录,支持多后端(local/S3/WebDAV)
|
|
3
|
+
// 特性:
|
|
4
|
+
// - 基于内容 hash 去重(相同内容只存储一次)
|
|
5
|
+
// - 支持 base64 data URL 解析
|
|
6
|
+
// - 支持普通 HTTP(S) URL 下载
|
|
7
|
+
// - 支持配置公开访问的 baseUrl
|
|
8
|
+
import { Logger } from 'koishi';
|
|
9
|
+
import { createHash, createHmac } from 'crypto';
|
|
10
|
+
import { createReadStream } from 'fs';
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import { join, dirname, basename, resolve } from 'path';
|
|
13
|
+
/**
|
|
14
|
+
* 解析 base64 data URL
|
|
15
|
+
* 格式: data:[<mediatype>][;base64],<data>
|
|
16
|
+
*/
|
|
17
|
+
function parseDataUrl(dataUrl) {
|
|
18
|
+
const match = dataUrl.match(/^data:([^;,]+)?(?:;base64)?,(.*)$/);
|
|
19
|
+
if (!match)
|
|
20
|
+
return null;
|
|
21
|
+
const mimeType = match[1] || 'application/octet-stream';
|
|
22
|
+
const base64Data = match[2];
|
|
23
|
+
try {
|
|
24
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
25
|
+
return { mimeType, buffer };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 检查是否是 data URL
|
|
33
|
+
*/
|
|
34
|
+
function isDataUrl(url) {
|
|
35
|
+
return url.startsWith('data:');
|
|
36
|
+
}
|
|
37
|
+
// ============ S3 上传辅助函数 ============
|
|
38
|
+
function toAmzDate(d) {
|
|
39
|
+
const pad = (n, w = 2) => n.toString().padStart(w, '0');
|
|
40
|
+
return (d.getUTCFullYear().toString() +
|
|
41
|
+
pad(d.getUTCMonth() + 1) +
|
|
42
|
+
pad(d.getUTCDate()) +
|
|
43
|
+
'T' +
|
|
44
|
+
pad(d.getUTCHours()) +
|
|
45
|
+
pad(d.getUTCMinutes()) +
|
|
46
|
+
pad(d.getUTCSeconds()) +
|
|
47
|
+
'Z');
|
|
48
|
+
}
|
|
49
|
+
function sha256Hex(data) {
|
|
50
|
+
return createHash('sha256').update(data).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
function hmac(key, data) {
|
|
53
|
+
return createHmac('sha256', key).update(data, 'utf8').digest();
|
|
54
|
+
}
|
|
55
|
+
function hmacHex(key, data) {
|
|
56
|
+
return createHmac('sha256', key).update(data, 'utf8').digest('hex');
|
|
57
|
+
}
|
|
58
|
+
function getSignatureKey(secretKey, dateStamp, regionName, serviceName) {
|
|
59
|
+
const kDate = hmac('AWS4' + secretKey, dateStamp);
|
|
60
|
+
const kRegion = hmac(kDate, regionName);
|
|
61
|
+
const kService = hmac(kRegion, serviceName);
|
|
62
|
+
return hmac(kService, 'aws4_request');
|
|
63
|
+
}
|
|
64
|
+
function trimSpaces(str) {
|
|
65
|
+
return str.replace(/\s+/g, ' ').trim();
|
|
66
|
+
}
|
|
67
|
+
function encodeKey(key) {
|
|
68
|
+
return key.split('/').map(encodeURIComponent).join('/');
|
|
69
|
+
}
|
|
70
|
+
function joinUrl(base, key) {
|
|
71
|
+
const b = base.replace(/\/$/, '');
|
|
72
|
+
return `${b}/${encodeKey(key)}`;
|
|
73
|
+
}
|
|
74
|
+
function buildS3HostAndPath(config, key) {
|
|
75
|
+
let base;
|
|
76
|
+
if (config.s3Endpoint) {
|
|
77
|
+
base = new URL(config.s3Endpoint);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
base = new URL(`https://s3.${config.s3Region || 'us-east-1'}.amazonaws.com`);
|
|
81
|
+
}
|
|
82
|
+
const bucket = config.s3Bucket;
|
|
83
|
+
const isIp = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(base.hostname);
|
|
84
|
+
const hasPort = !!base.port;
|
|
85
|
+
const isLocal = base.hostname === 'localhost';
|
|
86
|
+
const preferPath = isIp || hasPort || isLocal;
|
|
87
|
+
const usePath = !!config.s3ForcePathStyle || preferPath;
|
|
88
|
+
const hostname = base.hostname;
|
|
89
|
+
const port = base.port ? `:${base.port}` : '';
|
|
90
|
+
let host = hostname + port;
|
|
91
|
+
let path = '';
|
|
92
|
+
if (usePath) {
|
|
93
|
+
path = `/${bucket}/${encodeKey(key)}`;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
host = `${bucket}.${hostname}${port}`;
|
|
97
|
+
path = `/${encodeKey(key)}`;
|
|
98
|
+
}
|
|
99
|
+
const url = `${base.protocol}//${host}${path}`;
|
|
100
|
+
const canonicalUri = path;
|
|
101
|
+
return { host, url, canonicalUri };
|
|
102
|
+
}
|
|
103
|
+
async function uploadToS3(buffer, filename, mime, config) {
|
|
104
|
+
const bucket = config.s3Bucket;
|
|
105
|
+
const region = config.s3Region || 'us-east-1';
|
|
106
|
+
const accessKeyId = config.s3AccessKeyId;
|
|
107
|
+
const secretAccessKey = config.s3SecretAccessKey;
|
|
108
|
+
if (!bucket)
|
|
109
|
+
throw new Error('S3 缺少 bucket 配置');
|
|
110
|
+
if (!accessKeyId || !secretAccessKey)
|
|
111
|
+
throw new Error('S3 需提供访问凭证');
|
|
112
|
+
const now = new Date();
|
|
113
|
+
const amzDate = toAmzDate(now);
|
|
114
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
115
|
+
const key = filename;
|
|
116
|
+
const hostInfo = buildS3HostAndPath(config, key);
|
|
117
|
+
const { host, url, canonicalUri } = hostInfo;
|
|
118
|
+
const payloadHash = sha256Hex(buffer);
|
|
119
|
+
const headers = {
|
|
120
|
+
'host': host,
|
|
121
|
+
'x-amz-content-sha256': payloadHash,
|
|
122
|
+
'x-amz-date': amzDate,
|
|
123
|
+
};
|
|
124
|
+
if (mime)
|
|
125
|
+
headers['content-type'] = mime;
|
|
126
|
+
if (config.s3Acl)
|
|
127
|
+
headers['x-amz-acl'] = config.s3Acl;
|
|
128
|
+
const signedHeaders = Object.keys(headers)
|
|
129
|
+
.map(k => k.toLowerCase())
|
|
130
|
+
.sort()
|
|
131
|
+
.join(';');
|
|
132
|
+
const canonicalHeaders = Object.keys(headers)
|
|
133
|
+
.map(k => k.toLowerCase())
|
|
134
|
+
.sort()
|
|
135
|
+
.map(k => `${k}:${trimSpaces(String(headers[k]))}\n`)
|
|
136
|
+
.join('');
|
|
137
|
+
const canonicalRequest = [
|
|
138
|
+
'PUT',
|
|
139
|
+
canonicalUri,
|
|
140
|
+
'',
|
|
141
|
+
canonicalHeaders,
|
|
142
|
+
signedHeaders,
|
|
143
|
+
payloadHash,
|
|
144
|
+
].join('\n');
|
|
145
|
+
const algorithm = 'AWS4-HMAC-SHA256';
|
|
146
|
+
const credentialScope = `${dateStamp}/${region}/s3/aws4_request`;
|
|
147
|
+
const stringToSign = [
|
|
148
|
+
algorithm,
|
|
149
|
+
amzDate,
|
|
150
|
+
credentialScope,
|
|
151
|
+
sha256Hex(Buffer.from(canonicalRequest, 'utf8')),
|
|
152
|
+
].join('\n');
|
|
153
|
+
const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, 's3');
|
|
154
|
+
const signature = hmacHex(signingKey, stringToSign);
|
|
155
|
+
const authorization = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
156
|
+
const reqHeaders = { ...headers, Authorization: authorization };
|
|
157
|
+
const resp = await fetch(url, { method: 'PUT', headers: reqHeaders, body: new Uint8Array(buffer) });
|
|
158
|
+
if (!resp.ok)
|
|
159
|
+
throw new Error(`S3 上传失败: ${resp.status}`);
|
|
160
|
+
const publicUrl = config.s3PublicBaseUrl
|
|
161
|
+
? joinUrl(config.s3PublicBaseUrl, key)
|
|
162
|
+
: url;
|
|
163
|
+
return { url: publicUrl, key };
|
|
164
|
+
}
|
|
165
|
+
// ============ WebDAV 上传辅助函数 ============
|
|
166
|
+
function b64(str) {
|
|
167
|
+
return Buffer.from(str, 'utf8').toString('base64');
|
|
168
|
+
}
|
|
169
|
+
function trimSlash(s) {
|
|
170
|
+
return s.replace(/\/+$/, '');
|
|
171
|
+
}
|
|
172
|
+
function encodePath(path) {
|
|
173
|
+
return path.split('/').map(encodeURIComponent).join('/');
|
|
174
|
+
}
|
|
175
|
+
async function ensureWebDavBasePath(config) {
|
|
176
|
+
const base = (config.webdavBasePath || '').replace(/^\/+|\/+$/g, '');
|
|
177
|
+
if (!base)
|
|
178
|
+
return;
|
|
179
|
+
const segments = base.split('/').filter(Boolean);
|
|
180
|
+
let current = '';
|
|
181
|
+
for (const seg of segments) {
|
|
182
|
+
current = current ? `${current}/${seg}` : seg;
|
|
183
|
+
const url = `${trimSlash(config.webdavEndpoint)}/${encodePath(current)}`;
|
|
184
|
+
try {
|
|
185
|
+
await fetch(url, {
|
|
186
|
+
method: 'MKCOL',
|
|
187
|
+
headers: { 'Authorization': `Basic ${b64(`${config.webdavUsername}:${config.webdavPassword}`)}` }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Ignore errors
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function uploadToWebDav(buffer, filename, mime, config) {
|
|
196
|
+
if (!config.webdavEndpoint || !config.webdavUsername || !config.webdavPassword) {
|
|
197
|
+
throw new Error('WebDAV 缺少必要配置');
|
|
198
|
+
}
|
|
199
|
+
await ensureWebDavBasePath(config);
|
|
200
|
+
const remotePath = config.webdavBasePath
|
|
201
|
+
? `${config.webdavBasePath.replace(/\/+$/, '')}/${filename}`
|
|
202
|
+
: filename;
|
|
203
|
+
const url = `${trimSlash(config.webdavEndpoint)}/${encodePath(remotePath)}`;
|
|
204
|
+
const headers = {
|
|
205
|
+
'Authorization': `Basic ${b64(`${config.webdavUsername}:${config.webdavPassword}`)}`,
|
|
206
|
+
};
|
|
207
|
+
if (mime)
|
|
208
|
+
headers['Content-Type'] = mime;
|
|
209
|
+
const res = await fetch(url, { method: 'PUT', headers: headers, body: new Uint8Array(buffer) });
|
|
210
|
+
if (!res.ok)
|
|
211
|
+
throw new Error(`WebDAV 上传失败: ${res.status}`);
|
|
212
|
+
const publicUrl = config.webdavPublicBaseUrl
|
|
213
|
+
? `${trimSlash(config.webdavPublicBaseUrl)}/${encodePath(remotePath)}`
|
|
214
|
+
: url;
|
|
215
|
+
return { url: publicUrl, key: remotePath };
|
|
216
|
+
}
|
|
217
|
+
// ============ 本地上传辅助函数 ============
|
|
218
|
+
// 自动检测的 server baseUrl(从 Koishi server 获取)
|
|
219
|
+
let autoDetectedBaseUrl = null;
|
|
220
|
+
// 本地存储根目录(绝对路径)
|
|
221
|
+
let localStorageRoot = null;
|
|
222
|
+
// 本地存储路径(URL 路径)
|
|
223
|
+
let localStoragePath = '/media-luna/assets';
|
|
224
|
+
/**
|
|
225
|
+
* 初始化本地存储服务(仿照 Koishi assets-local 实现)
|
|
226
|
+
* - 使用 ctx.server 注册路由
|
|
227
|
+
* - 使用 file-type 流式检测 MIME
|
|
228
|
+
* - 使用 basename 防止路径遍历
|
|
229
|
+
*/
|
|
230
|
+
export function initAssetCacheLocalStorage(ctx, logger) {
|
|
231
|
+
const baseDir = ctx.baseDir || process.cwd();
|
|
232
|
+
localStorageRoot = resolve(baseDir, 'data/media-luna/assets');
|
|
233
|
+
// 获取 selfUrl
|
|
234
|
+
const server = ctx.get('server');
|
|
235
|
+
if (server) {
|
|
236
|
+
const selfUrl = server.config?.selfUrl;
|
|
237
|
+
if (selfUrl) {
|
|
238
|
+
autoDetectedBaseUrl = selfUrl.replace(/\/$/, '');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// 如果没有 selfUrl,监听 server/ready 事件获取
|
|
242
|
+
if (!autoDetectedBaseUrl) {
|
|
243
|
+
ctx.on('server/ready', () => {
|
|
244
|
+
const srv = ctx.get('server');
|
|
245
|
+
if (srv) {
|
|
246
|
+
const selfUrl = srv.config?.selfUrl;
|
|
247
|
+
if (selfUrl) {
|
|
248
|
+
autoDetectedBaseUrl = selfUrl.replace(/\/$/, '');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const port = srv.config?.port || 5140;
|
|
252
|
+
const host = srv.config?.host || 'localhost';
|
|
253
|
+
autoDetectedBaseUrl = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`;
|
|
254
|
+
}
|
|
255
|
+
logger.info(`Asset cache base URL: ${autoDetectedBaseUrl}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// 注册路由(仿照 assets-local)
|
|
260
|
+
if (server) {
|
|
261
|
+
// 获取统计信息
|
|
262
|
+
server.get(localStoragePath, async (reqCtx) => {
|
|
263
|
+
const stats = await getLocalStorageStats();
|
|
264
|
+
reqCtx.body = stats;
|
|
265
|
+
});
|
|
266
|
+
// 提供文件服务(仿照 assets-local: 使用流式读取 + file-type 自动检测)
|
|
267
|
+
server.get(localStoragePath + '/:name', async (reqCtx) => {
|
|
268
|
+
// 使用 basename 防止路径遍历(与 assets-local 一致)
|
|
269
|
+
const name = basename(reqCtx.params.name);
|
|
270
|
+
// 使用默认的存储根目录(在路由注册时已经确定)
|
|
271
|
+
const root = localStorageRoot;
|
|
272
|
+
const filename = resolve(root, name);
|
|
273
|
+
logger.debug(`Serving file: ${filename} (root: ${root}, name: ${name})`);
|
|
274
|
+
try {
|
|
275
|
+
// 先检查文件是否存在
|
|
276
|
+
await fs.access(filename);
|
|
277
|
+
// 动态导入 file-type(ESM 模块)
|
|
278
|
+
const fileType = await import('file-type');
|
|
279
|
+
// file-type 的 stream 函数可能在 default 上或直接导出
|
|
280
|
+
const streamFn = fileType.stream || fileType.default?.stream;
|
|
281
|
+
if (streamFn) {
|
|
282
|
+
const stream = await streamFn(createReadStream(filename));
|
|
283
|
+
reqCtx.type = stream.fileType?.mime || 'application/octet-stream';
|
|
284
|
+
reqCtx.body = stream;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// 回退:直接读取文件,根据扩展名猜测 MIME
|
|
288
|
+
const ext = name.split('.').pop()?.toLowerCase();
|
|
289
|
+
reqCtx.type = getMimeFromExtension(ext) || 'application/octet-stream';
|
|
290
|
+
reqCtx.body = createReadStream(filename);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
logger.warn(`File not found or error: ${filename}`, err);
|
|
295
|
+
reqCtx.status = 404;
|
|
296
|
+
reqCtx.body = 'File not found';
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// 兼容旧路径 /media-luna/cache/:name
|
|
300
|
+
server.get('/media-luna/cache/:name', async (reqCtx) => {
|
|
301
|
+
const name = basename(reqCtx.params.name);
|
|
302
|
+
const root = localStorageRoot;
|
|
303
|
+
const filename = resolve(root, name);
|
|
304
|
+
try {
|
|
305
|
+
await fs.access(filename);
|
|
306
|
+
const fileType = await import('file-type');
|
|
307
|
+
const streamFn = fileType.stream || fileType.default?.stream;
|
|
308
|
+
if (streamFn) {
|
|
309
|
+
const stream = await streamFn(createReadStream(filename));
|
|
310
|
+
reqCtx.type = stream.fileType?.mime || 'application/octet-stream';
|
|
311
|
+
reqCtx.body = stream;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
const ext = name.split('.').pop()?.toLowerCase();
|
|
315
|
+
reqCtx.type = getMimeFromExtension(ext) || 'application/octet-stream';
|
|
316
|
+
reqCtx.body = createReadStream(filename);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
reqCtx.status = 404;
|
|
321
|
+
reqCtx.body = 'File not found';
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
logger.info('Asset cache routes registered: /media-luna/assets/*, /media-luna/cache/*');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* 获取本地存储统计信息(仿照 assets-local)
|
|
329
|
+
*/
|
|
330
|
+
async function getLocalStorageStats() {
|
|
331
|
+
if (!localStorageRoot) {
|
|
332
|
+
return { assetCount: 0, assetSize: 0 };
|
|
333
|
+
}
|
|
334
|
+
let assetCount = 0;
|
|
335
|
+
let assetSize = 0;
|
|
336
|
+
try {
|
|
337
|
+
const files = await fs.readdir(localStorageRoot);
|
|
338
|
+
assetCount = files.length;
|
|
339
|
+
await Promise.all(files.map(async (file) => {
|
|
340
|
+
try {
|
|
341
|
+
const fileStat = await fs.stat(resolve(localStorageRoot, file));
|
|
342
|
+
assetSize += fileStat.size;
|
|
343
|
+
}
|
|
344
|
+
catch { }
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
catch { }
|
|
348
|
+
return { assetCount, assetSize };
|
|
349
|
+
}
|
|
350
|
+
function getLocalPublicUrl(config, relativePath) {
|
|
351
|
+
const publicPath = config.localPublicPath || '/media-luna/assets';
|
|
352
|
+
// 优先使用用户配置的 baseUrl,否则使用自动检测的
|
|
353
|
+
const baseUrl = config.localPublicBaseUrl || autoDetectedBaseUrl || '';
|
|
354
|
+
return `${baseUrl}${publicPath}/${relativePath}`;
|
|
355
|
+
}
|
|
356
|
+
async function uploadToLocal(buffer, filename, mime, config) {
|
|
357
|
+
const cacheDir = config.localCacheDir || 'data/media-luna/assets';
|
|
358
|
+
const fullCacheDir = join(process.cwd(), cacheDir);
|
|
359
|
+
const filePath = join(fullCacheDir, filename);
|
|
360
|
+
// 确保目录存在
|
|
361
|
+
await fs.mkdir(dirname(filePath), { recursive: true });
|
|
362
|
+
// 写入文件
|
|
363
|
+
await fs.writeFile(filePath, buffer);
|
|
364
|
+
const publicUrl = getLocalPublicUrl(config, filename);
|
|
365
|
+
return {
|
|
366
|
+
url: publicUrl,
|
|
367
|
+
key: filename
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// ============ Koishi Assets 上传辅助函数 ============
|
|
371
|
+
/**
|
|
372
|
+
* 使用 Koishi 的 assets 服务上传资源
|
|
373
|
+
* 需要用户安装 assets-local、assets-s3 等 assets 插件
|
|
374
|
+
*/
|
|
375
|
+
async function uploadWithKoishiAssets(ctx, source, filename) {
|
|
376
|
+
const assets = ctx.get('assets');
|
|
377
|
+
if (!assets) {
|
|
378
|
+
throw new Error('Koishi assets service is not available. Please install an assets plugin (e.g. assets-local, assets-s3).');
|
|
379
|
+
}
|
|
380
|
+
// ctx.assets.upload(url, file) 会自动下载 URL 内容并存储
|
|
381
|
+
// 对于 data URL 也可以直接处理
|
|
382
|
+
const cachedUrl = await assets.upload(source, filename);
|
|
383
|
+
return {
|
|
384
|
+
url: cachedUrl,
|
|
385
|
+
key: filename
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* 检查 Koishi assets 服务是否可用
|
|
390
|
+
*/
|
|
391
|
+
function isKoishiAssetsAvailable(ctx) {
|
|
392
|
+
try {
|
|
393
|
+
const assets = ctx.get('assets');
|
|
394
|
+
return !!assets;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// ============ MIME 类型辅助函数 ============
|
|
401
|
+
const extensionMimeMap = {
|
|
402
|
+
'jpg': 'image/jpeg',
|
|
403
|
+
'jpeg': 'image/jpeg',
|
|
404
|
+
'png': 'image/png',
|
|
405
|
+
'gif': 'image/gif',
|
|
406
|
+
'webp': 'image/webp',
|
|
407
|
+
'svg': 'image/svg+xml',
|
|
408
|
+
'mp3': 'audio/mpeg',
|
|
409
|
+
'wav': 'audio/wav',
|
|
410
|
+
'ogg': 'audio/ogg',
|
|
411
|
+
'mp4': 'video/mp4',
|
|
412
|
+
'webm': 'video/webm'
|
|
413
|
+
};
|
|
414
|
+
function getMimeFromExtension(ext) {
|
|
415
|
+
return extensionMimeMap[ext || ''] || 'application/octet-stream';
|
|
416
|
+
}
|
|
417
|
+
function getMimeFromUrl(url) {
|
|
418
|
+
const ext = url.split('.').pop()?.toLowerCase()?.split('?')[0];
|
|
419
|
+
return getMimeFromExtension(ext);
|
|
420
|
+
}
|
|
421
|
+
function getExtensionFromMime(mime) {
|
|
422
|
+
const mimeMap = {
|
|
423
|
+
'image/png': '.png',
|
|
424
|
+
'image/jpeg': '.jpg',
|
|
425
|
+
'image/gif': '.gif',
|
|
426
|
+
'image/webp': '.webp',
|
|
427
|
+
'image/svg+xml': '.svg',
|
|
428
|
+
'video/mp4': '.mp4',
|
|
429
|
+
'video/webm': '.webm',
|
|
430
|
+
'audio/mp3': '.mp3',
|
|
431
|
+
'audio/mpeg': '.mp3',
|
|
432
|
+
'audio/wav': '.wav',
|
|
433
|
+
'audio/ogg': '.ogg',
|
|
434
|
+
'application/pdf': '.pdf',
|
|
435
|
+
};
|
|
436
|
+
return mimeMap[mime] || '.bin';
|
|
437
|
+
}
|
|
438
|
+
// ============ Asset Cache Service ============
|
|
439
|
+
export class AssetCacheService {
|
|
440
|
+
ctx;
|
|
441
|
+
logger;
|
|
442
|
+
config = null;
|
|
443
|
+
configGetter = null;
|
|
444
|
+
constructor(ctx) {
|
|
445
|
+
this.ctx = ctx;
|
|
446
|
+
this.logger = new Logger('media-luna:asset-cache');
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 设置配置获取函数(避免循环依赖)
|
|
450
|
+
*/
|
|
451
|
+
setConfigGetter(getter) {
|
|
452
|
+
this.configGetter = getter;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* 设置存储配置
|
|
456
|
+
*/
|
|
457
|
+
setConfig(config) {
|
|
458
|
+
this.config = config;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 获取当前配置
|
|
462
|
+
*/
|
|
463
|
+
getConfig() {
|
|
464
|
+
return this.config;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* 检查 Koishi assets 服务是否可用
|
|
468
|
+
*/
|
|
469
|
+
isAssetsServiceAvailable() {
|
|
470
|
+
return isKoishiAssetsAvailable(this.ctx);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 从设置中加载存储配置(从 YAML 配置文件)
|
|
474
|
+
*/
|
|
475
|
+
async loadConfigFromSettings() {
|
|
476
|
+
try {
|
|
477
|
+
// 使用配置获取函数(避免循环依赖)
|
|
478
|
+
if (this.configGetter) {
|
|
479
|
+
const mwConfig = this.configGetter();
|
|
480
|
+
if (mwConfig.config && mwConfig.config.backend && mwConfig.config.backend !== 'none') {
|
|
481
|
+
this.config = mwConfig.config;
|
|
482
|
+
return this.config;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
this.logger.warn('Failed to load storage config from settings:', e);
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* 计算内容的 SHA256 hash
|
|
493
|
+
*/
|
|
494
|
+
hashContent(buffer) {
|
|
495
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* 计算源 URL 的 hash(用于记录来源,非去重)
|
|
499
|
+
*/
|
|
500
|
+
hashSource(source) {
|
|
501
|
+
return createHash('sha256').update(source).digest('hex');
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* 根据源 URL hash 快速查找缓存(第一级查找)
|
|
505
|
+
*/
|
|
506
|
+
async getCachedBySource(sourceUrl) {
|
|
507
|
+
const sourceHash = this.hashSource(sourceUrl);
|
|
508
|
+
const rows = await this.ctx.database.get('medialuna_asset_cache', { sourceHash });
|
|
509
|
+
if (rows.length > 0) {
|
|
510
|
+
// 更新最后访问时间
|
|
511
|
+
await this.ctx.database.set('medialuna_asset_cache', { id: rows[0].id }, {
|
|
512
|
+
lastAccessedAt: new Date()
|
|
513
|
+
});
|
|
514
|
+
return rows[0];
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* 根据内容 hash 检查是否已缓存(第二级查找,用于去重)
|
|
520
|
+
*/
|
|
521
|
+
async getCachedByContent(contentHash) {
|
|
522
|
+
const rows = await this.ctx.database.get('medialuna_asset_cache', { contentHash });
|
|
523
|
+
if (rows.length > 0) {
|
|
524
|
+
// 更新最后访问时间
|
|
525
|
+
await this.ctx.database.set('medialuna_asset_cache', { id: rows[0].id }, {
|
|
526
|
+
lastAccessedAt: new Date()
|
|
527
|
+
});
|
|
528
|
+
return rows[0];
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* 获取资源内容(处理 URL 或 data URL)
|
|
534
|
+
*/
|
|
535
|
+
async fetchContent(source) {
|
|
536
|
+
// 处理 base64 data URL
|
|
537
|
+
if (isDataUrl(source)) {
|
|
538
|
+
const parsed = parseDataUrl(source);
|
|
539
|
+
if (!parsed) {
|
|
540
|
+
throw new Error('Invalid data URL format');
|
|
541
|
+
}
|
|
542
|
+
return { buffer: parsed.buffer, mimeType: parsed.mimeType };
|
|
543
|
+
}
|
|
544
|
+
// 普通 HTTP(S) URL
|
|
545
|
+
this.logger.debug(`Downloading ${source.slice(0, 100)}...`);
|
|
546
|
+
const response = await fetch(source);
|
|
547
|
+
if (!response.ok) {
|
|
548
|
+
throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
|
|
549
|
+
}
|
|
550
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
551
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
552
|
+
const contentType = response.headers.get('content-type') || getMimeFromUrl(source);
|
|
553
|
+
const mimeType = contentType.split(';')[0].trim();
|
|
554
|
+
return { buffer, mimeType };
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* 缓存资源(URL 或 data URL)
|
|
558
|
+
*
|
|
559
|
+
* 使用两级缓存查找策略:
|
|
560
|
+
* 1. 第一级:用 sourceUrl 的 hash 快速查找(大部分情况命中,避免下载)
|
|
561
|
+
* 2. 第二级:下载后用 contentHash 查找(处理不同 URL 相同内容的去重)
|
|
562
|
+
*
|
|
563
|
+
* @param source 原始 URL 或 base64 data URL
|
|
564
|
+
* @returns 缓存结果
|
|
565
|
+
*/
|
|
566
|
+
async cacheAsset(source) {
|
|
567
|
+
// 检查配置
|
|
568
|
+
if (!this.config) {
|
|
569
|
+
await this.loadConfigFromSettings();
|
|
570
|
+
}
|
|
571
|
+
if (!this.config) {
|
|
572
|
+
throw new Error('Asset cache not configured. Please configure storage settings first.');
|
|
573
|
+
}
|
|
574
|
+
// 第一级查找:用源 URL 快速查找(避免下载)
|
|
575
|
+
// 对于 base64 不做这个优化,因为 base64 本身就包含内容
|
|
576
|
+
if (!isDataUrl(source)) {
|
|
577
|
+
const existingBySource = await this.getCachedBySource(source);
|
|
578
|
+
if (existingBySource) {
|
|
579
|
+
this.logger.debug(`Cache hit by source URL: ${source.slice(0, 50)}`);
|
|
580
|
+
return {
|
|
581
|
+
cachedUrl: existingBySource.cachedUrl,
|
|
582
|
+
fromCache: true,
|
|
583
|
+
backend: existingBySource.backend,
|
|
584
|
+
fileSize: existingBySource.fileSize,
|
|
585
|
+
contentHash: existingBySource.contentHash
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// 获取内容(下载或解析 base64)
|
|
590
|
+
const { buffer, mimeType } = await this.fetchContent(source);
|
|
591
|
+
// 计算内容 hash
|
|
592
|
+
const contentHash = this.hashContent(buffer);
|
|
593
|
+
// 第二级查找:根据内容 hash 检查是否已缓存(处理不同 URL 相同内容的情况)
|
|
594
|
+
const existingByContent = await this.getCachedByContent(contentHash);
|
|
595
|
+
if (existingByContent) {
|
|
596
|
+
this.logger.debug(`Cache hit by content hash: ${contentHash.slice(0, 12)}`);
|
|
597
|
+
// 如果是新的源 URL,添加一条记录指向同一个缓存文件
|
|
598
|
+
// 这样下次用这个 URL 访问时可以走第一级快速路径
|
|
599
|
+
if (!isDataUrl(source)) {
|
|
600
|
+
const sourceHash = this.hashSource(source);
|
|
601
|
+
try {
|
|
602
|
+
await this.ctx.database.create('medialuna_asset_cache', {
|
|
603
|
+
sourceUrl: source,
|
|
604
|
+
sourceHash,
|
|
605
|
+
contentHash,
|
|
606
|
+
backend: existingByContent.backend,
|
|
607
|
+
cachedUrl: existingByContent.cachedUrl,
|
|
608
|
+
cachedKey: existingByContent.cachedKey,
|
|
609
|
+
mimeType: existingByContent.mimeType,
|
|
610
|
+
fileSize: existingByContent.fileSize,
|
|
611
|
+
createdAt: new Date(),
|
|
612
|
+
lastAccessedAt: new Date()
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// 可能 sourceHash 已存在,忽略
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
cachedUrl: existingByContent.cachedUrl,
|
|
621
|
+
fromCache: true,
|
|
622
|
+
backend: existingByContent.backend,
|
|
623
|
+
fileSize: existingByContent.fileSize,
|
|
624
|
+
contentHash
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
// 生成唯一文件名(使用内容 hash)
|
|
628
|
+
const ext = getExtensionFromMime(mimeType);
|
|
629
|
+
const filename = `${contentHash.slice(0, 16)}${ext}`;
|
|
630
|
+
// 上传到对应后端
|
|
631
|
+
let result;
|
|
632
|
+
switch (this.config.backend) {
|
|
633
|
+
case 'koishi':
|
|
634
|
+
// 使用 Koishi 的 assets 服务
|
|
635
|
+
// 将 buffer 转换为 data URL 传递给 assets 服务,避免重复下载
|
|
636
|
+
const dataUrl = `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
637
|
+
result = await uploadWithKoishiAssets(this.ctx, dataUrl, filename);
|
|
638
|
+
break;
|
|
639
|
+
case 'local':
|
|
640
|
+
result = await uploadToLocal(buffer, filename, mimeType, this.config);
|
|
641
|
+
break;
|
|
642
|
+
case 's3':
|
|
643
|
+
result = await uploadToS3(buffer, filename, mimeType, this.config);
|
|
644
|
+
break;
|
|
645
|
+
case 'webdav':
|
|
646
|
+
result = await uploadToWebDav(buffer, filename, mimeType, this.config);
|
|
647
|
+
break;
|
|
648
|
+
default:
|
|
649
|
+
throw new Error(`Unknown backend: ${this.config.backend}`);
|
|
650
|
+
}
|
|
651
|
+
// 计算源 hash
|
|
652
|
+
const sourceForRecord = isDataUrl(source)
|
|
653
|
+
? `base64://${contentHash}`
|
|
654
|
+
: source;
|
|
655
|
+
const sourceHash = this.hashSource(sourceForRecord);
|
|
656
|
+
// 保存到数据库
|
|
657
|
+
const now = new Date();
|
|
658
|
+
await this.ctx.database.create('medialuna_asset_cache', {
|
|
659
|
+
sourceUrl: sourceForRecord,
|
|
660
|
+
sourceHash,
|
|
661
|
+
contentHash,
|
|
662
|
+
backend: this.config.backend,
|
|
663
|
+
cachedUrl: result.url,
|
|
664
|
+
cachedKey: result.key,
|
|
665
|
+
mimeType,
|
|
666
|
+
fileSize: buffer.length,
|
|
667
|
+
createdAt: now,
|
|
668
|
+
lastAccessedAt: now
|
|
669
|
+
});
|
|
670
|
+
this.logger.info(`Cached ${isDataUrl(source) ? '[base64 data]' : source.slice(0, 50)} -> ${result.url} (${this.config.backend})`);
|
|
671
|
+
return {
|
|
672
|
+
cachedUrl: result.url,
|
|
673
|
+
fromCache: false,
|
|
674
|
+
backend: this.config.backend,
|
|
675
|
+
fileSize: buffer.length,
|
|
676
|
+
contentHash
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* 批量缓存资源
|
|
681
|
+
*/
|
|
682
|
+
async cacheAssets(sources) {
|
|
683
|
+
const results = new Map();
|
|
684
|
+
for (const source of sources) {
|
|
685
|
+
try {
|
|
686
|
+
const result = await this.cacheAsset(source);
|
|
687
|
+
results.set(source, result);
|
|
688
|
+
}
|
|
689
|
+
catch (e) {
|
|
690
|
+
this.logger.warn(`Failed to cache ${isDataUrl(source) ? '[base64]' : source.slice(0, 50)}:`, e);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return results;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* 从 Buffer 缓存资源(用于上传场景)
|
|
697
|
+
*/
|
|
698
|
+
async cacheBuffer(buffer, mimeType, sourceIdentifier) {
|
|
699
|
+
// 检查配置
|
|
700
|
+
if (!this.config) {
|
|
701
|
+
await this.loadConfigFromSettings();
|
|
702
|
+
}
|
|
703
|
+
if (!this.config) {
|
|
704
|
+
throw new Error('Asset cache not configured. Please configure storage settings first.');
|
|
705
|
+
}
|
|
706
|
+
// 计算内容 hash
|
|
707
|
+
const contentHash = this.hashContent(buffer);
|
|
708
|
+
// 根据内容 hash 检查是否已缓存
|
|
709
|
+
const existing = await this.getCachedByContent(contentHash);
|
|
710
|
+
if (existing) {
|
|
711
|
+
return {
|
|
712
|
+
cachedUrl: existing.cachedUrl,
|
|
713
|
+
fromCache: true,
|
|
714
|
+
backend: existing.backend,
|
|
715
|
+
fileSize: existing.fileSize,
|
|
716
|
+
contentHash
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
// 生成文件名
|
|
720
|
+
const ext = getExtensionFromMime(mimeType);
|
|
721
|
+
const filename = `${contentHash.slice(0, 16)}${ext}`;
|
|
722
|
+
// 上传
|
|
723
|
+
let result;
|
|
724
|
+
switch (this.config.backend) {
|
|
725
|
+
case 'koishi':
|
|
726
|
+
// 使用 Koishi 的 assets 服务
|
|
727
|
+
const dataUrl = `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
728
|
+
result = await uploadWithKoishiAssets(this.ctx, dataUrl, filename);
|
|
729
|
+
break;
|
|
730
|
+
case 'local':
|
|
731
|
+
result = await uploadToLocal(buffer, filename, mimeType, this.config);
|
|
732
|
+
break;
|
|
733
|
+
case 's3':
|
|
734
|
+
result = await uploadToS3(buffer, filename, mimeType, this.config);
|
|
735
|
+
break;
|
|
736
|
+
case 'webdav':
|
|
737
|
+
result = await uploadToWebDav(buffer, filename, mimeType, this.config);
|
|
738
|
+
break;
|
|
739
|
+
default:
|
|
740
|
+
throw new Error(`Unknown backend: ${this.config.backend}`);
|
|
741
|
+
}
|
|
742
|
+
// 生成源标识
|
|
743
|
+
const sourceUrl = sourceIdentifier || `buffer://${contentHash}`;
|
|
744
|
+
const sourceHash = this.hashSource(sourceUrl);
|
|
745
|
+
// 保存到数据库
|
|
746
|
+
const now = new Date();
|
|
747
|
+
await this.ctx.database.create('medialuna_asset_cache', {
|
|
748
|
+
sourceUrl,
|
|
749
|
+
sourceHash,
|
|
750
|
+
contentHash,
|
|
751
|
+
backend: this.config.backend,
|
|
752
|
+
cachedUrl: result.url,
|
|
753
|
+
cachedKey: result.key,
|
|
754
|
+
mimeType,
|
|
755
|
+
fileSize: buffer.length,
|
|
756
|
+
createdAt: now,
|
|
757
|
+
lastAccessedAt: now
|
|
758
|
+
});
|
|
759
|
+
return {
|
|
760
|
+
cachedUrl: result.url,
|
|
761
|
+
fromCache: false,
|
|
762
|
+
backend: this.config.backend,
|
|
763
|
+
fileSize: buffer.length,
|
|
764
|
+
contentHash
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* 获取缓存统计信息
|
|
769
|
+
*/
|
|
770
|
+
async getStats() {
|
|
771
|
+
const allRecords = await this.ctx.database.get('medialuna_asset_cache', {});
|
|
772
|
+
const stats = {
|
|
773
|
+
totalFiles: allRecords.length,
|
|
774
|
+
totalSizeMB: 0,
|
|
775
|
+
byBackend: {},
|
|
776
|
+
oldestAccess: null,
|
|
777
|
+
newestAccess: null
|
|
778
|
+
};
|
|
779
|
+
for (const record of allRecords) {
|
|
780
|
+
stats.totalSizeMB += record.fileSize / (1024 * 1024);
|
|
781
|
+
// 按后端统计
|
|
782
|
+
if (!stats.byBackend[record.backend]) {
|
|
783
|
+
stats.byBackend[record.backend] = { count: 0, sizeMB: 0 };
|
|
784
|
+
}
|
|
785
|
+
stats.byBackend[record.backend].count++;
|
|
786
|
+
stats.byBackend[record.backend].sizeMB += record.fileSize / (1024 * 1024);
|
|
787
|
+
// 最老/最新访问时间
|
|
788
|
+
const accessTime = new Date(record.lastAccessedAt);
|
|
789
|
+
if (!stats.oldestAccess || accessTime < stats.oldestAccess) {
|
|
790
|
+
stats.oldestAccess = accessTime;
|
|
791
|
+
}
|
|
792
|
+
if (!stats.newestAccess || accessTime > stats.newestAccess) {
|
|
793
|
+
stats.newestAccess = accessTime;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return stats;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* 清理过期缓存(LRU 策略)
|
|
800
|
+
* @param maxAgeDays 保留天数
|
|
801
|
+
*/
|
|
802
|
+
async cleanupExpired(maxAgeDays = 30) {
|
|
803
|
+
const cutoff = new Date();
|
|
804
|
+
cutoff.setDate(cutoff.getDate() - maxAgeDays);
|
|
805
|
+
const expiredRecords = await this.ctx.database.get('medialuna_asset_cache', {
|
|
806
|
+
lastAccessedAt: { $lt: cutoff }
|
|
807
|
+
});
|
|
808
|
+
let deletedCount = 0;
|
|
809
|
+
for (const record of expiredRecords) {
|
|
810
|
+
try {
|
|
811
|
+
// 删除本地文件(如果是 local 后端)
|
|
812
|
+
if (record.backend === 'local' && record.cachedKey) {
|
|
813
|
+
const cacheDir = this.config?.localCacheDir || 'data/media-luna/assets';
|
|
814
|
+
const filePath = join(process.cwd(), cacheDir, record.cachedKey);
|
|
815
|
+
try {
|
|
816
|
+
await fs.unlink(filePath);
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
// 文件可能已不存在
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
// 从数据库删除记录
|
|
823
|
+
await this.ctx.database.remove('medialuna_asset_cache', { id: record.id });
|
|
824
|
+
deletedCount++;
|
|
825
|
+
}
|
|
826
|
+
catch (e) {
|
|
827
|
+
this.logger.warn(`Failed to delete cache record ${record.id}:`, e);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (deletedCount > 0) {
|
|
831
|
+
this.logger.info(`Cleaned up ${deletedCount} expired cache entries`);
|
|
832
|
+
}
|
|
833
|
+
return deletedCount;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* 根据内容 hash 删除缓存
|
|
837
|
+
*/
|
|
838
|
+
async deleteCacheByContentHash(contentHash) {
|
|
839
|
+
const records = await this.ctx.database.get('medialuna_asset_cache', { contentHash });
|
|
840
|
+
if (records.length === 0)
|
|
841
|
+
return false;
|
|
842
|
+
for (const record of records) {
|
|
843
|
+
// 删除本地文件
|
|
844
|
+
if (record.backend === 'local' && record.cachedKey) {
|
|
845
|
+
const cacheDir = this.config?.localCacheDir || 'data/media-luna/assets';
|
|
846
|
+
const filePath = join(process.cwd(), cacheDir, record.cachedKey);
|
|
847
|
+
try {
|
|
848
|
+
await fs.unlink(filePath);
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// 忽略
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
await this.ctx.database.remove('medialuna_asset_cache', { id: record.id });
|
|
855
|
+
}
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* 清空所有缓存
|
|
860
|
+
*/
|
|
861
|
+
async clearAll() {
|
|
862
|
+
const allRecords = await this.ctx.database.get('medialuna_asset_cache', {});
|
|
863
|
+
// 删除本地文件
|
|
864
|
+
for (const record of allRecords) {
|
|
865
|
+
if (record.backend === 'local' && record.cachedKey) {
|
|
866
|
+
const cacheDir = this.config?.localCacheDir || 'data/media-luna/assets';
|
|
867
|
+
const filePath = join(process.cwd(), cacheDir, record.cachedKey);
|
|
868
|
+
try {
|
|
869
|
+
await fs.unlink(filePath);
|
|
870
|
+
}
|
|
871
|
+
catch {
|
|
872
|
+
// 忽略
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
// 清空表
|
|
877
|
+
await this.ctx.database.remove('medialuna_asset_cache', {});
|
|
878
|
+
this.logger.info(`Cleared ${allRecords.length} cache entries`);
|
|
879
|
+
return allRecords.length;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
//# sourceMappingURL=asset-cache.service.js.map
|