koishi-plugin-kkk 1.0.0
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/CHANGELOG.md +57 -0
- package/LICENSE +674 -0
- package/README.md +98 -0
- package/assets/web/assets/CronEditor-CYgFpX-g.js +1 -0
- package/assets/web/assets/DesktopLayout-iI43ij11.js +1 -0
- package/assets/web/assets/MobileLayout-CziU8MCR.js +1 -0
- package/assets/web/assets/ThemeSwitch-DFohhwd9.js +1 -0
- package/assets/web/assets/index-BRROK0vh.js +47 -0
- package/assets/web/assets/index-Dj-StEiS.css +2 -0
- package/assets/web/assets/separator-CxcFlCzz.js +1 -0
- package/assets/web/favicon.svg +1 -0
- package/assets/web/icons.svg +24 -0
- package/assets/web/index.html +15 -0
- package/assets/webui.html +115 -0
- package/client/index.ts +63 -0
- package/config/default_config/app.yaml +64 -0
- package/config/default_config/bilibili.yaml +116 -0
- package/config/default_config/config.json +157 -0
- package/config/default_config/cookies.yaml +11 -0
- package/config/default_config/douyin.yaml +99 -0
- package/config/default_config/kuaishou.yaml +8 -0
- package/config/default_config/pushlist.yaml +37 -0
- package/config/default_config/request.yaml +22 -0
- package/config/default_config/upload.yaml +50 -0
- package/config/default_config/xiaohongshu.yaml +23 -0
- package/dist/index.js +1 -0
- package/lib/compat/axios.js +23 -0
- package/lib/compat/cron.js +77 -0
- package/lib/compat/esm.js +15 -0
- package/lib/compat/express.js +23 -0
- package/lib/compat/heroui.js +12 -0
- package/lib/compat/lodash.js +33 -0
- package/lib/compat/logger.js +118 -0
- package/lib/compat/node-karin.js +1053 -0
- package/lib/compat/phosphor-icons.js +27 -0
- package/lib/compat/qqcard.js +256 -0
- package/lib/compat/root.js +44 -0
- package/lib/compat/runtime.js +87 -0
- package/lib/compat/segment.js +167 -0
- package/lib/compat/sqlite3.js +117 -0
- package/lib/compat/start.js +7 -0
- package/lib/compat/syncConfig.js +65 -0
- package/lib/compat/template-react.js +30 -0
- package/lib/compat/text.js +65 -0
- package/lib/compat/yaml.js +13 -0
- package/lib/configBridge.js +91 -0
- package/lib/index.js +775 -0
- package/lib/karin/app.js +34 -0
- package/lib/karin/apps/__ready.js +9 -0
- package/lib/karin/apps/admin.js +136 -0
- package/lib/karin/apps/help.js +233 -0
- package/lib/karin/apps/push.js +291 -0
- package/lib/karin/apps/qrlogin.js +176 -0
- package/lib/karin/apps/statistics.js +209 -0
- package/lib/karin/apps/testPush.js +247 -0
- package/lib/karin/apps/tools.js +473 -0
- package/lib/karin/apps/update.js +252 -0
- package/lib/karin/index.js +37 -0
- package/lib/karin/module/db/bilibili.js +831 -0
- package/lib/karin/module/db/douyin.js +918 -0
- package/lib/karin/module/db/index.js +165 -0
- package/lib/karin/module/db/migration.js +199 -0
- package/lib/karin/module/db/statistics.js +559 -0
- package/lib/karin/module/index.js +94 -0
- package/lib/karin/module/server/constants/routes.js +48 -0
- package/lib/karin/module/server/controllers/bots.js +230 -0
- package/lib/karin/module/server/controllers/config.js +75 -0
- package/lib/karin/module/server/controllers/video.js +208 -0
- package/lib/karin/module/server/index.js +26 -0
- package/lib/karin/module/server/middlewares/auth.js +133 -0
- package/lib/karin/module/server/routes/amagi.js +57 -0
- package/lib/karin/module/server/routes/api.js +40 -0
- package/lib/karin/module/server/routes/index.js +97 -0
- package/lib/karin/module/server/routes/ssr.js +16 -0
- package/lib/karin/module/server/routes/static.js +43 -0
- package/lib/karin/module/utils/Base.js +403 -0
- package/lib/karin/module/utils/CardParser.js +487 -0
- package/lib/karin/module/utils/Common.js +418 -0
- package/lib/karin/module/utils/Config.js +434 -0
- package/lib/karin/module/utils/EmojiReaction.js +242 -0
- package/lib/karin/module/utils/ErrorHandler/handler.js +114 -0
- package/lib/karin/module/utils/ErrorHandler/index.js +55 -0
- package/lib/karin/module/utils/ErrorHandler/render.js +182 -0
- package/lib/karin/module/utils/ErrorHandler/sender.js +206 -0
- package/lib/karin/module/utils/ErrorHandler/strategy.js +41 -0
- package/lib/karin/module/utils/ErrorHandler/types.js +2 -0
- package/lib/karin/module/utils/ErrorHandler/utils.js +108 -0
- package/lib/karin/module/utils/FFmpeg.js +298 -0
- package/lib/karin/module/utils/ImageHelper.js +91 -0
- package/lib/karin/module/utils/ImageSlice.js +429 -0
- package/lib/karin/module/utils/MotionPhoto.js +250 -0
- package/lib/karin/module/utils/Network/Downloader.js +413 -0
- package/lib/karin/module/utils/Network/ImageDownloader.js +250 -0
- package/lib/karin/module/utils/Network/Network.js +332 -0
- package/lib/karin/module/utils/Network/ThrottleStream.js +71 -0
- package/lib/karin/module/utils/Network/constants.js +129 -0
- package/lib/karin/module/utils/Network/helpers.js +174 -0
- package/lib/karin/module/utils/Network/index.js +34 -0
- package/lib/karin/module/utils/Network/types.js +12 -0
- package/lib/karin/module/utils/Networks.js +17 -0
- package/lib/karin/module/utils/ParseLock.js +34 -0
- package/lib/karin/module/utils/ParseOverride.js +88 -0
- package/lib/karin/module/utils/QqCardResolve.js +114 -0
- package/lib/karin/module/utils/QqPanel.js +879 -0
- package/lib/karin/module/utils/Render/coverTheme.js +256 -0
- package/lib/karin/module/utils/Render/index.js +582 -0
- package/lib/karin/module/utils/amagiClient.js +338 -0
- package/lib/karin/module/utils/avatar.js +201 -0
- package/lib/karin/module/utils/bot.js +141 -0
- package/lib/karin/module/utils/build-metadata.js +50 -0
- package/lib/karin/module/utils/changelog.js +166 -0
- package/lib/karin/module/utils/index.js +26 -0
- package/lib/karin/module/utils/loginSession.js +259 -0
- package/lib/karin/module/utils/parseTip.js +93 -0
- package/lib/karin/module/utils/runtime-report.js +140 -0
- package/lib/karin/module/utils/semver.js +72 -0
- package/lib/karin/module/utils/statisticsAggregate.js +415 -0
- package/lib/karin/platform/bilibili/bilibili.js +1805 -0
- package/lib/karin/platform/bilibili/comments.js +294 -0
- package/lib/karin/platform/bilibili/danmaku.js +555 -0
- package/lib/karin/platform/bilibili/dynamic-text.js +827 -0
- package/lib/karin/platform/bilibili/genParams.js +37 -0
- package/lib/karin/platform/bilibili/getID.js +159 -0
- package/lib/karin/platform/bilibili/index.js +24 -0
- package/lib/karin/platform/bilibili/live-status.js +40 -0
- package/lib/karin/platform/bilibili/login.js +132 -0
- package/lib/karin/platform/bilibili/push.js +1397 -0
- package/lib/karin/platform/bilibili/riskControl.js +133 -0
- package/lib/karin/platform/bilibili/search.js +299 -0
- package/lib/karin/platform/common/danmaku-types.js +12 -0
- package/lib/karin/platform/douyin/comments.js +310 -0
- package/lib/karin/platform/douyin/danmaku.js +893 -0
- package/lib/karin/platform/douyin/douyin.js +1145 -0
- package/lib/karin/platform/douyin/getID.js +94 -0
- package/lib/karin/platform/douyin/index.js +24 -0
- package/lib/karin/platform/douyin/login.js +230 -0
- package/lib/karin/platform/douyin/push/favorite.js +75 -0
- package/lib/karin/platform/douyin/push/index.js +21 -0
- package/lib/karin/platform/douyin/push/live.js +69 -0
- package/lib/karin/platform/douyin/push/post.js +60 -0
- package/lib/karin/platform/douyin/push/recommend.js +74 -0
- package/lib/karin/platform/douyin/push/render.js +621 -0
- package/lib/karin/platform/douyin/push/types.js +2 -0
- package/lib/karin/platform/douyin/push.js +1128 -0
- package/lib/karin/platform/douyin/types.js +13 -0
- package/lib/karin/platform/douyin/videoQuality.js +222 -0
- package/lib/karin/platform/douyin/workType.js +182 -0
- package/lib/karin/platform/index.js +20 -0
- package/lib/karin/platform/kuaishou/comments.js +91 -0
- package/lib/karin/platform/kuaishou/getID.js +43 -0
- package/lib/karin/platform/kuaishou/getdata.js +41 -0
- package/lib/karin/platform/kuaishou/index.js +20 -0
- package/lib/karin/platform/kuaishou/kuaishou.js +122 -0
- package/lib/karin/platform/xiaohongshu/comments.js +162 -0
- package/lib/karin/platform/xiaohongshu/getID.js +129 -0
- package/lib/karin/platform/xiaohongshu/index.js +18 -0
- package/lib/karin/platform/xiaohongshu/xiaohongshu.js +643 -0
- package/lib/karin/root.js +73 -0
- package/lib/karin/setup.js +99 -0
- package/lib/karin/types/config/amagi.js +2 -0
- package/lib/karin/types/config/app.js +2 -0
- package/lib/karin/types/config/bilibili.js +2 -0
- package/lib/karin/types/config/cookies.js +2 -0
- package/lib/karin/types/config/douyin.js +2 -0
- package/lib/karin/types/config/index.js +2 -0
- package/lib/karin/types/config/kuaishou.js +2 -0
- package/lib/karin/types/config/pushlist.js +2 -0
- package/lib/karin/types/config/request.js +2 -0
- package/lib/karin/types/config/upload.js +2 -0
- package/lib/karin/types/config/xiaohongshu.js +2 -0
- package/lib/karin/types/datatypes.js +2 -0
- package/lib/karin/types/index.js +18 -0
- package/lib/karin/types/netrorks/NetworksConfigType.js +2 -0
- package/lib/karin/types/netrorks/index.js +17 -0
- package/lib/karin/web.config.js +49 -0
- package/lib/ktr/registry.js +82 -0
- package/lib/ktr/template/_preview/components/PreviewInfoCard.js +10 -0
- package/lib/ktr/template/_preview/components/PreviewLayout.js +10 -0
- package/lib/ktr/template/_preview/components/PreviewVideoCard.js +8 -0
- package/lib/ktr/template/_preview/render.js +142 -0
- package/lib/ktr/template/_preview/types.js +2 -0
- package/lib/ktr/template/_preview/utils/status.js +31 -0
- package/lib/ktr/template/_preview/utils/time.js +12 -0
- package/lib/ktr/template/bilibili/bangumi/components/bangumi.js +127 -0
- package/lib/ktr/template/bilibili/bangumi/components/types.js +2 -0
- package/lib/ktr/template/bilibili/bangumi/index.js +12 -0
- package/lib/ktr/template/bilibili/comment/components/Comment.js +193 -0
- package/lib/ktr/template/bilibili/comment/components/types.js +2 -0
- package/lib/ktr/template/bilibili/comment/index.js +10 -0
- package/lib/ktr/template/bilibili/components/Icons.js +66 -0
- package/lib/ktr/template/bilibili/components/shared.js +93 -0
- package/lib/ktr/template/bilibili/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/DYNAMIC_TYPE_ARTICLE.js +66 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/index.js +10 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/DYNAMIC_TYPE_AV.js +87 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/index.js +10 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/DYNAMIC_TYPE_DRAW.js +93 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/index.js +10 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/DYNAMIC_TYPE_FORWARD.js +132 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/index.js +10 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/DYNAMIC_TYPE_LIVE_RCMD.js +84 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/index.js +10 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/DYNAMIC_TYPE_WORD.js +38 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/types.js +2 -0
- package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/index.js +10 -0
- package/lib/ktr/template/bilibili/dynamic/components/AdditionalCard.js +59 -0
- package/lib/ktr/template/bilibili/dynamic/components/CommonComponents.js +30 -0
- package/lib/ktr/template/bilibili/dynamic/types.js +2 -0
- package/lib/ktr/template/bilibili/qrcodeImg/components/qrcodeImg.js +48 -0
- package/lib/ktr/template/bilibili/qrcodeImg/components/types.js +3 -0
- package/lib/ktr/template/bilibili/qrcodeImg/index.js +10 -0
- package/lib/ktr/template/bilibili/userlist/components/UserList.js +53 -0
- package/lib/ktr/template/bilibili/userlist/components/types.js +3 -0
- package/lib/ktr/template/bilibili/userlist/index.js +13 -0
- package/lib/ktr/template/bilibili/videoInfo/components/types.js +2 -0
- package/lib/ktr/template/bilibili/videoInfo/components/videoInfo.js +62 -0
- package/lib/ktr/template/bilibili/videoInfo/index.js +10 -0
- package/lib/ktr/template/components/AmbientCover.js +36 -0
- package/lib/ktr/template/components/DefaultLayout.js +33 -0
- package/lib/ktr/template/components/GlowImage.js +276 -0
- package/lib/ktr/template/components/QRCodeWithAvatar.js +34 -0
- package/lib/ktr/template/components/RolldownLogo.js +6 -0
- package/lib/ktr/template/components/ViteLogo.js +6 -0
- package/lib/ktr/template/components/iconRegistry.js +173 -0
- package/lib/ktr/template/douyin/article-work/components/ArticleWork.js +99 -0
- package/lib/ktr/template/douyin/article-work/components/types.js +3 -0
- package/lib/ktr/template/douyin/article-work/index.js +10 -0
- package/lib/ktr/template/douyin/comment/components/Comment.js +189 -0
- package/lib/ktr/template/douyin/comment/components/types.js +2 -0
- package/lib/ktr/template/douyin/comment/index.js +10 -0
- package/lib/ktr/template/douyin/components/Icons.js +29 -0
- package/lib/ktr/template/douyin/components/types.js +3 -0
- package/lib/ktr/template/douyin/dynamic/components/Dynamic.js +90 -0
- package/lib/ktr/template/douyin/dynamic/components/types.js +3 -0
- package/lib/ktr/template/douyin/dynamic/index.js +10 -0
- package/lib/ktr/template/douyin/favorite-list/components/FavoriteList.js +30 -0
- package/lib/ktr/template/douyin/favorite-list/components/types.js +3 -0
- package/lib/ktr/template/douyin/favorite-list/index.js +10 -0
- package/lib/ktr/template/douyin/image-work/components/ImageWork.js +136 -0
- package/lib/ktr/template/douyin/image-work/components/types.js +2 -0
- package/lib/ktr/template/douyin/image-work/index.js +10 -0
- package/lib/ktr/template/douyin/live/components/Live.js +51 -0
- package/lib/ktr/template/douyin/live/components/types.js +3 -0
- package/lib/ktr/template/douyin/live/index.js +10 -0
- package/lib/ktr/template/douyin/musicinfo/components/MusicInfo.js +60 -0
- package/lib/ktr/template/douyin/musicinfo/components/types.js +3 -0
- package/lib/ktr/template/douyin/musicinfo/index.js +10 -0
- package/lib/ktr/template/douyin/qrcodeImg/components/qrcodeImg.js +39 -0
- package/lib/ktr/template/douyin/qrcodeImg/components/types.js +3 -0
- package/lib/ktr/template/douyin/qrcodeImg/index.js +9 -0
- package/lib/ktr/template/douyin/recommend-list/components/RecommendList.js +30 -0
- package/lib/ktr/template/douyin/recommend-list/components/types.js +3 -0
- package/lib/ktr/template/douyin/recommend-list/index.js +10 -0
- package/lib/ktr/template/douyin/user_profile/components/UserVideoList.js +54 -0
- package/lib/ktr/template/douyin/user_profile/components/types.js +3 -0
- package/lib/ktr/template/douyin/user_profile/index.js +9 -0
- package/lib/ktr/template/douyin/userlist/components/UserList.js +50 -0
- package/lib/ktr/template/douyin/userlist/components/types.js +3 -0
- package/lib/ktr/template/douyin/userlist/index.js +13 -0
- package/lib/ktr/template/douyin/video-work/components/VideoWork.js +127 -0
- package/lib/ktr/template/douyin/video-work/components/types.js +2 -0
- package/lib/ktr/template/douyin/video-work/index.js +10 -0
- package/lib/ktr/template/kuaishou/comment/components/Comment.js +88 -0
- package/lib/ktr/template/kuaishou/comment/components/types.js +2 -0
- package/lib/ktr/template/kuaishou/comment/index.js +10 -0
- package/lib/ktr/template/other/changelog/components/changelog.js +117 -0
- package/lib/ktr/template/other/changelog/components/types.js +3 -0
- package/lib/ktr/template/other/changelog/index.js +9 -0
- package/lib/ktr/template/other/handlerError/components/errorTitles.js +227 -0
- package/lib/ktr/template/other/handlerError/components/handlerError.js +392 -0
- package/lib/ktr/template/other/handlerError/components/stackHighlight.js +109 -0
- package/lib/ktr/template/other/handlerError/components/types.js +2 -0
- package/lib/ktr/template/other/handlerError/index.js +9 -0
- package/lib/ktr/template/other/help/components/Help.js +79 -0
- package/lib/ktr/template/other/help/components/types.js +3 -0
- package/lib/ktr/template/other/help/index.js +12 -0
- package/lib/ktr/template/other/live-photo-tip/components/LivePhotoTip.js +80 -0
- package/lib/ktr/template/other/live-photo-tip/components/types.js +3 -0
- package/lib/ktr/template/other/live-photo-tip/index.js +9 -0
- package/lib/ktr/template/other/qrlogin/components/qrlogin.js +58 -0
- package/lib/ktr/template/other/qrlogin/components/types.js +3 -0
- package/lib/ktr/template/other/qrlogin/index.js +9 -0
- package/lib/ktr/template/other/runtime/components/runtime.js +101 -0
- package/lib/ktr/template/other/runtime/components/types.js +3 -0
- package/lib/ktr/template/other/runtime/index.js +9 -0
- package/lib/ktr/template/other/version_warning/components/VersionWarning.js +38 -0
- package/lib/ktr/template/other/version_warning/components/types.js +3 -0
- package/lib/ktr/template/other/version_warning/index.js +9 -0
- package/lib/ktr/template/statistics/components/BoxplotGuide.js +17 -0
- package/lib/ktr/template/statistics/components/ChartCard.js +15 -0
- package/lib/ktr/template/statistics/components/KpiCard.js +14 -0
- package/lib/ktr/template/statistics/components/PlatformTable.js +20 -0
- package/lib/ktr/template/statistics/components/RankList.js +61 -0
- package/lib/ktr/template/statistics/components/SSRChart.js +22 -0
- package/lib/ktr/template/statistics/components/chartPresets.js +630 -0
- package/lib/ktr/template/statistics/components/chartTheme.js +226 -0
- package/lib/ktr/template/statistics/components/format.js +38 -0
- package/lib/ktr/template/statistics/components/types.js +9 -0
- package/lib/ktr/template/statistics/global/components/GlobalStatistics.js +246 -0
- package/lib/ktr/template/statistics/global/components/types.js +3 -0
- package/lib/ktr/template/statistics/global/index.js +9 -0
- package/lib/ktr/template/statistics/group/components/GroupStatistics.js +181 -0
- package/lib/ktr/template/statistics/group/components/types.js +3 -0
- package/lib/ktr/template/statistics/group/index.js +9 -0
- package/lib/ktr/template/types/ctx.js +2 -0
- package/lib/ktr/template/xiaohongshu/comment/components/Comment.js +99 -0
- package/lib/ktr/template/xiaohongshu/comment/components/types.js +3 -0
- package/lib/ktr/template/xiaohongshu/comment/index.js +9 -0
- package/lib/ktr/template/xiaohongshu/noteInfo/components/noteInfo.js +93 -0
- package/lib/ktr/template/xiaohongshu/noteInfo/components/types.js +2 -0
- package/lib/ktr/template/xiaohongshu/noteInfo/index.js +9 -0
- package/lib/ktr/utils/QRCodeAvatar.js +99 -0
- package/lib/ktr/utils/QRcode.js +56 -0
- package/lib/ktr/utils/avatarProxy.js +45 -0
- package/lib/ktr/utils/cn.js +11 -0
- package/lib/ktr/utils/theme.js +12 -0
- package/lib/qqFields.json +75 -0
- package/lib/qqOptions.js +44 -0
- package/lib/richtext/index.js +25 -0
- package/lib/richtext/parse/index.js +232 -0
- package/lib/richtext/react/cn.js +11 -0
- package/lib/richtext/react/index.js +221 -0
- package/lib/richtext/types.js +2 -0
- package/lib/schema.js +352 -0
- package/lib/webui.js +341 -0
- package/package.json +88 -0
- package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_BV.svg +1 -0
- package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_GOODS.svg +1 -0
- package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_LOTTERY.svg +1 -0
- package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_VIEW_PICTURE.svg +1 -0
- package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_VOTE.svg +1 -0
- package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_WEB.svg +1 -0
- package/resources/image/bilibili/bilibili-dark.png +0 -0
- package/resources/image/bilibili/bilibili-light.png +0 -0
- package/resources/image/bilibili/bilibili.png +0 -0
- package/resources/image/bilibili/id-dark.svg +1 -0
- package/resources/image/bilibili/id-light.svg +1 -0
- package/resources/image/bilibili/level/lv0.svg +1 -0
- package/resources/image/bilibili/level/lv1.svg +1 -0
- package/resources/image/bilibili/level/lv2.svg +1 -0
- package/resources/image/bilibili/level/lv3.svg +1 -0
- package/resources/image/bilibili/level/lv4.svg +1 -0
- package/resources/image/bilibili/level/lv5.svg +1 -0
- package/resources/image/bilibili/level/lv6.svg +1 -0
- package/resources/image/bilibili/level/lv7.svg +1 -0
- package/resources/image/bilibili/like-dark.svg +6 -0
- package/resources/image/bilibili/like-light.svg +6 -0
- package/resources/image/bilibili/play.svg +28 -0
- package/resources/image/bilibili/res-local1.png +0 -0
- package/resources/image/bilibili/up_pb.svg +1 -0
- package/resources/image/bilibili//347/233/264/346/222/255/344/270/255.png +0 -0
- package/resources/image/douyin/douyin.png +0 -0
- package/resources/image/douyin/dylogo-dark.svg +23 -0
- package/resources/image/douyin/dylogo-light.svg +38 -0
- package/resources/image/douyin/search-dark.svg +1 -0
- package/resources/image/douyin/search-light.svg +3 -0
- package/resources/image/douyin//346/212/226/351/237/263-/347/233/264/346/222/255/344/270/255.png +0 -0
- package/resources/image/frame-logo.png +0 -0
- package/resources/image/kuaishou/logo.png +0 -0
- package/resources/image/other/handlerError/Milky.png +0 -0
- package/resources/image/other/handlerError/chronocat.svg +43 -0
- package/resources/image/other/handlerError/conwechat.webp +0 -0
- package/resources/image/other/handlerError/gocq-http.webp +0 -0
- package/resources/image/other/handlerError/lagrange.webp +0 -0
- package/resources/image/other/handlerError/llonebot.webp +0 -0
- package/resources/image/other/handlerError/napcat.webp +0 -0
- package/resources/image/other/handlerError/onebot.png +0 -0
- package/resources/image/other/handlerError/satori.png +0 -0
- package/resources/image/xiaohongshu/logo.png +0 -0
- package/resources/template/style.css +23708 -0
- package/resources/vite.svg +130 -0
- package/scripts/build.mjs +129 -0
- package/scripts/check-schema.cjs +55 -0
- package/scripts/deploy.mjs +78 -0
- package/scripts/make-demo-placeholders.mjs +112 -0
- package/scripts/patch-deps.mjs +46 -0
- package/scripts/patch-webui.mjs +112 -0
- package/scripts/smoke-commands.cjs +39 -0
- package/scripts/smoke-config.cjs +122 -0
- package/scripts/smoke-douyin.cjs +170 -0
- package/scripts/smoke-koishi-commands.cjs +210 -0
- package/scripts/smoke-login.cjs +110 -0
- package/scripts/smoke-parse.cjs +54 -0
- package/scripts/smoke-push-mock.cjs +114 -0
- package/scripts/smoke-push.cjs +104 -0
- package/scripts/smoke-qq.cjs +147 -0
- package/scripts/smoke-qqcard.cjs +108 -0
- package/scripts/smoke-qqpanel.cjs +205 -0
- package/scripts/smoke-render.cjs +106 -0
- package/scripts/smoke-server.cjs +94 -0
- package/src/compat/axios.ts +5 -0
- package/src/compat/cron.ts +86 -0
- package/src/compat/esm.ts +13 -0
- package/src/compat/express.ts +5 -0
- package/src/compat/heroui.tsx +53 -0
- package/src/compat/lodash.ts +32 -0
- package/src/compat/logger.ts +121 -0
- package/src/compat/node-karin.ts +1064 -0
- package/src/compat/phosphor-icons.tsx +25 -0
- package/src/compat/qqcard.ts +251 -0
- package/src/compat/root.ts +44 -0
- package/src/compat/runtime.ts +120 -0
- package/src/compat/segment.ts +161 -0
- package/src/compat/sqlite3.ts +123 -0
- package/src/compat/start.ts +3 -0
- package/src/compat/syncConfig.ts +64 -0
- package/src/compat/template-react.ts +54 -0
- package/src/compat/text.ts +58 -0
- package/src/compat/yaml.ts +6 -0
- package/src/configBridge.ts +94 -0
- package/src/index.ts +769 -0
- package/src/karin/app.ts +1 -0
- package/src/karin/apps/__ready.ts +14 -0
- package/src/karin/apps/admin.ts +127 -0
- package/src/karin/apps/help.ts +228 -0
- package/src/karin/apps/push.ts +327 -0
- package/src/karin/apps/qrlogin.ts +160 -0
- package/src/karin/apps/statistics.ts +203 -0
- package/src/karin/apps/testPush.ts +226 -0
- package/src/karin/apps/tools.ts +510 -0
- package/src/karin/apps/update.ts +239 -0
- package/src/karin/index.ts +9 -0
- package/src/karin/module/db/bilibili.ts +1089 -0
- package/src/karin/module/db/douyin.ts +1218 -0
- package/src/karin/module/db/index.ts +159 -0
- package/src/karin/module/db/migration.ts +249 -0
- package/src/karin/module/db/statistics.ts +817 -0
- package/src/karin/module/index.ts +37 -0
- package/src/karin/module/server/constants/routes.ts +53 -0
- package/src/karin/module/server/controllers/bots.ts +289 -0
- package/src/karin/module/server/controllers/config.ts +85 -0
- package/src/karin/module/server/controllers/video.ts +212 -0
- package/src/karin/module/server/index.ts +11 -0
- package/src/karin/module/server/middlewares/auth.ts +147 -0
- package/src/karin/module/server/routes/amagi.ts +57 -0
- package/src/karin/module/server/routes/api.ts +42 -0
- package/src/karin/module/server/routes/index.ts +68 -0
- package/src/karin/module/server/routes/ssr.ts +14 -0
- package/src/karin/module/server/routes/static.ts +43 -0
- package/src/karin/module/utils/Base.ts +497 -0
- package/src/karin/module/utils/CardParser.ts +503 -0
- package/src/karin/module/utils/Common.ts +448 -0
- package/src/karin/module/utils/Config.ts +443 -0
- package/src/karin/module/utils/EmojiReaction.ts +268 -0
- package/src/karin/module/utils/ErrorHandler/handler.ts +124 -0
- package/src/karin/module/utils/ErrorHandler/index.ts +48 -0
- package/src/karin/module/utils/ErrorHandler/render.ts +180 -0
- package/src/karin/module/utils/ErrorHandler/sender.ts +192 -0
- package/src/karin/module/utils/ErrorHandler/strategy.ts +41 -0
- package/src/karin/module/utils/ErrorHandler/types.ts +122 -0
- package/src/karin/module/utils/ErrorHandler/utils.ts +108 -0
- package/src/karin/module/utils/FFmpeg.ts +357 -0
- package/src/karin/module/utils/ImageHelper.ts +90 -0
- package/src/karin/module/utils/ImageSlice.ts +383 -0
- package/src/karin/module/utils/MotionPhoto.ts +297 -0
- package/src/karin/module/utils/Network/Downloader.ts +481 -0
- package/src/karin/module/utils/Network/ImageDownloader.ts +291 -0
- package/src/karin/module/utils/Network/Network.ts +344 -0
- package/src/karin/module/utils/Network/ThrottleStream.ts +76 -0
- package/src/karin/module/utils/Network/constants.ts +135 -0
- package/src/karin/module/utils/Network/helpers.ts +183 -0
- package/src/karin/module/utils/Network/index.ts +28 -0
- package/src/karin/module/utils/Network/types.ts +46 -0
- package/src/karin/module/utils/Networks.ts +20 -0
- package/src/karin/module/utils/ParseLock.ts +33 -0
- package/src/karin/module/utils/ParseOverride.ts +128 -0
- package/src/karin/module/utils/QqCardResolve.ts +116 -0
- package/src/karin/module/utils/QqPanel.ts +880 -0
- package/src/karin/module/utils/Render/coverTheme.ts +294 -0
- package/src/karin/module/utils/Render/index.ts +574 -0
- package/src/karin/module/utils/amagiClient.ts +368 -0
- package/src/karin/module/utils/avatar.ts +220 -0
- package/src/karin/module/utils/bot.ts +115 -0
- package/src/karin/module/utils/build-metadata.ts +60 -0
- package/src/karin/module/utils/changelog.ts +175 -0
- package/src/karin/module/utils/index.ts +10 -0
- package/src/karin/module/utils/loginSession.ts +291 -0
- package/src/karin/module/utils/parseTip.ts +59 -0
- package/src/karin/module/utils/runtime-report.ts +142 -0
- package/src/karin/module/utils/semver.ts +60 -0
- package/src/karin/module/utils/statisticsAggregate.ts +507 -0
- package/src/karin/platform/bilibili/bilibili.ts +2004 -0
- package/src/karin/platform/bilibili/comments.ts +397 -0
- package/src/karin/platform/bilibili/danmaku.ts +653 -0
- package/src/karin/platform/bilibili/dynamic-text.ts +926 -0
- package/src/karin/platform/bilibili/genParams.ts +30 -0
- package/src/karin/platform/bilibili/getID.ts +163 -0
- package/src/karin/platform/bilibili/index.ts +8 -0
- package/src/karin/platform/bilibili/live-status.ts +36 -0
- package/src/karin/platform/bilibili/login.ts +130 -0
- package/src/karin/platform/bilibili/push.ts +1615 -0
- package/src/karin/platform/bilibili/riskControl.ts +114 -0
- package/src/karin/platform/bilibili/search.ts +305 -0
- package/src/karin/platform/common/danmaku-types.ts +18 -0
- package/src/karin/platform/douyin/comments.ts +404 -0
- package/src/karin/platform/douyin/danmaku.ts +1187 -0
- package/src/karin/platform/douyin/douyin.ts +1244 -0
- package/src/karin/platform/douyin/getID.ts +101 -0
- package/src/karin/platform/douyin/index.ts +8 -0
- package/src/karin/platform/douyin/login.ts +251 -0
- package/src/karin/platform/douyin/push/favorite.ts +97 -0
- package/src/karin/platform/douyin/push/index.ts +5 -0
- package/src/karin/platform/douyin/push/live.ts +86 -0
- package/src/karin/platform/douyin/push/post.ts +75 -0
- package/src/karin/platform/douyin/push/recommend.ts +96 -0
- package/src/karin/platform/douyin/push/render.ts +768 -0
- package/src/karin/platform/douyin/push/types.ts +41 -0
- package/src/karin/platform/douyin/push.ts +1241 -0
- package/src/karin/platform/douyin/types.ts +151 -0
- package/src/karin/platform/douyin/videoQuality.ts +234 -0
- package/src/karin/platform/douyin/workType.ts +238 -0
- package/src/karin/platform/index.ts +4 -0
- package/src/karin/platform/kuaishou/comments.ts +114 -0
- package/src/karin/platform/kuaishou/getID.ts +48 -0
- package/src/karin/platform/kuaishou/getdata.ts +78 -0
- package/src/karin/platform/kuaishou/index.ts +4 -0
- package/src/karin/platform/kuaishou/kuaishou.ts +130 -0
- package/src/karin/platform/xiaohongshu/comments.ts +227 -0
- package/src/karin/platform/xiaohongshu/getID.ts +127 -0
- package/src/karin/platform/xiaohongshu/index.ts +2 -0
- package/src/karin/platform/xiaohongshu/xiaohongshu.ts +733 -0
- package/src/karin/root.ts +78 -0
- package/src/karin/setup.ts +77 -0
- package/src/karin/types/config/amagi.ts +46 -0
- package/src/karin/types/config/app.ts +129 -0
- package/src/karin/types/config/bilibili.ts +121 -0
- package/src/karin/types/config/cookies.ts +11 -0
- package/src/karin/types/config/douyin.ts +93 -0
- package/src/karin/types/config/index.ts +41 -0
- package/src/karin/types/config/kuaishou.ts +10 -0
- package/src/karin/types/config/pushlist.ts +55 -0
- package/src/karin/types/config/request.ts +27 -0
- package/src/karin/types/config/upload.ts +49 -0
- package/src/karin/types/config/xiaohongshu.ts +24 -0
- package/src/karin/types/datatypes.ts +42 -0
- package/src/karin/types/index.ts +4 -0
- package/src/karin/types/netrorks/NetworksConfigType.ts +61 -0
- package/src/karin/types/netrorks/index.ts +1 -0
- package/src/karin/web.config.ts +48 -0
- package/src/ktr/registry.ts +55 -0
- package/src/ktr/template/_preview/components/PreviewInfoCard.tsx +33 -0
- package/src/ktr/template/_preview/components/PreviewLayout.tsx +65 -0
- package/src/ktr/template/_preview/components/PreviewVideoCard.tsx +22 -0
- package/src/ktr/template/_preview/render.ts +158 -0
- package/src/ktr/template/_preview/types.ts +15 -0
- package/src/ktr/template/_preview/utils/status.ts +29 -0
- package/src/ktr/template/_preview/utils/time.ts +8 -0
- package/src/ktr/template/bilibili/bangumi/components/bangumi.tsx +462 -0
- package/src/ktr/template/bilibili/bangumi/components/types.ts +36 -0
- package/src/ktr/template/bilibili/bangumi/index.tsx +9 -0
- package/src/ktr/template/bilibili/comment/components/Comment.tsx +657 -0
- package/src/ktr/template/bilibili/comment/components/types.ts +26 -0
- package/src/ktr/template/bilibili/comment/index.tsx +11 -0
- package/src/ktr/template/bilibili/components/Icons.tsx +209 -0
- package/src/ktr/template/bilibili/components/shared.tsx +162 -0
- package/src/ktr/template/bilibili/components/types.ts +388 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/DYNAMIC_TYPE_ARTICLE.tsx +251 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/types.ts +67 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/index.tsx +12 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/DYNAMIC_TYPE_AV.tsx +238 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/types.ts +39 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/index.tsx +12 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/DYNAMIC_TYPE_DRAW.tsx +205 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/types.ts +20 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/index.tsx +11 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/DYNAMIC_TYPE_FORWARD.tsx +348 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/types.ts +16 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/index.tsx +12 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/DYNAMIC_TYPE_LIVE_RCMD.tsx +264 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/types.ts +32 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/index.tsx +12 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/DYNAMIC_TYPE_WORD.tsx +68 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/types.ts +14 -0
- package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/index.tsx +12 -0
- package/src/ktr/template/bilibili/dynamic/components/AdditionalCard.tsx +165 -0
- package/src/ktr/template/bilibili/dynamic/components/CommonComponents.tsx +142 -0
- package/src/ktr/template/bilibili/dynamic/types.ts +382 -0
- package/src/ktr/template/bilibili/qrcodeImg/components/qrcodeImg.tsx +179 -0
- package/src/ktr/template/bilibili/qrcodeImg/components/types.ts +9 -0
- package/src/ktr/template/bilibili/qrcodeImg/index.tsx +12 -0
- package/src/ktr/template/bilibili/userlist/components/UserList.tsx +246 -0
- package/src/ktr/template/bilibili/userlist/components/types.ts +35 -0
- package/src/ktr/template/bilibili/userlist/index.tsx +11 -0
- package/src/ktr/template/bilibili/videoInfo/components/types.ts +28 -0
- package/src/ktr/template/bilibili/videoInfo/components/videoInfo.tsx +217 -0
- package/src/ktr/template/bilibili/videoInfo/index.tsx +12 -0
- package/src/ktr/template/components/AmbientCover.tsx +50 -0
- package/src/ktr/template/components/DefaultLayout.tsx +166 -0
- package/src/ktr/template/components/GlowImage.tsx +572 -0
- package/src/ktr/template/components/QRCodeWithAvatar.tsx +52 -0
- package/src/ktr/template/components/RolldownLogo.tsx +110 -0
- package/src/ktr/template/components/ViteLogo.tsx +134 -0
- package/src/ktr/template/components/iconRegistry.ts +294 -0
- package/src/ktr/template/douyin/article-work/components/ArticleWork.tsx +301 -0
- package/src/ktr/template/douyin/article-work/components/types.ts +53 -0
- package/src/ktr/template/douyin/article-work/index.tsx +12 -0
- package/src/ktr/template/douyin/comment/components/Comment.tsx +612 -0
- package/src/ktr/template/douyin/comment/components/types.ts +85 -0
- package/src/ktr/template/douyin/comment/index.tsx +11 -0
- package/src/ktr/template/douyin/components/Icons.tsx +75 -0
- package/src/ktr/template/douyin/components/types.ts +83 -0
- package/src/ktr/template/douyin/dynamic/components/Dynamic.tsx +283 -0
- package/src/ktr/template/douyin/dynamic/components/types.ts +46 -0
- package/src/ktr/template/douyin/dynamic/index.tsx +11 -0
- package/src/ktr/template/douyin/favorite-list/components/FavoriteList.tsx +201 -0
- package/src/ktr/template/douyin/favorite-list/components/types.ts +38 -0
- package/src/ktr/template/douyin/favorite-list/index.tsx +12 -0
- package/src/ktr/template/douyin/image-work/components/ImageWork.tsx +437 -0
- package/src/ktr/template/douyin/image-work/components/types.ts +78 -0
- package/src/ktr/template/douyin/image-work/index.tsx +11 -0
- package/src/ktr/template/douyin/live/components/Live.tsx +229 -0
- package/src/ktr/template/douyin/live/components/types.ts +47 -0
- package/src/ktr/template/douyin/live/index.tsx +11 -0
- package/src/ktr/template/douyin/musicinfo/components/MusicInfo.tsx +187 -0
- package/src/ktr/template/douyin/musicinfo/components/types.ts +31 -0
- package/src/ktr/template/douyin/musicinfo/index.tsx +11 -0
- package/src/ktr/template/douyin/qrcodeImg/components/qrcodeImg.tsx +170 -0
- package/src/ktr/template/douyin/qrcodeImg/components/types.ts +9 -0
- package/src/ktr/template/douyin/qrcodeImg/index.tsx +9 -0
- package/src/ktr/template/douyin/recommend-list/components/RecommendList.tsx +201 -0
- package/src/ktr/template/douyin/recommend-list/components/types.ts +37 -0
- package/src/ktr/template/douyin/recommend-list/index.tsx +12 -0
- package/src/ktr/template/douyin/user_profile/components/UserVideoList.tsx +225 -0
- package/src/ktr/template/douyin/user_profile/components/types.ts +71 -0
- package/src/ktr/template/douyin/user_profile/index.tsx +9 -0
- package/src/ktr/template/douyin/userlist/components/UserList.tsx +229 -0
- package/src/ktr/template/douyin/userlist/components/types.ts +35 -0
- package/src/ktr/template/douyin/userlist/index.tsx +11 -0
- package/src/ktr/template/douyin/video-work/components/VideoWork.tsx +398 -0
- package/src/ktr/template/douyin/video-work/components/types.ts +80 -0
- package/src/ktr/template/douyin/video-work/index.tsx +11 -0
- package/src/ktr/template/font.css +6071 -0
- package/src/ktr/template/global.d.ts +69 -0
- package/src/ktr/template/kuaishou/comment/components/Comment.tsx +207 -0
- package/src/ktr/template/kuaishou/comment/components/types.ts +47 -0
- package/src/ktr/template/kuaishou/comment/index.tsx +11 -0
- package/src/ktr/template/other/changelog/components/changelog.tsx +411 -0
- package/src/ktr/template/other/changelog/components/types.ts +21 -0
- package/src/ktr/template/other/changelog/index.tsx +9 -0
- package/src/ktr/template/other/handlerError/components/errorTitles.ts +224 -0
- package/src/ktr/template/other/handlerError/components/handlerError.tsx +1076 -0
- package/src/ktr/template/other/handlerError/components/stackHighlight.ts +127 -0
- package/src/ktr/template/other/handlerError/components/types.ts +124 -0
- package/src/ktr/template/other/handlerError/index.tsx +9 -0
- package/src/ktr/template/other/help/components/Help.tsx +249 -0
- package/src/ktr/template/other/help/components/types.ts +47 -0
- package/src/ktr/template/other/help/index.tsx +9 -0
- package/src/ktr/template/other/live-photo-tip/components/LivePhotoTip.tsx +295 -0
- package/src/ktr/template/other/live-photo-tip/components/types.ts +8 -0
- package/src/ktr/template/other/live-photo-tip/index.tsx +9 -0
- package/src/ktr/template/other/qrlogin/components/qrlogin.tsx +295 -0
- package/src/ktr/template/other/qrlogin/components/types.ts +11 -0
- package/src/ktr/template/other/qrlogin/index.tsx +9 -0
- package/src/ktr/template/other/runtime/components/runtime.tsx +347 -0
- package/src/ktr/template/other/runtime/components/types.ts +75 -0
- package/src/ktr/template/other/runtime/index.tsx +9 -0
- package/src/ktr/template/other/version_warning/components/VersionWarning.tsx +392 -0
- package/src/ktr/template/other/version_warning/components/types.ts +8 -0
- package/src/ktr/template/other/version_warning/index.tsx +9 -0
- package/src/ktr/template/statistics/components/BoxplotGuide.tsx +57 -0
- package/src/ktr/template/statistics/components/ChartCard.tsx +40 -0
- package/src/ktr/template/statistics/components/KpiCard.tsx +39 -0
- package/src/ktr/template/statistics/components/PlatformTable.tsx +83 -0
- package/src/ktr/template/statistics/components/RankList.tsx +127 -0
- package/src/ktr/template/statistics/components/SSRChart.tsx +41 -0
- package/src/ktr/template/statistics/components/chartPresets.ts +700 -0
- package/src/ktr/template/statistics/components/chartTheme.ts +234 -0
- package/src/ktr/template/statistics/components/format.ts +33 -0
- package/src/ktr/template/statistics/components/types.ts +92 -0
- package/src/ktr/template/statistics/global/components/GlobalStatistics.tsx +597 -0
- package/src/ktr/template/statistics/global/components/types.ts +138 -0
- package/src/ktr/template/statistics/global/index.tsx +9 -0
- package/src/ktr/template/statistics/group/components/GroupStatistics.tsx +468 -0
- package/src/ktr/template/statistics/group/components/types.ts +113 -0
- package/src/ktr/template/statistics/group/index.tsx +9 -0
- package/src/ktr/template/style.css +65 -0
- package/src/ktr/template/types/ctx.ts +42 -0
- package/src/ktr/template/xiaohongshu/comment/components/Comment.tsx +264 -0
- package/src/ktr/template/xiaohongshu/comment/components/types.ts +119 -0
- package/src/ktr/template/xiaohongshu/comment/index.tsx +9 -0
- package/src/ktr/template/xiaohongshu/noteInfo/components/noteInfo.tsx +271 -0
- package/src/ktr/template/xiaohongshu/noteInfo/components/types.ts +66 -0
- package/src/ktr/template/xiaohongshu/noteInfo/index.tsx +9 -0
- package/src/ktr/utils/QRCodeAvatar.ts +102 -0
- package/src/ktr/utils/QRcode.ts +65 -0
- package/src/ktr/utils/avatarProxy.ts +42 -0
- package/src/ktr/utils/cn.ts +6 -0
- package/src/ktr/utils/theme.ts +10 -0
- package/src/qqFields.json +75 -0
- package/src/qqOptions.ts +64 -0
- package/src/richtext/index.ts +9 -0
- package/src/richtext/parse/index.ts +271 -0
- package/src/richtext/react/cn.ts +6 -0
- package/src/richtext/react/index.tsx +582 -0
- package/src/richtext/types.ts +320 -0
- package/src/schema.ts +372 -0
- package/src/webui.ts +352 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.buildMarkdownImageMessage = exports.toMarkdownImage = exports.sendParseTip = exports.DANMAKU_SUPPORTED = void 0;
|
|
40
|
+
exports.rememberPanelRequest = rememberPanelRequest;
|
|
41
|
+
exports.resolvePanelQualitySize = resolvePanelQualitySize;
|
|
42
|
+
exports.resolvePanelToken = resolvePanelToken;
|
|
43
|
+
exports.isQqPlatform = isQqPlatform;
|
|
44
|
+
exports.cmdInput = cmdInput;
|
|
45
|
+
exports.rememberPanelMessage = rememberPanelMessage;
|
|
46
|
+
exports.recallLastPanel = recallLastPanel;
|
|
47
|
+
exports.replyReplacing = replyReplacing;
|
|
48
|
+
exports.buildDownloadTip = buildDownloadTip;
|
|
49
|
+
exports.sendBangumiPanelPage = sendBangumiPanelPage;
|
|
50
|
+
exports.sendBangumiPanel = sendBangumiPanel;
|
|
51
|
+
exports.sendQqParsePanel = sendQqParsePanel;
|
|
52
|
+
/**
|
|
53
|
+
* QQ 平台的「解析交互面板」(Markdown + 原生按钮)。
|
|
54
|
+
*
|
|
55
|
+
* ## 为什么要面板
|
|
56
|
+
* QQ 侧解析的默认行为是「发链接 → 直接按配置的画质解析」:用户既改不了画质,
|
|
57
|
+
* 也换不了「要不要带弹幕」。而 QQ 官方 bot 支持 Markdown + 按钮,
|
|
58
|
+
* 于是这里在解析**之前**插一步:把可选内容和画质列出来,点一下按钮再真正解析。
|
|
59
|
+
*
|
|
60
|
+
* ## 体积限制
|
|
61
|
+
* QQ 富媒体上传有硬限制:视频 mp4 软限制 30MB、硬限制 200MB,
|
|
62
|
+
* 超过软限制会**降级成文件**发送,超过硬限制直接报错 850031。
|
|
63
|
+
* 所以超过 \`qqFileLimitMB\`(默认 200)的画质**不生成按钮** —— 点了也发不出去;
|
|
64
|
+
* 介于软/硬限制之间的档位会带一个 📄 标记,提示「会以文件形式发送」。
|
|
65
|
+
* @see https://bot.q.qq.com/wiki/develop/api-v2/autogen/api/v2_users_user_openid_files.post.html
|
|
66
|
+
*
|
|
67
|
+
* ## 无状态
|
|
68
|
+
* 面板本身不记录任何会话状态:当前选择(纯视频 / 带弹幕)编码在按钮的 \`action.data\` 里,
|
|
69
|
+
* 点「+弹幕」就是把带 \`--panel=1\` 的同一条命令再发一次,由插件重新渲染面板。
|
|
70
|
+
* 好处是重启、多实例、按钮点两次都不会串状态。
|
|
71
|
+
*/
|
|
72
|
+
const node_karin_1 = require("../../../compat/node-karin.js");
|
|
73
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
74
|
+
const runtime_1 = require("../../../compat/runtime");
|
|
75
|
+
const ParseOverride_1 = require("./ParseOverride");
|
|
76
|
+
const Config_1 = require("./Config");
|
|
77
|
+
const videoQuality_1 = require("../../platform/douyin/videoQuality");
|
|
78
|
+
const Render_1 = require("../../module/utils/Render");
|
|
79
|
+
const danmaku_1 = require("../../platform/bilibili/danmaku");
|
|
80
|
+
// 头像框 / 昵称颜色要从 UP 主页接口拿,和解析结果保持一致
|
|
81
|
+
const dynamic_text_1 = require("../../platform/bilibili/dynamic-text");
|
|
82
|
+
// 抖音卡片复用推送那套构建器(保真度最高)
|
|
83
|
+
const render_1 = require("../../platform/douyin/push/render");
|
|
84
|
+
const amagiClient_1 = require("./amagiClient");
|
|
85
|
+
/**
|
|
86
|
+
* B站画质标识 → 文案。
|
|
87
|
+
* 只留分辨率本身(「高清 / 清晰 / 流畅 / 超清」这类形容词按需求去掉了),
|
|
88
|
+
* 60 帧、HDR、杜比这些是画质差异本身,保留。
|
|
89
|
+
*/
|
|
90
|
+
const BILI_QN_LABEL = {
|
|
91
|
+
6: '240P',
|
|
92
|
+
16: '360P',
|
|
93
|
+
32: '480P',
|
|
94
|
+
64: '720P',
|
|
95
|
+
74: '720P60',
|
|
96
|
+
80: '1080P',
|
|
97
|
+
112: '1080P+',
|
|
98
|
+
116: '1080P60',
|
|
99
|
+
120: '4K',
|
|
100
|
+
125: 'HDR',
|
|
101
|
+
126: '杜比视界',
|
|
102
|
+
127: '8K'
|
|
103
|
+
};
|
|
104
|
+
/** 抖音档位 → 文案 */
|
|
105
|
+
const DY_LABEL = {
|
|
106
|
+
'4k': '4K',
|
|
107
|
+
'2k': '2K',
|
|
108
|
+
'1080p': '1080P',
|
|
109
|
+
'720p': '720P',
|
|
110
|
+
'540p': '540P'
|
|
111
|
+
};
|
|
112
|
+
/** QQ 富媒体「软限制」:视频超过 30MB 会降级成文件发送 */
|
|
113
|
+
const QQ_SOFT_LIMIT_MB = 30;
|
|
114
|
+
const pendingRequests = new Map();
|
|
115
|
+
const PENDING_TTL = 30 * 60 * 1000;
|
|
116
|
+
/**
|
|
117
|
+
* 记住一次面板请求并返回短令牌。
|
|
118
|
+
* 同一个作品复用同一个令牌,避免用户反复点面板把表撑爆。
|
|
119
|
+
* @param request 面板请求(含规范链接)
|
|
120
|
+
*/
|
|
121
|
+
function rememberPanelRequest(request, sizes) {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
for (const [token, item] of pendingRequests) {
|
|
124
|
+
if (Date.now() - item.at > PENDING_TTL) {
|
|
125
|
+
pendingRequests.delete(token);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (item.url === request.url) {
|
|
129
|
+
item.at = now;
|
|
130
|
+
if (sizes)
|
|
131
|
+
item.sizes = sizes;
|
|
132
|
+
return token;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const token = Math.random().toString(36).slice(2, 8);
|
|
136
|
+
pendingRequests.set(token, { ...request, at: now, sizes });
|
|
137
|
+
return token;
|
|
138
|
+
}
|
|
139
|
+
/** 取某一档画质的预估体积(MB);拿不到返回 0 */
|
|
140
|
+
function resolvePanelQualitySize(token, quality) {
|
|
141
|
+
if (!token || quality === undefined)
|
|
142
|
+
return 0;
|
|
143
|
+
const item = pendingRequests.get(token);
|
|
144
|
+
const size = item?.sizes?.[String(quality)];
|
|
145
|
+
return Number.isFinite(size) ? Number(size) : 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 令牌 → 规范链接。过期的令牌返回空串(此时按「没找到链接」处理即可)。
|
|
149
|
+
* @param token 令牌
|
|
150
|
+
*/
|
|
151
|
+
function resolvePanelToken(token) {
|
|
152
|
+
if (!token)
|
|
153
|
+
return '';
|
|
154
|
+
const item = pendingRequests.get(token);
|
|
155
|
+
if (!item)
|
|
156
|
+
return '';
|
|
157
|
+
if (Date.now() - item.at > PENDING_TTL) {
|
|
158
|
+
pendingRequests.delete(token);
|
|
159
|
+
return '';
|
|
160
|
+
}
|
|
161
|
+
item.at = Date.now();
|
|
162
|
+
return item.url;
|
|
163
|
+
}
|
|
164
|
+
/** 面板信息缓存(点「+弹幕」会重新渲染面板,没必要再打一次接口) */
|
|
165
|
+
const infoCache = new Map();
|
|
166
|
+
const INFO_TTL = 5 * 60 * 1000;
|
|
167
|
+
/**
|
|
168
|
+
* 当前部署能不能烧录弹幕。
|
|
169
|
+
*
|
|
170
|
+
* 上游的弹幕烧录全部经由 `node-karin` 的 `ffmpeg()` 封装。
|
|
171
|
+
* 兼容层现在给的是**真实现**(子进程跑 ffmpeg / ffprobe),所以这里按「机器上到底有没有 ffmpeg」
|
|
172
|
+
* 动态判断:有就正常提供弹幕选项,没有才降级提示「未接入 ffmpeg」。
|
|
173
|
+
*/
|
|
174
|
+
exports.DANMAKU_SUPPORTED = (0, node_karin_1.isFfmpegAvailable)();
|
|
175
|
+
/** 是否 QQ 平台(官方适配器 platform 为 qqguild / qq / qqbot) */
|
|
176
|
+
function isQqPlatform(e) {
|
|
177
|
+
const platform = String(e?.bot?.adapter?.name ?? e?.bot?.adapter?.protocol ?? '');
|
|
178
|
+
return /qqguild|qqbot|^qq$|official/i.test(platform);
|
|
179
|
+
}
|
|
180
|
+
/** 秒 → mm:ss / hh:mm:ss */
|
|
181
|
+
const formatDuration = (seconds) => {
|
|
182
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
183
|
+
return '';
|
|
184
|
+
const total = Math.round(seconds);
|
|
185
|
+
const hours = Math.floor(total / 3600);
|
|
186
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
187
|
+
const secs = total % 60;
|
|
188
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
189
|
+
return hours ? hours + ':' + pad(minutes) + ':' + pad(secs) : pad(minutes) + ':' + pad(secs);
|
|
190
|
+
};
|
|
191
|
+
/** 拉取 B站作品信息与可选画质 */
|
|
192
|
+
async function fetchBilibiliInfo(request) {
|
|
193
|
+
// 用模块级 fetcher(= AmagiBase 包装过的那一层):它会在失败信封上抛错,
|
|
194
|
+
// 拿到的就是 `{ success, data: { code, data: 稿件 } }` 这层形状,与平台解析代码一致
|
|
195
|
+
const infoResponse = await amagiClient_1.bilibiliFetcher.fetchVideoInfo({ bvid: request.id });
|
|
196
|
+
const detail = infoResponse?.data?.data;
|
|
197
|
+
if (!detail)
|
|
198
|
+
return null;
|
|
199
|
+
const page = request.page;
|
|
200
|
+
const cid = page ? (detail.pages?.[page - 1]?.cid ?? detail.cid) : detail.cid;
|
|
201
|
+
const duration = Number(page ? (detail.pages?.[page - 1]?.duration ?? detail.duration) : detail.duration) || 0;
|
|
202
|
+
/**
|
|
203
|
+
* 面板卡片要和「解析结果」那张一模一样 —— 解析结果带热门弹幕(就是卡片顶部那些飘过去的文字),
|
|
204
|
+
* 面板阶段也拉一份,用户选清晰度时看到的就是最终卡片。
|
|
205
|
+
*/
|
|
206
|
+
let hotDanmaku = [];
|
|
207
|
+
try {
|
|
208
|
+
const segments = Math.max(1, Math.ceil(duration / 360));
|
|
209
|
+
const lists = await Promise.all(Array.from({ length: segments }, (_, index) => amagiClient_1.bilibiliFetcher.fetchVideoDanmaku({ cid, segment_index: index + 1 })
|
|
210
|
+
.then((response) => response?.data?.data?.elems || [])
|
|
211
|
+
.catch(() => [])));
|
|
212
|
+
hotDanmaku = (0, danmaku_1.getHotDanmaku)(lists.flat(), 20);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
node_karin_1.logger.debug('[QQ面板] 拉取热门弹幕失败: ' + String(error));
|
|
216
|
+
}
|
|
217
|
+
const playResponse = await amagiClient_1.bilibiliFetcher.fetchVideoStreamUrl({ avid: detail.aid, cid });
|
|
218
|
+
const stream = playResponse?.data?.data;
|
|
219
|
+
const options = [];
|
|
220
|
+
if (Array.isArray(stream?.dash?.video) && stream.dash.video.length) {
|
|
221
|
+
// 音频是独立流,最终要么合流要么单独下载,体积算进去才不会低估
|
|
222
|
+
const audioBandwidth = Number(stream.dash.audio?.[0]?.bandwidth) || 0;
|
|
223
|
+
const seen = new Set();
|
|
224
|
+
for (const item of stream.dash.video) {
|
|
225
|
+
const qn = Number(item?.id);
|
|
226
|
+
if (!qn || seen.has(qn))
|
|
227
|
+
continue;
|
|
228
|
+
seen.add(qn);
|
|
229
|
+
// dash 只给带宽(bit/s),体积按 带宽 × 时长 估算 —— 精确定长要额外发 HEAD 请求,面板阶段不值得
|
|
230
|
+
const sizeMB = ((Number(item.bandwidth) || 0) + audioBandwidth) * duration / 8 / 1024 / 1024;
|
|
231
|
+
options.push({ id: String(qn), label: BILI_QN_LABEL[qn] ?? (qn + 'P'), sizeMB });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (Array.isArray(stream?.durl) && stream.durl.length) {
|
|
235
|
+
// 未登录时只有 durl(免登录 360P),体积字段是精确字节数
|
|
236
|
+
const qn = Number(stream.quality) || 16;
|
|
237
|
+
options.push({
|
|
238
|
+
id: String(qn),
|
|
239
|
+
label: BILI_QN_LABEL[qn] ?? '360P 流畅',
|
|
240
|
+
sizeMB: (Number(stream.durl[0]?.size) || 0) / 1024 / 1024
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
options.sort((left, right) => right.sizeMB - left.sizeMB);
|
|
244
|
+
return {
|
|
245
|
+
title: String(detail.title ?? ''),
|
|
246
|
+
author: String(detail.owner?.name ?? ''),
|
|
247
|
+
duration: formatDuration(duration),
|
|
248
|
+
options,
|
|
249
|
+
detail,
|
|
250
|
+
hotDanmaku
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/** 拉取抖音作品信息与可选画质 */
|
|
254
|
+
async function fetchDouyinInfo(request) {
|
|
255
|
+
const workResponse = await amagiClient_1.douyinFetcher.parseWork({ aweme_id: request.id });
|
|
256
|
+
const detail = workResponse?.data?.aweme_detail;
|
|
257
|
+
if (!detail)
|
|
258
|
+
return null;
|
|
259
|
+
const awemeType = Number(detail.aweme_type);
|
|
260
|
+
const isVideo = awemeType === 0 || awemeType === 55;
|
|
261
|
+
if (!isVideo)
|
|
262
|
+
return null;
|
|
263
|
+
const best = new Map();
|
|
264
|
+
for (const item of (detail.video?.bit_rate ?? [])) {
|
|
265
|
+
if (item?.format !== 'mp4')
|
|
266
|
+
continue;
|
|
267
|
+
const level = (0, videoQuality_1.getDouyinQualityLevel)(item);
|
|
268
|
+
if (!level)
|
|
269
|
+
continue;
|
|
270
|
+
const prev = best.get(level);
|
|
271
|
+
// 同档位优先 H.264(兼容性),其次体积更大的那路源
|
|
272
|
+
const better = !prev ||
|
|
273
|
+
(Number(prev.is_bytevc1) - Number(item.is_bytevc1)) > 0 ||
|
|
274
|
+
(Number(prev.is_bytevc1) === Number(item.is_bytevc1) && Number(item.play_addr?.data_size) > Number(prev.play_addr?.data_size));
|
|
275
|
+
if (better)
|
|
276
|
+
best.set(level, item);
|
|
277
|
+
}
|
|
278
|
+
const options = [...best.entries()].map(([level, item]) => ({
|
|
279
|
+
id: level,
|
|
280
|
+
label: DY_LABEL[level] ?? level,
|
|
281
|
+
sizeMB: (Number(item.play_addr?.data_size) || 0) / 1024 / 1024
|
|
282
|
+
}));
|
|
283
|
+
options.sort((left, right) => right.sizeMB - left.sizeMB);
|
|
284
|
+
return {
|
|
285
|
+
title: String(detail.desc ?? ''),
|
|
286
|
+
author: String(detail.author?.nickname ?? ''),
|
|
287
|
+
duration: formatDuration(Number(detail.video?.duration) / 1000),
|
|
288
|
+
options,
|
|
289
|
+
// 抖音卡片用:renderWorkImage 需要原始的 aweme_detail
|
|
290
|
+
detail
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/** 带缓存地拉取面板信息(失败也缓存,避免用户在坏链接上连点) */
|
|
294
|
+
async function fetchPanelInfo(request) {
|
|
295
|
+
const key = request.platform + ':' + request.id + ':' + (request.page ?? '');
|
|
296
|
+
const cached = infoCache.get(key);
|
|
297
|
+
if (cached && Date.now() - cached.at < INFO_TTL)
|
|
298
|
+
return cached.info;
|
|
299
|
+
try {
|
|
300
|
+
const info = request.platform === 'bilibili' ? await fetchBilibiliInfo(request) : await fetchDouyinInfo(request);
|
|
301
|
+
infoCache.set(key, { at: Date.now(), info });
|
|
302
|
+
if (infoCache.size > 64)
|
|
303
|
+
infoCache.delete(infoCache.keys().next().value);
|
|
304
|
+
return info;
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
node_karin_1.logger.debug('[QQ面板] 拉取作品信息失败: ' + String(error));
|
|
308
|
+
infoCache.set(key, { at: Date.now(), info: null });
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 生成一个 QQ **markdown 内联指令按钮**。
|
|
314
|
+
*
|
|
315
|
+
* 官方支持两种指令标签(见「文本交互」文档):
|
|
316
|
+
* - \`<qqbot-cmd-enter text="xxx" />\`:点击后**直接发送**,但**群聊不支持**;
|
|
317
|
+
* - \`<qqbot-cmd-input text="xxx" show="xxx" reference="false" />\`:点击后把文本插进输入框(群聊可用)。
|
|
318
|
+
* 群聊场景只有后者能用,所以面板用 \`cmd-input\`:\`text\` 是真正发出去的命令,
|
|
319
|
+
* \`show\` 是用户在消息里看到的文字,两者都要 urlencode。
|
|
320
|
+
*
|
|
321
|
+
* 用内联按钮而不是原生 keyboard 的另一个好处:不用再和适配器的 \`render_data\` 属性名较劲
|
|
322
|
+
* (Koishi 的 \`h()\` 会把属性 camelize,按钮文字曾经因此变成一整条指令)。
|
|
323
|
+
* @param command 点击后发送的指令文本
|
|
324
|
+
* @param show 展示文字(默认同 command)
|
|
325
|
+
*/
|
|
326
|
+
function cmdInput(command, show) {
|
|
327
|
+
const text = encodeURIComponent(command).replace(/'/g, '%27');
|
|
328
|
+
const label = encodeURIComponent(show ?? command).replace(/'/g, '%27');
|
|
329
|
+
return '<qqbot-cmd-input text="' + text + '" show="' + label + '" reference="false" />';
|
|
330
|
+
}
|
|
331
|
+
/* ------------------------------------------------------------------ *
|
|
332
|
+
* 上一条面板消息的撤回
|
|
333
|
+
*
|
|
334
|
+
* 面板是「一步步点」的:选集 → 选清晰度 → 下载。每点一步群里就多一条面板,
|
|
335
|
+
* 几步下来满屏都是面板。这里记住每个频道最后一条面板的消息 id,
|
|
336
|
+
* 下次再操作时把它撤回(配置 `recallPanel` 控制,默认开)。
|
|
337
|
+
* ------------------------------------------------------------------ */
|
|
338
|
+
const lastPanelMessages = new Map();
|
|
339
|
+
const LAST_PANEL_TTL = 30 * 60 * 1000;
|
|
340
|
+
/**
|
|
341
|
+
* 记住刚刚发出的面板消息(供下一次操作撤回)。
|
|
342
|
+
* 提示类消息(「检测到B站链接,开始解析」「收到请求,开始下载」「加载中…」)也用这个记,
|
|
343
|
+
* 这样它们同样会在下一步被撤掉,群里不会堆一串提示。
|
|
344
|
+
*/
|
|
345
|
+
function rememberPanelMessage(e, messageId) {
|
|
346
|
+
if (!messageId)
|
|
347
|
+
return;
|
|
348
|
+
try {
|
|
349
|
+
const key = String(e.contact?.peer ?? e.channelId ?? '');
|
|
350
|
+
if (!key)
|
|
351
|
+
return;
|
|
352
|
+
lastPanelMessages.set(key, { id: String(messageId), at: Date.now() });
|
|
353
|
+
if (lastPanelMessages.size > 128)
|
|
354
|
+
lastPanelMessages.delete(lastPanelMessages.keys().next().value);
|
|
355
|
+
}
|
|
356
|
+
catch { /* 记不住也不影响功能 */ }
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 撤回这个频道上一条面板消息(配置关掉时什么都不做)。
|
|
360
|
+
* 发送新面板之前、以及用户点完画质开始下载时都调一次。
|
|
361
|
+
*/
|
|
362
|
+
async function recallLastPanel(e) {
|
|
363
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
364
|
+
if (!runtime || runtime.config?.recallPanel === false)
|
|
365
|
+
return;
|
|
366
|
+
try {
|
|
367
|
+
const key = String(e.contact?.peer ?? e.channelId ?? '');
|
|
368
|
+
const entry = key ? lastPanelMessages.get(key) : undefined;
|
|
369
|
+
if (!entry)
|
|
370
|
+
return;
|
|
371
|
+
lastPanelMessages.delete(key);
|
|
372
|
+
if (Date.now() - entry.at > LAST_PANEL_TTL)
|
|
373
|
+
return;
|
|
374
|
+
// 撤回必须带上频道号:QQ 适配器的 deleteMessage 会对 channelId 调 startsWith,
|
|
375
|
+
// 传 undefined 会直接抛 TypeError(这正是一直撤不掉的原因)
|
|
376
|
+
await e.bot?.recallMsg?.(entry.id, key);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
node_karin_1.logger.debug('[QQ面板] 撤回上一条面板失败: ' + String(error));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* 面板渲染比较慢(大卡片要十几秒),先回一句「加载中…」再渲染。
|
|
384
|
+
* 返回这条提示的消息 id,渲染完由调用方撤回。
|
|
385
|
+
*/
|
|
386
|
+
async function showLoadingTip(e) {
|
|
387
|
+
try {
|
|
388
|
+
await recallLastPanel(e);
|
|
389
|
+
const tip = await e.reply('加载中…');
|
|
390
|
+
const id = tip?.messageId;
|
|
391
|
+
rememberPanelMessage(e, id);
|
|
392
|
+
return id;
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
node_karin_1.logger.debug('[QQ面板] 加载中提示失败: ' + String(error));
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/** 渲染完成后:撤掉「加载中…」,把新面板发出去并记下来 */
|
|
400
|
+
async function replaceLoadingTip(e, loadingId, content) {
|
|
401
|
+
if (loadingId) {
|
|
402
|
+
try {
|
|
403
|
+
await e.bot?.recallMsg?.(loadingId, String(e.contact?.peer ?? e.channelId ?? ''));
|
|
404
|
+
const key = String(e.contact?.peer ?? e.channelId ?? '');
|
|
405
|
+
const tracked = key ? lastPanelMessages.get(key) : undefined;
|
|
406
|
+
if (tracked?.id === loadingId)
|
|
407
|
+
lastPanelMessages.delete(key);
|
|
408
|
+
}
|
|
409
|
+
catch { /* 撤不掉就留着 */ }
|
|
410
|
+
}
|
|
411
|
+
const sent = await e.reply(content);
|
|
412
|
+
rememberPanelMessage(e, sent?.messageId);
|
|
413
|
+
return sent?.messageId;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* 解析流程的统一出口:**发新消息前先撤掉上一条**,再把新消息记下来。
|
|
417
|
+
*
|
|
418
|
+
* 从「检测到链接」→ 面板 → 开始下载 → 发送中 → 最终视频,全程群里只保留最新的一条,
|
|
419
|
+
* 不会越堆越多。配置 `recallPanel` 关掉时只发不撤。
|
|
420
|
+
*/
|
|
421
|
+
async function replyReplacing(e, content) {
|
|
422
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
423
|
+
try {
|
|
424
|
+
if (runtime?.config?.recallPanel !== false)
|
|
425
|
+
await recallLastPanel(e);
|
|
426
|
+
}
|
|
427
|
+
catch { /* 撤不掉就继续发 */ }
|
|
428
|
+
const sent = await e.reply(content);
|
|
429
|
+
rememberPanelMessage(e, sent?.messageId);
|
|
430
|
+
return sent;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* 「收到请求,开始下载」这条消息 + 一个查进度的按钮。
|
|
434
|
+
*
|
|
435
|
+
* 按钮里带上本次任务的特征串(B站是 bvid),点它就只查**这一条**的进度,
|
|
436
|
+
* 多个下载同时在跑时不会串味(下载文件名里本来就带 bvid)。
|
|
437
|
+
*/
|
|
438
|
+
function buildDownloadTip(taskId, text = '收到请求,开始下载') {
|
|
439
|
+
// 体积/文件形式的提示统一放到真正发送时的「发送中…」里(见 Base.ts),这里只给进度按钮
|
|
440
|
+
const command = (0, runtime_1.commandInvocation)('下载进度') + (taskId ? ' ' + taskId : '');
|
|
441
|
+
return node_karin_1.segment.markdown(text + '\n' + cmdInput(command, '查询下载进度'));
|
|
442
|
+
}
|
|
443
|
+
/** 画质按钮文案:标签 + 体积,超过软限制用文字标注「文件」(不用 emoji) */
|
|
444
|
+
const qualityLabel = (option) => {
|
|
445
|
+
const size = Math.round(option.sizeMB);
|
|
446
|
+
return option.label + ' ' + size + 'M' + (option.sizeMB > QQ_SOFT_LIMIT_MB ? ' 文件' : '');
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* 渲染面板卡片(和解析结果同一套模板)并上传到 assets,拿到 QQ markdown 能用的图片地址。
|
|
450
|
+
*
|
|
451
|
+
* QQ 的 markdown 图片必须是**可访问的 https 地址**(本地文件、base64 都不认),
|
|
452
|
+
* 所以这里:渲染 → data URL → 交给宿主 assets 服务转存 → 拿 URL。
|
|
453
|
+
* 拿不到 URL 就返回 null,调用方退回原来的纯文字表格面板。
|
|
454
|
+
*/
|
|
455
|
+
async function uploadPanelCard(e, request, detail, hotDanmaku = []) {
|
|
456
|
+
try {
|
|
457
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
458
|
+
const assets = runtime?.ctx?.assets;
|
|
459
|
+
if (!assets || typeof assets.upload !== 'function') {
|
|
460
|
+
node_karin_1.logger.debug('[QQ面板] 宿主没有可用的 assets 服务,跳过卡片图');
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
// 抖音:直接复用推送那套卡片构建器(renderWorkImage),保证和解析结果同一张卡
|
|
464
|
+
if (request.platform === 'douyin') {
|
|
465
|
+
const images = await (0, render_1.renderWorkImage)({
|
|
466
|
+
e,
|
|
467
|
+
Detail_Data: detail,
|
|
468
|
+
create_time: Number(detail?.create_time) || Math.floor(Date.now() / 1000),
|
|
469
|
+
shareLink: 'https://www.douyin.com/video/' + String(detail?.aweme_id ?? request.id),
|
|
470
|
+
// 还没选档,所以不显示分辨率块(和分享信息图一致)
|
|
471
|
+
videoSource: undefined
|
|
472
|
+
});
|
|
473
|
+
const first = Array.isArray(images) ? images[0] : images;
|
|
474
|
+
const src = String(first?.attrs?.src ?? first?.data?.file ?? '');
|
|
475
|
+
if (!src.startsWith('data:image/'))
|
|
476
|
+
return null;
|
|
477
|
+
const buffer = Buffer.from(src.slice(src.indexOf(',') + 1), 'base64');
|
|
478
|
+
const meta = (0, Render_1.getImageMetadata)(buffer);
|
|
479
|
+
const uploaded = await assets.upload(src, 'kkk-douyin-panel.png');
|
|
480
|
+
const url = typeof uploaded === 'string' ? uploaded : uploaded?.url;
|
|
481
|
+
if (!url || !/^https?:\/\//i.test(String(url)))
|
|
482
|
+
return null;
|
|
483
|
+
return { url: String(url), width: Number(meta.width) || 0, height: Number(meta.height) || 0 };
|
|
484
|
+
}
|
|
485
|
+
/** UP 主页信息(头像框 / 昵称颜色),失败不影响面板 */
|
|
486
|
+
let ownerCard = null;
|
|
487
|
+
if (request.platform === 'bilibili' && detail.owner?.mid) {
|
|
488
|
+
try {
|
|
489
|
+
const cardRes = await amagiClient_1.bilibiliFetcher.fetchUserCard({ host_mid: detail.owner.mid });
|
|
490
|
+
ownerCard = cardRes?.data?.data?.card ?? null;
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
node_karin_1.logger.debug('[QQ面板] 拉取 UP 主页失败(头像框/粉名会缺省): ' + String(error?.message ?? error));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const route = 'bilibili/videoInfo';
|
|
497
|
+
const cardData = request.platform === 'bilibili'
|
|
498
|
+
? {
|
|
499
|
+
share_url: 'https://b23.tv/' + (detail.bvid ?? request.id),
|
|
500
|
+
title: detail.title,
|
|
501
|
+
desc: '',
|
|
502
|
+
stat: detail.stat,
|
|
503
|
+
bvid: detail.bvid ?? request.id,
|
|
504
|
+
ctime: detail.ctime,
|
|
505
|
+
pic: detail.pic,
|
|
506
|
+
// 和解析结果一致:带热门弹幕,卡片顶部就有弹幕飘过
|
|
507
|
+
hotDanmaku,
|
|
508
|
+
/**
|
|
509
|
+
* 头像框和粉名都来自 UP 主页(userCard)—— 面板只拉了视频信息,
|
|
510
|
+
* 之前直接传 detail.owner 就少了 frame / usernameMeta 两个字段,
|
|
511
|
+
* 于是卡片上「没有头像框、名字也不是粉色」。这里和解析那边用同一份构造。
|
|
512
|
+
*/
|
|
513
|
+
owner: {
|
|
514
|
+
...detail.owner,
|
|
515
|
+
usernameMeta: ownerCard ? (0, dynamic_text_1.getUsernameMetadata)(ownerCard) : undefined,
|
|
516
|
+
frame: ownerCard?.pendant?.image || ''
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
: {
|
|
520
|
+
// 抖音卡片的数据结构由模板决定,这里先不做卡片,交给兜底面板
|
|
521
|
+
};
|
|
522
|
+
const images = await (0, Render_1.Render)(e, route, cardData).catch(() => null);
|
|
523
|
+
const first = Array.isArray(images) ? images[0] : images;
|
|
524
|
+
const src = String(first?.attrs?.src ?? first?.data?.file ?? '');
|
|
525
|
+
if (!src.startsWith('data:image/'))
|
|
526
|
+
return null;
|
|
527
|
+
const buffer = Buffer.from(src.slice(src.indexOf(',') + 1), 'base64');
|
|
528
|
+
const meta = (0, Render_1.getImageMetadata)(buffer);
|
|
529
|
+
const result = await assets.upload(src, 'kkk-panel.png');
|
|
530
|
+
const url = typeof result === 'string' ? result : result?.url;
|
|
531
|
+
if (!url || !/^https?:\/\//i.test(String(url))) {
|
|
532
|
+
node_karin_1.logger.debug('[QQ面板] assets 上传返回的地址不可用: ' + String(url || result));
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
return { url: String(url), width: Number(meta.width) || 0, height: Number(meta.height) || 0 };
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
node_karin_1.logger.debug('[QQ面板] 渲染/上传卡片失败: ' + String(error?.message ?? error));
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* 番剧面板:**卡片图 + 分集表格(可翻页)**。
|
|
544
|
+
*
|
|
545
|
+
* 关键点:
|
|
546
|
+
* - 卡片仍然用原来的 `bilibili/bangumi` 模板渲染,只是**不再把分集预览画进图里**
|
|
547
|
+
* (几十集会拉成一张超长图),分集改为表格按钮;
|
|
548
|
+
* - 表格行列数由配置 `bangumiPanelRows` × `bangumiPanelCols` 决定(默认 4×4);
|
|
549
|
+
* - 分集**倒序**排列(最新一集在最前面);
|
|
550
|
+
/* 番剧面板不保存任何状态:分集与卡片数据由调用方每次算好传进来(见 sendBangumiPanelPage) */
|
|
551
|
+
async function renderBangumiCard(e, cardData) {
|
|
552
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
553
|
+
const assets = runtime?.ctx?.assets;
|
|
554
|
+
if (!assets || typeof assets.upload !== 'function')
|
|
555
|
+
return null;
|
|
556
|
+
const images = await (0, Render_1.Render)(e, 'bilibili/bangumi', { ...cardData, Episodes: [], length: 0, hideTip: true });
|
|
557
|
+
const first = Array.isArray(images) ? images[0] : images;
|
|
558
|
+
const src = String(first?.attrs?.src ?? '');
|
|
559
|
+
if (!src.startsWith('data:image/'))
|
|
560
|
+
return null;
|
|
561
|
+
const buffer = Buffer.from(src.slice(src.indexOf(',') + 1), 'base64');
|
|
562
|
+
const meta = (0, Render_1.getImageMetadata)(buffer);
|
|
563
|
+
const uploaded = await assets.upload(src, 'kkk-bangumi.png');
|
|
564
|
+
const url = typeof uploaded === 'string' ? uploaded : uploaded?.url;
|
|
565
|
+
if (!url || !/^https?:\/\//i.test(String(url)))
|
|
566
|
+
return null;
|
|
567
|
+
return { url: String(url), width: Number(meta.width) || 1440, height: Number(meta.height) || 1080 };
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* 发送番剧面板的某一页。
|
|
571
|
+
*
|
|
572
|
+
* 参数全是「算出来的」:分集数组、卡片数据、页码 —— 不依赖任何内存状态,
|
|
573
|
+
* 所以宿主重启后点旧面板的「下一页」依然能正常出页面。
|
|
574
|
+
*/
|
|
575
|
+
async function sendBangumiPanelPage(e, episodes, cardData, page = 1) {
|
|
576
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
577
|
+
if (!runtime)
|
|
578
|
+
return false;
|
|
579
|
+
if (runtime.config.qqPanel === false)
|
|
580
|
+
return false;
|
|
581
|
+
if (!isQqPlatform(e))
|
|
582
|
+
return false;
|
|
583
|
+
if (!Array.isArray(episodes) || !episodes.length)
|
|
584
|
+
return false;
|
|
585
|
+
try {
|
|
586
|
+
const config = runtime.config;
|
|
587
|
+
const clamp = (value, min, max, fallback) => {
|
|
588
|
+
const num = Number(value);
|
|
589
|
+
return Number.isFinite(num) && num >= min && num <= max ? Math.floor(num) : fallback;
|
|
590
|
+
};
|
|
591
|
+
// 表格行列数:配置项 bangumiPanelCols / bangumiPanelRows(默认 5×4 = 一页 20 集)
|
|
592
|
+
// 注意:markdown 表格的**第一行就是表头**,所以「4 行」= 1 行表头 + 3 行正文,全都放数字
|
|
593
|
+
const cols = clamp(config.bangumiPanelCols, 2, 8, 5);
|
|
594
|
+
const rows = clamp(config.bangumiPanelRows, 1, 10, 4);
|
|
595
|
+
const perPage = cols * rows;
|
|
596
|
+
// 倒序:最新一集排最前
|
|
597
|
+
const ordered = episodes.map((episode, index) => ({ episode, number: index + 1 })).reverse();
|
|
598
|
+
const pages = Math.max(1, Math.ceil(ordered.length / perPage));
|
|
599
|
+
const current = Math.min(Math.max(1, Number(page) || 1), pages);
|
|
600
|
+
const slice = ordered.slice((current - 1) * perPage, current * perPage);
|
|
601
|
+
// 这一步前面已经有「检测到B站链接,开始解析」了,不再重复发「加载中…」
|
|
602
|
+
const card = await renderBangumiCard(e, cardData);
|
|
603
|
+
if (!card)
|
|
604
|
+
return false;
|
|
605
|
+
const parseCommand = (0, runtime_1.commandInvocation)('解析');
|
|
606
|
+
const lines = [''];
|
|
607
|
+
/**
|
|
608
|
+
* 一格:纯数字按钮,点下去就是选这一集。
|
|
609
|
+
*
|
|
610
|
+
* 按钮里**直接写链接**(不再用内存令牌):令牌存在插件内存里,宿主一重启就失效,
|
|
611
|
+
* 用户再点旧面板就会「没有任何反应」。链接本身很短(bilibili.com/video/BVxxx),
|
|
612
|
+
* 而且只在按钮的 command 里、用户看到的是数字。
|
|
613
|
+
*/
|
|
614
|
+
const episodeCell = ({ episode, number }) => {
|
|
615
|
+
const url = episode?.bvid ? 'https://www.bilibili.com/video/' + episode.bvid : (episode?.link || '');
|
|
616
|
+
return cmdInput(parseCommand + ' ' + url + ' --panel=1', String(number));
|
|
617
|
+
};
|
|
618
|
+
// 表格:第一行(markdown 的表头行)也放数字,整页 cols × rows 格全是可选集数
|
|
619
|
+
for (let i = 0; i < perPage; i += cols) {
|
|
620
|
+
const rowCells = slice.slice(i, i + cols).map(episodeCell);
|
|
621
|
+
while (rowCells.length < cols)
|
|
622
|
+
rowCells.push(' ');
|
|
623
|
+
lines.push('| ' + rowCells.join(' | ') + ' |');
|
|
624
|
+
// 表头行后面必须跟分隔行
|
|
625
|
+
if (i === 0)
|
|
626
|
+
lines.push('| ' + Array(cols).fill(':---:').join(' | ') + ' |');
|
|
627
|
+
}
|
|
628
|
+
// 分页信息放表格**下面**(不放表头里,表头那行要留给集数)
|
|
629
|
+
/**
|
|
630
|
+
* 翻页按钮把番剧链接写进去(重启后也能用)。
|
|
631
|
+
* 注意链接要用 `bangumi/play/ss<season_id>` 这种**解析器认得的**形式:
|
|
632
|
+
* 卡片数据里的 `Link` 是 `bangumi/media/md…`,kkk 拿它取不到作品 ID(日志:无法获取作品ID)。
|
|
633
|
+
*/
|
|
634
|
+
const seasonUrl = Number(cardData?.seasonID)
|
|
635
|
+
? 'https://www.bilibili.com/bangumi/play/ss' + String(cardData.seasonID)
|
|
636
|
+
: String(cardData?.Link || '');
|
|
637
|
+
const pageCommand = (page) => parseCommand + ' ' + seasonUrl + ' --bgp=' + page;
|
|
638
|
+
const prev = current > 1 ? cmdInput(pageCommand(current - 1), '上一页') : '';
|
|
639
|
+
const next = current < pages ? cmdInput(pageCommand(current + 1), '下一页') : '';
|
|
640
|
+
// 翻页信息放在表格**外面**(不占表格格子):上一页 | 第 x/y 页 | 下一页
|
|
641
|
+
lines.push('');
|
|
642
|
+
lines.push((prev ? prev : ' ') + ' 第 ' + current + ' / ' + pages + ' 页 ' + (next ? next : ' '));
|
|
643
|
+
await recallLastPanel(e);
|
|
644
|
+
const sent = await e.reply(node_karin_1.segment.markdown(lines.join('\n')));
|
|
645
|
+
rememberPanelMessage(e, sent?.messageId);
|
|
646
|
+
node_karin_1.logger.debug('[QQ面板] 番剧面板 ' + current + '/' + pages + ' 页(' + ordered.length + ' 集,' + cols + '×' + rows + ')');
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
catch (error) {
|
|
650
|
+
node_karin_1.logger.debug('[QQ面板] 番剧面板发送失败: ' + String(error?.message ?? error));
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/** 发送番剧面板(episodes + 卡片数据 + 页码,全程不依赖内存状态) */
|
|
655
|
+
async function sendBangumiPanel(e, episodes, cardData, page = 1) {
|
|
656
|
+
if (!Array.isArray(episodes) || !episodes.length)
|
|
657
|
+
return false;
|
|
658
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
659
|
+
if (!runtime)
|
|
660
|
+
return false;
|
|
661
|
+
if (runtime.config.qqPanel === false)
|
|
662
|
+
return false;
|
|
663
|
+
if (!isQqPlatform(e))
|
|
664
|
+
return false;
|
|
665
|
+
return await sendBangumiPanelPage(e, episodes, cardData, page);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* 生成并发送解析面板。
|
|
669
|
+
* @param e 消息事件
|
|
670
|
+
* @param request 作品信息
|
|
671
|
+
* @param options.danmaku 当前是否为「视频 + 弹幕」
|
|
672
|
+
* @returns 是否已经发出面板(false 时调用方应继续正常解析)
|
|
673
|
+
*/
|
|
674
|
+
async function sendQqParsePanel(e, request, options) {
|
|
675
|
+
const runtime = (0, runtime_1.tryGetRuntime)();
|
|
676
|
+
if (!runtime)
|
|
677
|
+
return false;
|
|
678
|
+
if (runtime.config.qqPanel === false)
|
|
679
|
+
return false;
|
|
680
|
+
if (!isQqPlatform(e))
|
|
681
|
+
return false;
|
|
682
|
+
const info = await fetchPanelInfo(request);
|
|
683
|
+
if (!info || !info.options.length)
|
|
684
|
+
return false;
|
|
685
|
+
const limit = Number(runtime.config.qqFileLimitMB ?? 200) || 200;
|
|
686
|
+
const visible = info.options.filter((option) => option.sizeMB <= limit);
|
|
687
|
+
// 一档都不满足就别把面板做空:保留最小的一档并明确提示,否则用户连解析都点不了
|
|
688
|
+
const overflow = visible.length === 0;
|
|
689
|
+
const shown = overflow ? [info.options[info.options.length - 1]] : visible;
|
|
690
|
+
// 按钮发出去的就是「用户视角的指令」,前缀按 Koishi 当前配置来(配了空前缀就是不带前缀的裸指令)
|
|
691
|
+
const parseCommand = (0, runtime_1.commandInvocation)('解析');
|
|
692
|
+
const danmakuCommand = (0, runtime_1.commandInvocation)('弹幕解析');
|
|
693
|
+
/**
|
|
694
|
+
* 面板只由「卡片图片 + 按钮」组成,不带任何说明文字:
|
|
695
|
+
* 文字会被 QQ 挤成一堆行,手机上看很乱;卡片图里已经有标题/UP/时长/数据了。
|
|
696
|
+
*/
|
|
697
|
+
const lines = [];
|
|
698
|
+
// 这一段要拉弹幕 + 渲染卡片(十几秒),先给一句「加载中…」,也顺便撤掉上一条面板
|
|
699
|
+
const loadingId = await showLoadingTip(e);
|
|
700
|
+
const card = await uploadPanelCard(e, request, info.detail, info.hotDanmaku ?? []);
|
|
701
|
+
if (card && card.url) {
|
|
702
|
+
// QQ markdown 的图片必须写成 
|
|
703
|
+
lines.push('');
|
|
704
|
+
}
|
|
705
|
+
// 按钮里只放短令牌,链接存在内存里(见 rememberPanelRequest 的说明);
|
|
706
|
+
// 顺带把各档体积存进去:解析时能在「收到请求,开始下载」里提示会不会变成文件发送
|
|
707
|
+
const sizes = {};
|
|
708
|
+
for (const option of info.options)
|
|
709
|
+
sizes[String(option.id)] = option.sizeMB;
|
|
710
|
+
const token = rememberPanelRequest(request, sizes);
|
|
711
|
+
/**
|
|
712
|
+
* 按钮里**同时写链接和令牌**:
|
|
713
|
+
* - 链接让按钮「无状态」——宿主重启后点旧面板照样能解析(令牌会失效);
|
|
714
|
+
* - 令牌只用来查这一档的预估体积(提示「超过 30MB 会以文件发送」)。
|
|
715
|
+
*/
|
|
716
|
+
const urlPart = request.url && request.url.length <= 120 ? request.url : '';
|
|
717
|
+
const short = '--p=' + token;
|
|
718
|
+
const qualityFlag = request.platform === 'bilibili' ? '--qn=' : '--q=';
|
|
719
|
+
/** 一个按钮:点下去**直接按这一档画质解析** */
|
|
720
|
+
const cell = (command, id, label) => cmdInput(command + ' ' + (urlPart || short) + ' ' + short + ' ' + qualityFlag + id, label);
|
|
721
|
+
/**
|
|
722
|
+
* 表格排版。
|
|
723
|
+
* - 开启弹幕解析:清晰度 / 视频 / 弹幕 / 大小 四列(清晰度是文字,后两列是按钮)
|
|
724
|
+
* - **关闭**弹幕解析:去掉弹幕列,且「清晰度」本身就是按钮(配置项 enableDanmakuParse)
|
|
725
|
+
*/
|
|
726
|
+
/**
|
|
727
|
+
* 弹幕功能已整体移除:这里恒为 false,面板只保留「清晰度=按钮 + 大小」两列。
|
|
728
|
+
* (卡片上方的热门弹幕是渲染层面的事,不在这个面板里,不受影响)
|
|
729
|
+
*/
|
|
730
|
+
const danmakuEnabled = false;
|
|
731
|
+
if (danmakuEnabled) {
|
|
732
|
+
lines.push('| 清晰度 | 视频 | 弹幕 | 大小 |');
|
|
733
|
+
lines.push('| :--- | :---: | :---: | ---: |');
|
|
734
|
+
for (const option of shown) {
|
|
735
|
+
const size = Math.round(option.sizeMB) + 'M';
|
|
736
|
+
const videoCell = cell(parseCommand, option.id, '视频');
|
|
737
|
+
const danmakuCell = cell(danmakuCommand, option.id, '弹幕');
|
|
738
|
+
lines.push('| ' + option.label + ' | ' + videoCell + ' | ' + danmakuCell + ' | ' + size + ' |');
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
// 没有弹幕可选时,画质名直接当按钮,省掉中间那一步
|
|
743
|
+
lines.push('| 清晰度 | 大小 |');
|
|
744
|
+
lines.push('| :--- | ---: |');
|
|
745
|
+
for (const option of shown) {
|
|
746
|
+
const size = Math.round(option.sizeMB) + 'M';
|
|
747
|
+
lines.push('| ' + cell(parseCommand, option.id, option.label) + ' | ' + size + ' |');
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
await replaceLoadingTip(e, loadingId, node_karin_1.segment.markdown(lines.join(String.fromCharCode(10))));
|
|
751
|
+
node_karin_1.logger.debug('[QQ面板] 已发送解析面板: ' + request.platform + ' ' + request.id + '(' + shown.length + '/' + info.options.length + ' 档画质)');
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
exports.default = sendQqParsePanel;
|
|
755
|
+
/**
|
|
756
|
+
* 解析开始时的提示语 —— 两种来源说两句话:
|
|
757
|
+
* - **面板按钮点出来的**:画质面板刚发过,这里只回「收到请求,开始下载」
|
|
758
|
+
* - 手工发链接的:仍然是「检测到 XX 链接,开始解析」
|
|
759
|
+
* 两种情况都会先用 replyReplacing 撤掉上一条机器人消息,群里始终只留最新一条。
|
|
760
|
+
*/
|
|
761
|
+
const sendParseTip = async (e, platformName) => {
|
|
762
|
+
const fromPanel = (0, ParseOverride_1.getParseOverride)()?.fromPanel === true;
|
|
763
|
+
if (fromPanel) {
|
|
764
|
+
await replyReplacing(e, '收到请求,开始下载');
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (Config_1.Config.app.parseTip) {
|
|
768
|
+
await replyReplacing(e, '检测到' + platformName + '链接,开始解析');
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
exports.sendParseTip = sendParseTip;
|
|
772
|
+
/**
|
|
773
|
+
* 把一张远程图片转成 QQ markdown 能用的地址。
|
|
774
|
+
*
|
|
775
|
+
* QQ 的 markdown 图片必须写成 ``,而且外链常常被拦,
|
|
776
|
+
* 所以这里先下载再通过宿主的 assets 服务上传一份,返回可直接嵌进 markdown 的地址与原始尺寸。
|
|
777
|
+
*
|
|
778
|
+
* @param url 原始图片地址
|
|
779
|
+
* @param maxWidth 最大显示宽度(等比缩放,避免大图刷屏)
|
|
780
|
+
*/
|
|
781
|
+
const toMarkdownImage = async (url, maxWidth = 420) => {
|
|
782
|
+
try {
|
|
783
|
+
const ctx = (0, runtime_1.tryGetRuntime)()?.ctx;
|
|
784
|
+
const assets = ctx?.assets;
|
|
785
|
+
if (!assets?.upload) {
|
|
786
|
+
// 没有 assets 服务就只能用原始链接(QQ 大概率取不到,但比直接放弃强)
|
|
787
|
+
node_karin_1.logger.mark('[QQ面板] 宿主没有 assets 服务,md 图片改用原始链接');
|
|
788
|
+
return /^https?:\/\//i.test(url) ? '' : null;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* 三种图片来源都要认:
|
|
792
|
+
* 1. **data URI**(卡片、提示图 ✓)
|
|
793
|
+
* 2. **本地文件路径 / file://**(实况图的 Motion Photo 封面就是这种!
|
|
794
|
+
* 之前只处理远程 URL,这里 fetch 一个本地路径必然失败 →
|
|
795
|
+
* md 生成不出来 → 整条图集退化成「一张一张发」)
|
|
796
|
+
* 3. 远程 https 图片
|
|
797
|
+
*/
|
|
798
|
+
let buffer;
|
|
799
|
+
let mime = 'image/jpeg';
|
|
800
|
+
const localPath = url.startsWith('file://') ? decodeURIComponent(url.replace(/^file:\/\//, '')) : url;
|
|
801
|
+
/**
|
|
802
|
+
* **base64:// 必须认** —— 抖音的图片地址就是这种(processImageUrl 的产物)。
|
|
803
|
+
* 少了这条会让 md 生成失败,整条图集退化成「一张一张发」
|
|
804
|
+
* (诊断日志表现为「准备发送图集: md=无 视频=9 段」)。
|
|
805
|
+
*/
|
|
806
|
+
if (url.startsWith('base64://')) {
|
|
807
|
+
buffer = Buffer.from(url.slice('base64://'.length), 'base64');
|
|
808
|
+
mime = 'image/jpeg';
|
|
809
|
+
}
|
|
810
|
+
else if (!url.startsWith('data:') && !/^https?:\/\//i.test(url) && node_fs_1.default.existsSync(localPath)) {
|
|
811
|
+
buffer = node_fs_1.default.readFileSync(localPath);
|
|
812
|
+
mime = localPath.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg';
|
|
813
|
+
}
|
|
814
|
+
else if (url.startsWith('data:')) {
|
|
815
|
+
const comma = url.indexOf(',');
|
|
816
|
+
mime = url.slice(5, url.indexOf(';')) || 'image/jpeg';
|
|
817
|
+
buffer = Buffer.from(url.slice(comma + 1), 'base64');
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
const res = await fetch(url);
|
|
821
|
+
if (!res.ok) {
|
|
822
|
+
node_karin_1.logger.mark('[QQ面板] md 图片下载失败 HTTP ' + res.status + ',改用原始链接: ' + url.slice(0, 60));
|
|
823
|
+
return '';
|
|
824
|
+
}
|
|
825
|
+
buffer = Buffer.from(await res.arrayBuffer());
|
|
826
|
+
mime = String(res.headers.get('content-type') ?? 'image/jpeg').split(';')[0];
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* 尺寸必须尽量取到**真实值** —— 原来取不到就用 maxWidth 兜底,
|
|
830
|
+
* 结果 width/height 相等 → 图片被拉成**正方形**(用户反馈「比例不对」)。
|
|
831
|
+
* 这里补一层 ffprobe(webp/avif 这类 getImageMetadata 认不出的格式也能拿到)。
|
|
832
|
+
*/
|
|
833
|
+
const meta = (0, Render_1.getImageMetadata)(buffer);
|
|
834
|
+
let realWidth = Number(meta.width) || 0;
|
|
835
|
+
let realHeight = Number(meta.height) || 0;
|
|
836
|
+
if (!realWidth || !realHeight) {
|
|
837
|
+
try {
|
|
838
|
+
const { spawnSync } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
|
|
839
|
+
const probe = spawnSync('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'csv=p=0', '-'], { input: buffer });
|
|
840
|
+
const parts = String(probe.stdout ?? '').trim().split(',');
|
|
841
|
+
realWidth = Number(parts[0]) || 0;
|
|
842
|
+
realHeight = Number(parts[1]) || 0;
|
|
843
|
+
}
|
|
844
|
+
catch { /* 兜底失败 */ }
|
|
845
|
+
}
|
|
846
|
+
const uploaded = await assets.upload('data:' + mime + ';base64,' + buffer.toString('base64'), 'kkk-md.png');
|
|
847
|
+
const finalUrl = typeof uploaded === 'string' ? uploaded : uploaded?.url;
|
|
848
|
+
if (!finalUrl || !/^https?:\/\//i.test(String(finalUrl))) {
|
|
849
|
+
// 上传拿不到公网地址:退回原始链接,至少图片还能显示
|
|
850
|
+
node_karin_1.logger.mark('[QQ面板] assets 上传没有返回公网地址,md 图片改用原始链接');
|
|
851
|
+
return /^https?:\/\//i.test(url) ? '' : null;
|
|
852
|
+
}
|
|
853
|
+
if (!realWidth || !realHeight) {
|
|
854
|
+
// 真的拿不到:宁可不写尺寸让客户端按原图比例显示,也别硬套成正方形
|
|
855
|
+
node_karin_1.logger.mark('[QQ面板] 图片尺寸未知,改用原始尺寸显示: ' + url.slice(0, 50));
|
|
856
|
+
return '';
|
|
857
|
+
}
|
|
858
|
+
const w = Math.min(realWidth, maxWidth);
|
|
859
|
+
const h = Math.max(1, Math.round((realHeight * w) / realWidth));
|
|
860
|
+
return '';
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
node_karin_1.logger.mark('[QQ面板] 图片转 markdown 失败: ' + String(error?.message ?? error).slice(0, 120));
|
|
864
|
+
// 兜底:直接用原始链接(QQ 取不到就取不到,总比整条图集退化成逐张发好)
|
|
865
|
+
return /^https?:\/\//i.test(url) ? '' : null;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
exports.toMarkdownImage = toMarkdownImage;
|
|
869
|
+
/**
|
|
870
|
+
* 一组图片合成**一条** markdown 消息(图集解析用):
|
|
871
|
+
* 一张图一条消息、或者走合并转发都会刷屏,这里统一成单条 md,图片按 maxWidth 等比缩放。
|
|
872
|
+
*/
|
|
873
|
+
const buildMarkdownImageMessage = async (urls, maxWidth = 420) => {
|
|
874
|
+
const parts = (await Promise.all(urls.map((url) => (0, exports.toMarkdownImage)(url, maxWidth)))).filter(Boolean);
|
|
875
|
+
if (!parts.length)
|
|
876
|
+
return null;
|
|
877
|
+
return node_karin_1.segment.markdown(parts.join('\n'));
|
|
878
|
+
};
|
|
879
|
+
exports.buildMarkdownImageMessage = buildMarkdownImageMessage;
|