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.
Files changed (702) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +674 -0
  3. package/README.md +98 -0
  4. package/assets/web/assets/CronEditor-CYgFpX-g.js +1 -0
  5. package/assets/web/assets/DesktopLayout-iI43ij11.js +1 -0
  6. package/assets/web/assets/MobileLayout-CziU8MCR.js +1 -0
  7. package/assets/web/assets/ThemeSwitch-DFohhwd9.js +1 -0
  8. package/assets/web/assets/index-BRROK0vh.js +47 -0
  9. package/assets/web/assets/index-Dj-StEiS.css +2 -0
  10. package/assets/web/assets/separator-CxcFlCzz.js +1 -0
  11. package/assets/web/favicon.svg +1 -0
  12. package/assets/web/icons.svg +24 -0
  13. package/assets/web/index.html +15 -0
  14. package/assets/webui.html +115 -0
  15. package/client/index.ts +63 -0
  16. package/config/default_config/app.yaml +64 -0
  17. package/config/default_config/bilibili.yaml +116 -0
  18. package/config/default_config/config.json +157 -0
  19. package/config/default_config/cookies.yaml +11 -0
  20. package/config/default_config/douyin.yaml +99 -0
  21. package/config/default_config/kuaishou.yaml +8 -0
  22. package/config/default_config/pushlist.yaml +37 -0
  23. package/config/default_config/request.yaml +22 -0
  24. package/config/default_config/upload.yaml +50 -0
  25. package/config/default_config/xiaohongshu.yaml +23 -0
  26. package/dist/index.js +1 -0
  27. package/lib/compat/axios.js +23 -0
  28. package/lib/compat/cron.js +77 -0
  29. package/lib/compat/esm.js +15 -0
  30. package/lib/compat/express.js +23 -0
  31. package/lib/compat/heroui.js +12 -0
  32. package/lib/compat/lodash.js +33 -0
  33. package/lib/compat/logger.js +118 -0
  34. package/lib/compat/node-karin.js +1053 -0
  35. package/lib/compat/phosphor-icons.js +27 -0
  36. package/lib/compat/qqcard.js +256 -0
  37. package/lib/compat/root.js +44 -0
  38. package/lib/compat/runtime.js +87 -0
  39. package/lib/compat/segment.js +167 -0
  40. package/lib/compat/sqlite3.js +117 -0
  41. package/lib/compat/start.js +7 -0
  42. package/lib/compat/syncConfig.js +65 -0
  43. package/lib/compat/template-react.js +30 -0
  44. package/lib/compat/text.js +65 -0
  45. package/lib/compat/yaml.js +13 -0
  46. package/lib/configBridge.js +91 -0
  47. package/lib/index.js +775 -0
  48. package/lib/karin/app.js +34 -0
  49. package/lib/karin/apps/__ready.js +9 -0
  50. package/lib/karin/apps/admin.js +136 -0
  51. package/lib/karin/apps/help.js +233 -0
  52. package/lib/karin/apps/push.js +291 -0
  53. package/lib/karin/apps/qrlogin.js +176 -0
  54. package/lib/karin/apps/statistics.js +209 -0
  55. package/lib/karin/apps/testPush.js +247 -0
  56. package/lib/karin/apps/tools.js +473 -0
  57. package/lib/karin/apps/update.js +252 -0
  58. package/lib/karin/index.js +37 -0
  59. package/lib/karin/module/db/bilibili.js +831 -0
  60. package/lib/karin/module/db/douyin.js +918 -0
  61. package/lib/karin/module/db/index.js +165 -0
  62. package/lib/karin/module/db/migration.js +199 -0
  63. package/lib/karin/module/db/statistics.js +559 -0
  64. package/lib/karin/module/index.js +94 -0
  65. package/lib/karin/module/server/constants/routes.js +48 -0
  66. package/lib/karin/module/server/controllers/bots.js +230 -0
  67. package/lib/karin/module/server/controllers/config.js +75 -0
  68. package/lib/karin/module/server/controllers/video.js +208 -0
  69. package/lib/karin/module/server/index.js +26 -0
  70. package/lib/karin/module/server/middlewares/auth.js +133 -0
  71. package/lib/karin/module/server/routes/amagi.js +57 -0
  72. package/lib/karin/module/server/routes/api.js +40 -0
  73. package/lib/karin/module/server/routes/index.js +97 -0
  74. package/lib/karin/module/server/routes/ssr.js +16 -0
  75. package/lib/karin/module/server/routes/static.js +43 -0
  76. package/lib/karin/module/utils/Base.js +403 -0
  77. package/lib/karin/module/utils/CardParser.js +487 -0
  78. package/lib/karin/module/utils/Common.js +418 -0
  79. package/lib/karin/module/utils/Config.js +434 -0
  80. package/lib/karin/module/utils/EmojiReaction.js +242 -0
  81. package/lib/karin/module/utils/ErrorHandler/handler.js +114 -0
  82. package/lib/karin/module/utils/ErrorHandler/index.js +55 -0
  83. package/lib/karin/module/utils/ErrorHandler/render.js +182 -0
  84. package/lib/karin/module/utils/ErrorHandler/sender.js +206 -0
  85. package/lib/karin/module/utils/ErrorHandler/strategy.js +41 -0
  86. package/lib/karin/module/utils/ErrorHandler/types.js +2 -0
  87. package/lib/karin/module/utils/ErrorHandler/utils.js +108 -0
  88. package/lib/karin/module/utils/FFmpeg.js +298 -0
  89. package/lib/karin/module/utils/ImageHelper.js +91 -0
  90. package/lib/karin/module/utils/ImageSlice.js +429 -0
  91. package/lib/karin/module/utils/MotionPhoto.js +250 -0
  92. package/lib/karin/module/utils/Network/Downloader.js +413 -0
  93. package/lib/karin/module/utils/Network/ImageDownloader.js +250 -0
  94. package/lib/karin/module/utils/Network/Network.js +332 -0
  95. package/lib/karin/module/utils/Network/ThrottleStream.js +71 -0
  96. package/lib/karin/module/utils/Network/constants.js +129 -0
  97. package/lib/karin/module/utils/Network/helpers.js +174 -0
  98. package/lib/karin/module/utils/Network/index.js +34 -0
  99. package/lib/karin/module/utils/Network/types.js +12 -0
  100. package/lib/karin/module/utils/Networks.js +17 -0
  101. package/lib/karin/module/utils/ParseLock.js +34 -0
  102. package/lib/karin/module/utils/ParseOverride.js +88 -0
  103. package/lib/karin/module/utils/QqCardResolve.js +114 -0
  104. package/lib/karin/module/utils/QqPanel.js +879 -0
  105. package/lib/karin/module/utils/Render/coverTheme.js +256 -0
  106. package/lib/karin/module/utils/Render/index.js +582 -0
  107. package/lib/karin/module/utils/amagiClient.js +338 -0
  108. package/lib/karin/module/utils/avatar.js +201 -0
  109. package/lib/karin/module/utils/bot.js +141 -0
  110. package/lib/karin/module/utils/build-metadata.js +50 -0
  111. package/lib/karin/module/utils/changelog.js +166 -0
  112. package/lib/karin/module/utils/index.js +26 -0
  113. package/lib/karin/module/utils/loginSession.js +259 -0
  114. package/lib/karin/module/utils/parseTip.js +93 -0
  115. package/lib/karin/module/utils/runtime-report.js +140 -0
  116. package/lib/karin/module/utils/semver.js +72 -0
  117. package/lib/karin/module/utils/statisticsAggregate.js +415 -0
  118. package/lib/karin/platform/bilibili/bilibili.js +1805 -0
  119. package/lib/karin/platform/bilibili/comments.js +294 -0
  120. package/lib/karin/platform/bilibili/danmaku.js +555 -0
  121. package/lib/karin/platform/bilibili/dynamic-text.js +827 -0
  122. package/lib/karin/platform/bilibili/genParams.js +37 -0
  123. package/lib/karin/platform/bilibili/getID.js +159 -0
  124. package/lib/karin/platform/bilibili/index.js +24 -0
  125. package/lib/karin/platform/bilibili/live-status.js +40 -0
  126. package/lib/karin/platform/bilibili/login.js +132 -0
  127. package/lib/karin/platform/bilibili/push.js +1397 -0
  128. package/lib/karin/platform/bilibili/riskControl.js +133 -0
  129. package/lib/karin/platform/bilibili/search.js +299 -0
  130. package/lib/karin/platform/common/danmaku-types.js +12 -0
  131. package/lib/karin/platform/douyin/comments.js +310 -0
  132. package/lib/karin/platform/douyin/danmaku.js +893 -0
  133. package/lib/karin/platform/douyin/douyin.js +1145 -0
  134. package/lib/karin/platform/douyin/getID.js +94 -0
  135. package/lib/karin/platform/douyin/index.js +24 -0
  136. package/lib/karin/platform/douyin/login.js +230 -0
  137. package/lib/karin/platform/douyin/push/favorite.js +75 -0
  138. package/lib/karin/platform/douyin/push/index.js +21 -0
  139. package/lib/karin/platform/douyin/push/live.js +69 -0
  140. package/lib/karin/platform/douyin/push/post.js +60 -0
  141. package/lib/karin/platform/douyin/push/recommend.js +74 -0
  142. package/lib/karin/platform/douyin/push/render.js +621 -0
  143. package/lib/karin/platform/douyin/push/types.js +2 -0
  144. package/lib/karin/platform/douyin/push.js +1128 -0
  145. package/lib/karin/platform/douyin/types.js +13 -0
  146. package/lib/karin/platform/douyin/videoQuality.js +222 -0
  147. package/lib/karin/platform/douyin/workType.js +182 -0
  148. package/lib/karin/platform/index.js +20 -0
  149. package/lib/karin/platform/kuaishou/comments.js +91 -0
  150. package/lib/karin/platform/kuaishou/getID.js +43 -0
  151. package/lib/karin/platform/kuaishou/getdata.js +41 -0
  152. package/lib/karin/platform/kuaishou/index.js +20 -0
  153. package/lib/karin/platform/kuaishou/kuaishou.js +122 -0
  154. package/lib/karin/platform/xiaohongshu/comments.js +162 -0
  155. package/lib/karin/platform/xiaohongshu/getID.js +129 -0
  156. package/lib/karin/platform/xiaohongshu/index.js +18 -0
  157. package/lib/karin/platform/xiaohongshu/xiaohongshu.js +643 -0
  158. package/lib/karin/root.js +73 -0
  159. package/lib/karin/setup.js +99 -0
  160. package/lib/karin/types/config/amagi.js +2 -0
  161. package/lib/karin/types/config/app.js +2 -0
  162. package/lib/karin/types/config/bilibili.js +2 -0
  163. package/lib/karin/types/config/cookies.js +2 -0
  164. package/lib/karin/types/config/douyin.js +2 -0
  165. package/lib/karin/types/config/index.js +2 -0
  166. package/lib/karin/types/config/kuaishou.js +2 -0
  167. package/lib/karin/types/config/pushlist.js +2 -0
  168. package/lib/karin/types/config/request.js +2 -0
  169. package/lib/karin/types/config/upload.js +2 -0
  170. package/lib/karin/types/config/xiaohongshu.js +2 -0
  171. package/lib/karin/types/datatypes.js +2 -0
  172. package/lib/karin/types/index.js +18 -0
  173. package/lib/karin/types/netrorks/NetworksConfigType.js +2 -0
  174. package/lib/karin/types/netrorks/index.js +17 -0
  175. package/lib/karin/web.config.js +49 -0
  176. package/lib/ktr/registry.js +82 -0
  177. package/lib/ktr/template/_preview/components/PreviewInfoCard.js +10 -0
  178. package/lib/ktr/template/_preview/components/PreviewLayout.js +10 -0
  179. package/lib/ktr/template/_preview/components/PreviewVideoCard.js +8 -0
  180. package/lib/ktr/template/_preview/render.js +142 -0
  181. package/lib/ktr/template/_preview/types.js +2 -0
  182. package/lib/ktr/template/_preview/utils/status.js +31 -0
  183. package/lib/ktr/template/_preview/utils/time.js +12 -0
  184. package/lib/ktr/template/bilibili/bangumi/components/bangumi.js +127 -0
  185. package/lib/ktr/template/bilibili/bangumi/components/types.js +2 -0
  186. package/lib/ktr/template/bilibili/bangumi/index.js +12 -0
  187. package/lib/ktr/template/bilibili/comment/components/Comment.js +193 -0
  188. package/lib/ktr/template/bilibili/comment/components/types.js +2 -0
  189. package/lib/ktr/template/bilibili/comment/index.js +10 -0
  190. package/lib/ktr/template/bilibili/components/Icons.js +66 -0
  191. package/lib/ktr/template/bilibili/components/shared.js +93 -0
  192. package/lib/ktr/template/bilibili/components/types.js +2 -0
  193. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/DYNAMIC_TYPE_ARTICLE.js +66 -0
  194. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/types.js +2 -0
  195. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/index.js +10 -0
  196. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/DYNAMIC_TYPE_AV.js +87 -0
  197. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/types.js +2 -0
  198. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/index.js +10 -0
  199. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/DYNAMIC_TYPE_DRAW.js +93 -0
  200. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/types.js +2 -0
  201. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/index.js +10 -0
  202. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/DYNAMIC_TYPE_FORWARD.js +132 -0
  203. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/types.js +2 -0
  204. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/index.js +10 -0
  205. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/DYNAMIC_TYPE_LIVE_RCMD.js +84 -0
  206. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/types.js +2 -0
  207. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/index.js +10 -0
  208. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/DYNAMIC_TYPE_WORD.js +38 -0
  209. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/types.js +2 -0
  210. package/lib/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/index.js +10 -0
  211. package/lib/ktr/template/bilibili/dynamic/components/AdditionalCard.js +59 -0
  212. package/lib/ktr/template/bilibili/dynamic/components/CommonComponents.js +30 -0
  213. package/lib/ktr/template/bilibili/dynamic/types.js +2 -0
  214. package/lib/ktr/template/bilibili/qrcodeImg/components/qrcodeImg.js +48 -0
  215. package/lib/ktr/template/bilibili/qrcodeImg/components/types.js +3 -0
  216. package/lib/ktr/template/bilibili/qrcodeImg/index.js +10 -0
  217. package/lib/ktr/template/bilibili/userlist/components/UserList.js +53 -0
  218. package/lib/ktr/template/bilibili/userlist/components/types.js +3 -0
  219. package/lib/ktr/template/bilibili/userlist/index.js +13 -0
  220. package/lib/ktr/template/bilibili/videoInfo/components/types.js +2 -0
  221. package/lib/ktr/template/bilibili/videoInfo/components/videoInfo.js +62 -0
  222. package/lib/ktr/template/bilibili/videoInfo/index.js +10 -0
  223. package/lib/ktr/template/components/AmbientCover.js +36 -0
  224. package/lib/ktr/template/components/DefaultLayout.js +33 -0
  225. package/lib/ktr/template/components/GlowImage.js +276 -0
  226. package/lib/ktr/template/components/QRCodeWithAvatar.js +34 -0
  227. package/lib/ktr/template/components/RolldownLogo.js +6 -0
  228. package/lib/ktr/template/components/ViteLogo.js +6 -0
  229. package/lib/ktr/template/components/iconRegistry.js +173 -0
  230. package/lib/ktr/template/douyin/article-work/components/ArticleWork.js +99 -0
  231. package/lib/ktr/template/douyin/article-work/components/types.js +3 -0
  232. package/lib/ktr/template/douyin/article-work/index.js +10 -0
  233. package/lib/ktr/template/douyin/comment/components/Comment.js +189 -0
  234. package/lib/ktr/template/douyin/comment/components/types.js +2 -0
  235. package/lib/ktr/template/douyin/comment/index.js +10 -0
  236. package/lib/ktr/template/douyin/components/Icons.js +29 -0
  237. package/lib/ktr/template/douyin/components/types.js +3 -0
  238. package/lib/ktr/template/douyin/dynamic/components/Dynamic.js +90 -0
  239. package/lib/ktr/template/douyin/dynamic/components/types.js +3 -0
  240. package/lib/ktr/template/douyin/dynamic/index.js +10 -0
  241. package/lib/ktr/template/douyin/favorite-list/components/FavoriteList.js +30 -0
  242. package/lib/ktr/template/douyin/favorite-list/components/types.js +3 -0
  243. package/lib/ktr/template/douyin/favorite-list/index.js +10 -0
  244. package/lib/ktr/template/douyin/image-work/components/ImageWork.js +136 -0
  245. package/lib/ktr/template/douyin/image-work/components/types.js +2 -0
  246. package/lib/ktr/template/douyin/image-work/index.js +10 -0
  247. package/lib/ktr/template/douyin/live/components/Live.js +51 -0
  248. package/lib/ktr/template/douyin/live/components/types.js +3 -0
  249. package/lib/ktr/template/douyin/live/index.js +10 -0
  250. package/lib/ktr/template/douyin/musicinfo/components/MusicInfo.js +60 -0
  251. package/lib/ktr/template/douyin/musicinfo/components/types.js +3 -0
  252. package/lib/ktr/template/douyin/musicinfo/index.js +10 -0
  253. package/lib/ktr/template/douyin/qrcodeImg/components/qrcodeImg.js +39 -0
  254. package/lib/ktr/template/douyin/qrcodeImg/components/types.js +3 -0
  255. package/lib/ktr/template/douyin/qrcodeImg/index.js +9 -0
  256. package/lib/ktr/template/douyin/recommend-list/components/RecommendList.js +30 -0
  257. package/lib/ktr/template/douyin/recommend-list/components/types.js +3 -0
  258. package/lib/ktr/template/douyin/recommend-list/index.js +10 -0
  259. package/lib/ktr/template/douyin/user_profile/components/UserVideoList.js +54 -0
  260. package/lib/ktr/template/douyin/user_profile/components/types.js +3 -0
  261. package/lib/ktr/template/douyin/user_profile/index.js +9 -0
  262. package/lib/ktr/template/douyin/userlist/components/UserList.js +50 -0
  263. package/lib/ktr/template/douyin/userlist/components/types.js +3 -0
  264. package/lib/ktr/template/douyin/userlist/index.js +13 -0
  265. package/lib/ktr/template/douyin/video-work/components/VideoWork.js +127 -0
  266. package/lib/ktr/template/douyin/video-work/components/types.js +2 -0
  267. package/lib/ktr/template/douyin/video-work/index.js +10 -0
  268. package/lib/ktr/template/kuaishou/comment/components/Comment.js +88 -0
  269. package/lib/ktr/template/kuaishou/comment/components/types.js +2 -0
  270. package/lib/ktr/template/kuaishou/comment/index.js +10 -0
  271. package/lib/ktr/template/other/changelog/components/changelog.js +117 -0
  272. package/lib/ktr/template/other/changelog/components/types.js +3 -0
  273. package/lib/ktr/template/other/changelog/index.js +9 -0
  274. package/lib/ktr/template/other/handlerError/components/errorTitles.js +227 -0
  275. package/lib/ktr/template/other/handlerError/components/handlerError.js +392 -0
  276. package/lib/ktr/template/other/handlerError/components/stackHighlight.js +109 -0
  277. package/lib/ktr/template/other/handlerError/components/types.js +2 -0
  278. package/lib/ktr/template/other/handlerError/index.js +9 -0
  279. package/lib/ktr/template/other/help/components/Help.js +79 -0
  280. package/lib/ktr/template/other/help/components/types.js +3 -0
  281. package/lib/ktr/template/other/help/index.js +12 -0
  282. package/lib/ktr/template/other/live-photo-tip/components/LivePhotoTip.js +80 -0
  283. package/lib/ktr/template/other/live-photo-tip/components/types.js +3 -0
  284. package/lib/ktr/template/other/live-photo-tip/index.js +9 -0
  285. package/lib/ktr/template/other/qrlogin/components/qrlogin.js +58 -0
  286. package/lib/ktr/template/other/qrlogin/components/types.js +3 -0
  287. package/lib/ktr/template/other/qrlogin/index.js +9 -0
  288. package/lib/ktr/template/other/runtime/components/runtime.js +101 -0
  289. package/lib/ktr/template/other/runtime/components/types.js +3 -0
  290. package/lib/ktr/template/other/runtime/index.js +9 -0
  291. package/lib/ktr/template/other/version_warning/components/VersionWarning.js +38 -0
  292. package/lib/ktr/template/other/version_warning/components/types.js +3 -0
  293. package/lib/ktr/template/other/version_warning/index.js +9 -0
  294. package/lib/ktr/template/statistics/components/BoxplotGuide.js +17 -0
  295. package/lib/ktr/template/statistics/components/ChartCard.js +15 -0
  296. package/lib/ktr/template/statistics/components/KpiCard.js +14 -0
  297. package/lib/ktr/template/statistics/components/PlatformTable.js +20 -0
  298. package/lib/ktr/template/statistics/components/RankList.js +61 -0
  299. package/lib/ktr/template/statistics/components/SSRChart.js +22 -0
  300. package/lib/ktr/template/statistics/components/chartPresets.js +630 -0
  301. package/lib/ktr/template/statistics/components/chartTheme.js +226 -0
  302. package/lib/ktr/template/statistics/components/format.js +38 -0
  303. package/lib/ktr/template/statistics/components/types.js +9 -0
  304. package/lib/ktr/template/statistics/global/components/GlobalStatistics.js +246 -0
  305. package/lib/ktr/template/statistics/global/components/types.js +3 -0
  306. package/lib/ktr/template/statistics/global/index.js +9 -0
  307. package/lib/ktr/template/statistics/group/components/GroupStatistics.js +181 -0
  308. package/lib/ktr/template/statistics/group/components/types.js +3 -0
  309. package/lib/ktr/template/statistics/group/index.js +9 -0
  310. package/lib/ktr/template/types/ctx.js +2 -0
  311. package/lib/ktr/template/xiaohongshu/comment/components/Comment.js +99 -0
  312. package/lib/ktr/template/xiaohongshu/comment/components/types.js +3 -0
  313. package/lib/ktr/template/xiaohongshu/comment/index.js +9 -0
  314. package/lib/ktr/template/xiaohongshu/noteInfo/components/noteInfo.js +93 -0
  315. package/lib/ktr/template/xiaohongshu/noteInfo/components/types.js +2 -0
  316. package/lib/ktr/template/xiaohongshu/noteInfo/index.js +9 -0
  317. package/lib/ktr/utils/QRCodeAvatar.js +99 -0
  318. package/lib/ktr/utils/QRcode.js +56 -0
  319. package/lib/ktr/utils/avatarProxy.js +45 -0
  320. package/lib/ktr/utils/cn.js +11 -0
  321. package/lib/ktr/utils/theme.js +12 -0
  322. package/lib/qqFields.json +75 -0
  323. package/lib/qqOptions.js +44 -0
  324. package/lib/richtext/index.js +25 -0
  325. package/lib/richtext/parse/index.js +232 -0
  326. package/lib/richtext/react/cn.js +11 -0
  327. package/lib/richtext/react/index.js +221 -0
  328. package/lib/richtext/types.js +2 -0
  329. package/lib/schema.js +352 -0
  330. package/lib/webui.js +341 -0
  331. package/package.json +88 -0
  332. package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_BV.svg +1 -0
  333. package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_GOODS.svg +1 -0
  334. package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_LOTTERY.svg +1 -0
  335. package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_VIEW_PICTURE.svg +1 -0
  336. package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_VOTE.svg +1 -0
  337. package/resources/image/bilibili/RICH_TEXT_NODE_TYPE/RICH_TEXT_NODE_TYPE_WEB.svg +1 -0
  338. package/resources/image/bilibili/bilibili-dark.png +0 -0
  339. package/resources/image/bilibili/bilibili-light.png +0 -0
  340. package/resources/image/bilibili/bilibili.png +0 -0
  341. package/resources/image/bilibili/id-dark.svg +1 -0
  342. package/resources/image/bilibili/id-light.svg +1 -0
  343. package/resources/image/bilibili/level/lv0.svg +1 -0
  344. package/resources/image/bilibili/level/lv1.svg +1 -0
  345. package/resources/image/bilibili/level/lv2.svg +1 -0
  346. package/resources/image/bilibili/level/lv3.svg +1 -0
  347. package/resources/image/bilibili/level/lv4.svg +1 -0
  348. package/resources/image/bilibili/level/lv5.svg +1 -0
  349. package/resources/image/bilibili/level/lv6.svg +1 -0
  350. package/resources/image/bilibili/level/lv7.svg +1 -0
  351. package/resources/image/bilibili/like-dark.svg +6 -0
  352. package/resources/image/bilibili/like-light.svg +6 -0
  353. package/resources/image/bilibili/play.svg +28 -0
  354. package/resources/image/bilibili/res-local1.png +0 -0
  355. package/resources/image/bilibili/up_pb.svg +1 -0
  356. package/resources/image/bilibili//347/233/264/346/222/255/344/270/255.png +0 -0
  357. package/resources/image/douyin/douyin.png +0 -0
  358. package/resources/image/douyin/dylogo-dark.svg +23 -0
  359. package/resources/image/douyin/dylogo-light.svg +38 -0
  360. package/resources/image/douyin/search-dark.svg +1 -0
  361. package/resources/image/douyin/search-light.svg +3 -0
  362. package/resources/image/douyin//346/212/226/351/237/263-/347/233/264/346/222/255/344/270/255.png +0 -0
  363. package/resources/image/frame-logo.png +0 -0
  364. package/resources/image/kuaishou/logo.png +0 -0
  365. package/resources/image/other/handlerError/Milky.png +0 -0
  366. package/resources/image/other/handlerError/chronocat.svg +43 -0
  367. package/resources/image/other/handlerError/conwechat.webp +0 -0
  368. package/resources/image/other/handlerError/gocq-http.webp +0 -0
  369. package/resources/image/other/handlerError/lagrange.webp +0 -0
  370. package/resources/image/other/handlerError/llonebot.webp +0 -0
  371. package/resources/image/other/handlerError/napcat.webp +0 -0
  372. package/resources/image/other/handlerError/onebot.png +0 -0
  373. package/resources/image/other/handlerError/satori.png +0 -0
  374. package/resources/image/xiaohongshu/logo.png +0 -0
  375. package/resources/template/style.css +23708 -0
  376. package/resources/vite.svg +130 -0
  377. package/scripts/build.mjs +129 -0
  378. package/scripts/check-schema.cjs +55 -0
  379. package/scripts/deploy.mjs +78 -0
  380. package/scripts/make-demo-placeholders.mjs +112 -0
  381. package/scripts/patch-deps.mjs +46 -0
  382. package/scripts/patch-webui.mjs +112 -0
  383. package/scripts/smoke-commands.cjs +39 -0
  384. package/scripts/smoke-config.cjs +122 -0
  385. package/scripts/smoke-douyin.cjs +170 -0
  386. package/scripts/smoke-koishi-commands.cjs +210 -0
  387. package/scripts/smoke-login.cjs +110 -0
  388. package/scripts/smoke-parse.cjs +54 -0
  389. package/scripts/smoke-push-mock.cjs +114 -0
  390. package/scripts/smoke-push.cjs +104 -0
  391. package/scripts/smoke-qq.cjs +147 -0
  392. package/scripts/smoke-qqcard.cjs +108 -0
  393. package/scripts/smoke-qqpanel.cjs +205 -0
  394. package/scripts/smoke-render.cjs +106 -0
  395. package/scripts/smoke-server.cjs +94 -0
  396. package/src/compat/axios.ts +5 -0
  397. package/src/compat/cron.ts +86 -0
  398. package/src/compat/esm.ts +13 -0
  399. package/src/compat/express.ts +5 -0
  400. package/src/compat/heroui.tsx +53 -0
  401. package/src/compat/lodash.ts +32 -0
  402. package/src/compat/logger.ts +121 -0
  403. package/src/compat/node-karin.ts +1064 -0
  404. package/src/compat/phosphor-icons.tsx +25 -0
  405. package/src/compat/qqcard.ts +251 -0
  406. package/src/compat/root.ts +44 -0
  407. package/src/compat/runtime.ts +120 -0
  408. package/src/compat/segment.ts +161 -0
  409. package/src/compat/sqlite3.ts +123 -0
  410. package/src/compat/start.ts +3 -0
  411. package/src/compat/syncConfig.ts +64 -0
  412. package/src/compat/template-react.ts +54 -0
  413. package/src/compat/text.ts +58 -0
  414. package/src/compat/yaml.ts +6 -0
  415. package/src/configBridge.ts +94 -0
  416. package/src/index.ts +769 -0
  417. package/src/karin/app.ts +1 -0
  418. package/src/karin/apps/__ready.ts +14 -0
  419. package/src/karin/apps/admin.ts +127 -0
  420. package/src/karin/apps/help.ts +228 -0
  421. package/src/karin/apps/push.ts +327 -0
  422. package/src/karin/apps/qrlogin.ts +160 -0
  423. package/src/karin/apps/statistics.ts +203 -0
  424. package/src/karin/apps/testPush.ts +226 -0
  425. package/src/karin/apps/tools.ts +510 -0
  426. package/src/karin/apps/update.ts +239 -0
  427. package/src/karin/index.ts +9 -0
  428. package/src/karin/module/db/bilibili.ts +1089 -0
  429. package/src/karin/module/db/douyin.ts +1218 -0
  430. package/src/karin/module/db/index.ts +159 -0
  431. package/src/karin/module/db/migration.ts +249 -0
  432. package/src/karin/module/db/statistics.ts +817 -0
  433. package/src/karin/module/index.ts +37 -0
  434. package/src/karin/module/server/constants/routes.ts +53 -0
  435. package/src/karin/module/server/controllers/bots.ts +289 -0
  436. package/src/karin/module/server/controllers/config.ts +85 -0
  437. package/src/karin/module/server/controllers/video.ts +212 -0
  438. package/src/karin/module/server/index.ts +11 -0
  439. package/src/karin/module/server/middlewares/auth.ts +147 -0
  440. package/src/karin/module/server/routes/amagi.ts +57 -0
  441. package/src/karin/module/server/routes/api.ts +42 -0
  442. package/src/karin/module/server/routes/index.ts +68 -0
  443. package/src/karin/module/server/routes/ssr.ts +14 -0
  444. package/src/karin/module/server/routes/static.ts +43 -0
  445. package/src/karin/module/utils/Base.ts +497 -0
  446. package/src/karin/module/utils/CardParser.ts +503 -0
  447. package/src/karin/module/utils/Common.ts +448 -0
  448. package/src/karin/module/utils/Config.ts +443 -0
  449. package/src/karin/module/utils/EmojiReaction.ts +268 -0
  450. package/src/karin/module/utils/ErrorHandler/handler.ts +124 -0
  451. package/src/karin/module/utils/ErrorHandler/index.ts +48 -0
  452. package/src/karin/module/utils/ErrorHandler/render.ts +180 -0
  453. package/src/karin/module/utils/ErrorHandler/sender.ts +192 -0
  454. package/src/karin/module/utils/ErrorHandler/strategy.ts +41 -0
  455. package/src/karin/module/utils/ErrorHandler/types.ts +122 -0
  456. package/src/karin/module/utils/ErrorHandler/utils.ts +108 -0
  457. package/src/karin/module/utils/FFmpeg.ts +357 -0
  458. package/src/karin/module/utils/ImageHelper.ts +90 -0
  459. package/src/karin/module/utils/ImageSlice.ts +383 -0
  460. package/src/karin/module/utils/MotionPhoto.ts +297 -0
  461. package/src/karin/module/utils/Network/Downloader.ts +481 -0
  462. package/src/karin/module/utils/Network/ImageDownloader.ts +291 -0
  463. package/src/karin/module/utils/Network/Network.ts +344 -0
  464. package/src/karin/module/utils/Network/ThrottleStream.ts +76 -0
  465. package/src/karin/module/utils/Network/constants.ts +135 -0
  466. package/src/karin/module/utils/Network/helpers.ts +183 -0
  467. package/src/karin/module/utils/Network/index.ts +28 -0
  468. package/src/karin/module/utils/Network/types.ts +46 -0
  469. package/src/karin/module/utils/Networks.ts +20 -0
  470. package/src/karin/module/utils/ParseLock.ts +33 -0
  471. package/src/karin/module/utils/ParseOverride.ts +128 -0
  472. package/src/karin/module/utils/QqCardResolve.ts +116 -0
  473. package/src/karin/module/utils/QqPanel.ts +880 -0
  474. package/src/karin/module/utils/Render/coverTheme.ts +294 -0
  475. package/src/karin/module/utils/Render/index.ts +574 -0
  476. package/src/karin/module/utils/amagiClient.ts +368 -0
  477. package/src/karin/module/utils/avatar.ts +220 -0
  478. package/src/karin/module/utils/bot.ts +115 -0
  479. package/src/karin/module/utils/build-metadata.ts +60 -0
  480. package/src/karin/module/utils/changelog.ts +175 -0
  481. package/src/karin/module/utils/index.ts +10 -0
  482. package/src/karin/module/utils/loginSession.ts +291 -0
  483. package/src/karin/module/utils/parseTip.ts +59 -0
  484. package/src/karin/module/utils/runtime-report.ts +142 -0
  485. package/src/karin/module/utils/semver.ts +60 -0
  486. package/src/karin/module/utils/statisticsAggregate.ts +507 -0
  487. package/src/karin/platform/bilibili/bilibili.ts +2004 -0
  488. package/src/karin/platform/bilibili/comments.ts +397 -0
  489. package/src/karin/platform/bilibili/danmaku.ts +653 -0
  490. package/src/karin/platform/bilibili/dynamic-text.ts +926 -0
  491. package/src/karin/platform/bilibili/genParams.ts +30 -0
  492. package/src/karin/platform/bilibili/getID.ts +163 -0
  493. package/src/karin/platform/bilibili/index.ts +8 -0
  494. package/src/karin/platform/bilibili/live-status.ts +36 -0
  495. package/src/karin/platform/bilibili/login.ts +130 -0
  496. package/src/karin/platform/bilibili/push.ts +1615 -0
  497. package/src/karin/platform/bilibili/riskControl.ts +114 -0
  498. package/src/karin/platform/bilibili/search.ts +305 -0
  499. package/src/karin/platform/common/danmaku-types.ts +18 -0
  500. package/src/karin/platform/douyin/comments.ts +404 -0
  501. package/src/karin/platform/douyin/danmaku.ts +1187 -0
  502. package/src/karin/platform/douyin/douyin.ts +1244 -0
  503. package/src/karin/platform/douyin/getID.ts +101 -0
  504. package/src/karin/platform/douyin/index.ts +8 -0
  505. package/src/karin/platform/douyin/login.ts +251 -0
  506. package/src/karin/platform/douyin/push/favorite.ts +97 -0
  507. package/src/karin/platform/douyin/push/index.ts +5 -0
  508. package/src/karin/platform/douyin/push/live.ts +86 -0
  509. package/src/karin/platform/douyin/push/post.ts +75 -0
  510. package/src/karin/platform/douyin/push/recommend.ts +96 -0
  511. package/src/karin/platform/douyin/push/render.ts +768 -0
  512. package/src/karin/platform/douyin/push/types.ts +41 -0
  513. package/src/karin/platform/douyin/push.ts +1241 -0
  514. package/src/karin/platform/douyin/types.ts +151 -0
  515. package/src/karin/platform/douyin/videoQuality.ts +234 -0
  516. package/src/karin/platform/douyin/workType.ts +238 -0
  517. package/src/karin/platform/index.ts +4 -0
  518. package/src/karin/platform/kuaishou/comments.ts +114 -0
  519. package/src/karin/platform/kuaishou/getID.ts +48 -0
  520. package/src/karin/platform/kuaishou/getdata.ts +78 -0
  521. package/src/karin/platform/kuaishou/index.ts +4 -0
  522. package/src/karin/platform/kuaishou/kuaishou.ts +130 -0
  523. package/src/karin/platform/xiaohongshu/comments.ts +227 -0
  524. package/src/karin/platform/xiaohongshu/getID.ts +127 -0
  525. package/src/karin/platform/xiaohongshu/index.ts +2 -0
  526. package/src/karin/platform/xiaohongshu/xiaohongshu.ts +733 -0
  527. package/src/karin/root.ts +78 -0
  528. package/src/karin/setup.ts +77 -0
  529. package/src/karin/types/config/amagi.ts +46 -0
  530. package/src/karin/types/config/app.ts +129 -0
  531. package/src/karin/types/config/bilibili.ts +121 -0
  532. package/src/karin/types/config/cookies.ts +11 -0
  533. package/src/karin/types/config/douyin.ts +93 -0
  534. package/src/karin/types/config/index.ts +41 -0
  535. package/src/karin/types/config/kuaishou.ts +10 -0
  536. package/src/karin/types/config/pushlist.ts +55 -0
  537. package/src/karin/types/config/request.ts +27 -0
  538. package/src/karin/types/config/upload.ts +49 -0
  539. package/src/karin/types/config/xiaohongshu.ts +24 -0
  540. package/src/karin/types/datatypes.ts +42 -0
  541. package/src/karin/types/index.ts +4 -0
  542. package/src/karin/types/netrorks/NetworksConfigType.ts +61 -0
  543. package/src/karin/types/netrorks/index.ts +1 -0
  544. package/src/karin/web.config.ts +48 -0
  545. package/src/ktr/registry.ts +55 -0
  546. package/src/ktr/template/_preview/components/PreviewInfoCard.tsx +33 -0
  547. package/src/ktr/template/_preview/components/PreviewLayout.tsx +65 -0
  548. package/src/ktr/template/_preview/components/PreviewVideoCard.tsx +22 -0
  549. package/src/ktr/template/_preview/render.ts +158 -0
  550. package/src/ktr/template/_preview/types.ts +15 -0
  551. package/src/ktr/template/_preview/utils/status.ts +29 -0
  552. package/src/ktr/template/_preview/utils/time.ts +8 -0
  553. package/src/ktr/template/bilibili/bangumi/components/bangumi.tsx +462 -0
  554. package/src/ktr/template/bilibili/bangumi/components/types.ts +36 -0
  555. package/src/ktr/template/bilibili/bangumi/index.tsx +9 -0
  556. package/src/ktr/template/bilibili/comment/components/Comment.tsx +657 -0
  557. package/src/ktr/template/bilibili/comment/components/types.ts +26 -0
  558. package/src/ktr/template/bilibili/comment/index.tsx +11 -0
  559. package/src/ktr/template/bilibili/components/Icons.tsx +209 -0
  560. package/src/ktr/template/bilibili/components/shared.tsx +162 -0
  561. package/src/ktr/template/bilibili/components/types.ts +388 -0
  562. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/DYNAMIC_TYPE_ARTICLE.tsx +251 -0
  563. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/components/types.ts +67 -0
  564. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_ARTICLE/index.tsx +12 -0
  565. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/DYNAMIC_TYPE_AV.tsx +238 -0
  566. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/components/types.ts +39 -0
  567. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_AV/index.tsx +12 -0
  568. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/DYNAMIC_TYPE_DRAW.tsx +205 -0
  569. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/components/types.ts +20 -0
  570. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_DRAW/index.tsx +11 -0
  571. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/DYNAMIC_TYPE_FORWARD.tsx +348 -0
  572. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/components/types.ts +16 -0
  573. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_FORWARD/index.tsx +12 -0
  574. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/DYNAMIC_TYPE_LIVE_RCMD.tsx +264 -0
  575. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/components/types.ts +32 -0
  576. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD/index.tsx +12 -0
  577. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/DYNAMIC_TYPE_WORD.tsx +68 -0
  578. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/components/types.ts +14 -0
  579. package/src/ktr/template/bilibili/dynamic/DYNAMIC_TYPE_WORD/index.tsx +12 -0
  580. package/src/ktr/template/bilibili/dynamic/components/AdditionalCard.tsx +165 -0
  581. package/src/ktr/template/bilibili/dynamic/components/CommonComponents.tsx +142 -0
  582. package/src/ktr/template/bilibili/dynamic/types.ts +382 -0
  583. package/src/ktr/template/bilibili/qrcodeImg/components/qrcodeImg.tsx +179 -0
  584. package/src/ktr/template/bilibili/qrcodeImg/components/types.ts +9 -0
  585. package/src/ktr/template/bilibili/qrcodeImg/index.tsx +12 -0
  586. package/src/ktr/template/bilibili/userlist/components/UserList.tsx +246 -0
  587. package/src/ktr/template/bilibili/userlist/components/types.ts +35 -0
  588. package/src/ktr/template/bilibili/userlist/index.tsx +11 -0
  589. package/src/ktr/template/bilibili/videoInfo/components/types.ts +28 -0
  590. package/src/ktr/template/bilibili/videoInfo/components/videoInfo.tsx +217 -0
  591. package/src/ktr/template/bilibili/videoInfo/index.tsx +12 -0
  592. package/src/ktr/template/components/AmbientCover.tsx +50 -0
  593. package/src/ktr/template/components/DefaultLayout.tsx +166 -0
  594. package/src/ktr/template/components/GlowImage.tsx +572 -0
  595. package/src/ktr/template/components/QRCodeWithAvatar.tsx +52 -0
  596. package/src/ktr/template/components/RolldownLogo.tsx +110 -0
  597. package/src/ktr/template/components/ViteLogo.tsx +134 -0
  598. package/src/ktr/template/components/iconRegistry.ts +294 -0
  599. package/src/ktr/template/douyin/article-work/components/ArticleWork.tsx +301 -0
  600. package/src/ktr/template/douyin/article-work/components/types.ts +53 -0
  601. package/src/ktr/template/douyin/article-work/index.tsx +12 -0
  602. package/src/ktr/template/douyin/comment/components/Comment.tsx +612 -0
  603. package/src/ktr/template/douyin/comment/components/types.ts +85 -0
  604. package/src/ktr/template/douyin/comment/index.tsx +11 -0
  605. package/src/ktr/template/douyin/components/Icons.tsx +75 -0
  606. package/src/ktr/template/douyin/components/types.ts +83 -0
  607. package/src/ktr/template/douyin/dynamic/components/Dynamic.tsx +283 -0
  608. package/src/ktr/template/douyin/dynamic/components/types.ts +46 -0
  609. package/src/ktr/template/douyin/dynamic/index.tsx +11 -0
  610. package/src/ktr/template/douyin/favorite-list/components/FavoriteList.tsx +201 -0
  611. package/src/ktr/template/douyin/favorite-list/components/types.ts +38 -0
  612. package/src/ktr/template/douyin/favorite-list/index.tsx +12 -0
  613. package/src/ktr/template/douyin/image-work/components/ImageWork.tsx +437 -0
  614. package/src/ktr/template/douyin/image-work/components/types.ts +78 -0
  615. package/src/ktr/template/douyin/image-work/index.tsx +11 -0
  616. package/src/ktr/template/douyin/live/components/Live.tsx +229 -0
  617. package/src/ktr/template/douyin/live/components/types.ts +47 -0
  618. package/src/ktr/template/douyin/live/index.tsx +11 -0
  619. package/src/ktr/template/douyin/musicinfo/components/MusicInfo.tsx +187 -0
  620. package/src/ktr/template/douyin/musicinfo/components/types.ts +31 -0
  621. package/src/ktr/template/douyin/musicinfo/index.tsx +11 -0
  622. package/src/ktr/template/douyin/qrcodeImg/components/qrcodeImg.tsx +170 -0
  623. package/src/ktr/template/douyin/qrcodeImg/components/types.ts +9 -0
  624. package/src/ktr/template/douyin/qrcodeImg/index.tsx +9 -0
  625. package/src/ktr/template/douyin/recommend-list/components/RecommendList.tsx +201 -0
  626. package/src/ktr/template/douyin/recommend-list/components/types.ts +37 -0
  627. package/src/ktr/template/douyin/recommend-list/index.tsx +12 -0
  628. package/src/ktr/template/douyin/user_profile/components/UserVideoList.tsx +225 -0
  629. package/src/ktr/template/douyin/user_profile/components/types.ts +71 -0
  630. package/src/ktr/template/douyin/user_profile/index.tsx +9 -0
  631. package/src/ktr/template/douyin/userlist/components/UserList.tsx +229 -0
  632. package/src/ktr/template/douyin/userlist/components/types.ts +35 -0
  633. package/src/ktr/template/douyin/userlist/index.tsx +11 -0
  634. package/src/ktr/template/douyin/video-work/components/VideoWork.tsx +398 -0
  635. package/src/ktr/template/douyin/video-work/components/types.ts +80 -0
  636. package/src/ktr/template/douyin/video-work/index.tsx +11 -0
  637. package/src/ktr/template/font.css +6071 -0
  638. package/src/ktr/template/global.d.ts +69 -0
  639. package/src/ktr/template/kuaishou/comment/components/Comment.tsx +207 -0
  640. package/src/ktr/template/kuaishou/comment/components/types.ts +47 -0
  641. package/src/ktr/template/kuaishou/comment/index.tsx +11 -0
  642. package/src/ktr/template/other/changelog/components/changelog.tsx +411 -0
  643. package/src/ktr/template/other/changelog/components/types.ts +21 -0
  644. package/src/ktr/template/other/changelog/index.tsx +9 -0
  645. package/src/ktr/template/other/handlerError/components/errorTitles.ts +224 -0
  646. package/src/ktr/template/other/handlerError/components/handlerError.tsx +1076 -0
  647. package/src/ktr/template/other/handlerError/components/stackHighlight.ts +127 -0
  648. package/src/ktr/template/other/handlerError/components/types.ts +124 -0
  649. package/src/ktr/template/other/handlerError/index.tsx +9 -0
  650. package/src/ktr/template/other/help/components/Help.tsx +249 -0
  651. package/src/ktr/template/other/help/components/types.ts +47 -0
  652. package/src/ktr/template/other/help/index.tsx +9 -0
  653. package/src/ktr/template/other/live-photo-tip/components/LivePhotoTip.tsx +295 -0
  654. package/src/ktr/template/other/live-photo-tip/components/types.ts +8 -0
  655. package/src/ktr/template/other/live-photo-tip/index.tsx +9 -0
  656. package/src/ktr/template/other/qrlogin/components/qrlogin.tsx +295 -0
  657. package/src/ktr/template/other/qrlogin/components/types.ts +11 -0
  658. package/src/ktr/template/other/qrlogin/index.tsx +9 -0
  659. package/src/ktr/template/other/runtime/components/runtime.tsx +347 -0
  660. package/src/ktr/template/other/runtime/components/types.ts +75 -0
  661. package/src/ktr/template/other/runtime/index.tsx +9 -0
  662. package/src/ktr/template/other/version_warning/components/VersionWarning.tsx +392 -0
  663. package/src/ktr/template/other/version_warning/components/types.ts +8 -0
  664. package/src/ktr/template/other/version_warning/index.tsx +9 -0
  665. package/src/ktr/template/statistics/components/BoxplotGuide.tsx +57 -0
  666. package/src/ktr/template/statistics/components/ChartCard.tsx +40 -0
  667. package/src/ktr/template/statistics/components/KpiCard.tsx +39 -0
  668. package/src/ktr/template/statistics/components/PlatformTable.tsx +83 -0
  669. package/src/ktr/template/statistics/components/RankList.tsx +127 -0
  670. package/src/ktr/template/statistics/components/SSRChart.tsx +41 -0
  671. package/src/ktr/template/statistics/components/chartPresets.ts +700 -0
  672. package/src/ktr/template/statistics/components/chartTheme.ts +234 -0
  673. package/src/ktr/template/statistics/components/format.ts +33 -0
  674. package/src/ktr/template/statistics/components/types.ts +92 -0
  675. package/src/ktr/template/statistics/global/components/GlobalStatistics.tsx +597 -0
  676. package/src/ktr/template/statistics/global/components/types.ts +138 -0
  677. package/src/ktr/template/statistics/global/index.tsx +9 -0
  678. package/src/ktr/template/statistics/group/components/GroupStatistics.tsx +468 -0
  679. package/src/ktr/template/statistics/group/components/types.ts +113 -0
  680. package/src/ktr/template/statistics/group/index.tsx +9 -0
  681. package/src/ktr/template/style.css +65 -0
  682. package/src/ktr/template/types/ctx.ts +42 -0
  683. package/src/ktr/template/xiaohongshu/comment/components/Comment.tsx +264 -0
  684. package/src/ktr/template/xiaohongshu/comment/components/types.ts +119 -0
  685. package/src/ktr/template/xiaohongshu/comment/index.tsx +9 -0
  686. package/src/ktr/template/xiaohongshu/noteInfo/components/noteInfo.tsx +271 -0
  687. package/src/ktr/template/xiaohongshu/noteInfo/components/types.ts +66 -0
  688. package/src/ktr/template/xiaohongshu/noteInfo/index.tsx +9 -0
  689. package/src/ktr/utils/QRCodeAvatar.ts +102 -0
  690. package/src/ktr/utils/QRcode.ts +65 -0
  691. package/src/ktr/utils/avatarProxy.ts +42 -0
  692. package/src/ktr/utils/cn.ts +6 -0
  693. package/src/ktr/utils/theme.ts +10 -0
  694. package/src/qqFields.json +75 -0
  695. package/src/qqOptions.ts +64 -0
  696. package/src/richtext/index.ts +9 -0
  697. package/src/richtext/parse/index.ts +271 -0
  698. package/src/richtext/react/cn.ts +6 -0
  699. package/src/richtext/react/index.tsx +582 -0
  700. package/src/richtext/types.ts +320 -0
  701. package/src/schema.ts +372 -0
  702. package/src/webui.ts +352 -0
@@ -0,0 +1,1805 @@
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.extractArticleImages = exports.getvideosize = exports.bilibiliProcessVideos = exports.parseAdditionalCard = exports.generateDecorationCard = exports.cover = exports.TimeFormatter = exports.Bilibili = void 0;
40
+ const node_fs_1 = __importDefault(require("node:fs"));
41
+ const QqPanel_1 = require("../../module/utils/QqPanel");
42
+ const ImageSlice_1 = require("../../module/utils/ImageSlice");
43
+ const amagi_1 = require("@ikenxuan/amagi");
44
+ const date_fns_1 = require("date-fns");
45
+ const locale_1 = require("date-fns/locale");
46
+ const node_karin_1 = __importStar(require("../../../compat/node-karin.js"));
47
+ // 番剧在 QQ 上用「卡片 + 分集表格」面板(见 sendBangumiPanel 的说明)
48
+ const QqPanel_2 = require("../../module/utils/QqPanel");
49
+ const utils_1 = require("../../module/utils");
50
+ const amagiClient_1 = require("../../module/utils/amagiClient");
51
+ const Config_1 = require("../../module/utils/Config");
52
+ const ParseOverride_1 = require("../../module/utils/ParseOverride");
53
+ const bilibili_1 = require("../../platform/bilibili");
54
+ const danmaku_1 = require("../../platform/bilibili/danmaku");
55
+ const dynamic_text_1 = require("../../platform/bilibili/dynamic-text");
56
+ let img;
57
+ /** 评论请求统一使用匿名态,避免账号 Cookie 改变评论热度池结果。 */
58
+ const bilibiliAnonymousRequestConfig = {
59
+ headers: {
60
+ Cookie: ''
61
+ }
62
+ };
63
+ /**
64
+ * 链接解析出的类型 → 统计用的内容形态。
65
+ * B站这边类型在构造时就定了(`data.type`),不像抖音要等作品详情回来才知道,
66
+ * 所以直接查表,命中不了说明不是可解析的类型,留空让调用方跳过计数。
67
+ */
68
+ const BILIBILI_WORK_TYPES = {
69
+ one_video: 'video',
70
+ bangumi_video_info: 'bangumi',
71
+ dynamic_info: 'dynamic',
72
+ live_room_detail: 'live'
73
+ };
74
+ class Bilibili extends utils_1.Base {
75
+ e;
76
+ type;
77
+ STATUS;
78
+ isVIP;
79
+ Type;
80
+ islogin;
81
+ downloadfilename;
82
+ /** 强制烧录弹幕(用于 #弹幕解析 命令) */
83
+ forceBurnDanmaku;
84
+ /** 本次解析的内容形态,供统计埋点读取 */
85
+ workType;
86
+ get botadapter() {
87
+ return this.e.bot?.adapter?.name;
88
+ }
89
+ constructor(e, data, options) {
90
+ super(e);
91
+ this.e = e;
92
+ this.isVIP = false;
93
+ this.Type = data?.type;
94
+ this.workType = BILIBILI_WORK_TYPES[data?.type];
95
+ this.islogin = data?.USER?.STATUS === 'isLogin';
96
+ this.downloadfilename = '';
97
+ this.forceBurnDanmaku = options?.forceBurnDanmaku ?? false;
98
+ this.headers.Referer = 'https://www.bilibili.com/';
99
+ this.headers.Cookie = Config_1.Config.amagi.cookies.bilibili;
100
+ }
101
+ async BilibiliHandler(iddata) {
102
+ // 面板点进来的:卡片已经在面板里发过了,这里只回一句「收到请求」就开始下载
103
+ const fromPanel = (0, ParseOverride_1.getParseOverride)()?.fromPanel === true;
104
+ if (fromPanel) {
105
+ // 面板点进来的:只回一句「收到请求,开始下载」,并挂一个只查本次任务的进度按钮
106
+ // replyReplacing 会先撤掉上一条(也就是刚点的画质面板),群里只留这句提示
107
+ await (0, QqPanel_2.replyReplacing)(this.e, (0, QqPanel_2.buildDownloadTip)(String(iddata.bvid ?? ''), '收到请求,开始下载'));
108
+ }
109
+ else if (Config_1.Config.app.parseTip) {
110
+ // 同样:发这句话时把上一条机器人消息撤掉
111
+ await (0, QqPanel_2.replyReplacing)(this.e, '检测到B站链接,开始解析');
112
+ }
113
+ switch (this.Type) {
114
+ case 'one_video': {
115
+ const infoData = await this.amagi.bilibili.fetcher.fetchVideoInfo({ bvid: iddata.bvid });
116
+ const playUrlData = await this.amagi.bilibili.fetcher.fetchVideoStreamUrl({
117
+ avid: infoData.data.data.aid,
118
+ cid: iddata.p ? (infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid) : infoData.data.data.cid
119
+ });
120
+ // const playUrl = bilibiliApiUrls.视频流信息({ avid: infoData.data.aid, cid: infoData.data.cid })
121
+ /**
122
+ * 免登录(直链 durl)分支的判断。
123
+ *
124
+ * 上游只看「画质 < 64 就当免登录」——**登录状态下选 360P/480P 会被误判**,
125
+ * 于是走了形状完全不同的直链分支,一取就崩
126
+ * (日志里的 Cannot read properties of undefined (reading '0') 就是它)。
127
+ *
128
+ * 现在:先看 Cookie 到底有没有登录;没登录时才按低画质走直链;接口没给 dash 时也走直链。
129
+ */
130
+ const loginStatus = (await (0, bilibili_1.checkCk)()).Status === 'isLogin';
131
+ this.islogin = loginStatus;
132
+ /** 有没有 dash:无 CK 部署只会下发 durl,这时无论如何都得走直链分支 */
133
+ const hasDash = !!playUrlData.data?.data?.dash;
134
+ /** 低画质(< 64):**只在没登录时**才因此走直链;登录了就用 dash 正常取流 */
135
+ const wantLowQuality = Config_1.Config.bilibili.videoQuality !== 0 && Config_1.Config.bilibili.videoQuality < 64;
136
+ const useAnonymousQuality = loginStatus ? !hasDash : (!hasDash || wantLowQuality);
137
+ this.downloadfilename = infoData.data.data.title.substring(0, 50).replace(/[\\/:*?"<>|\r\n\s]/g, ' ');
138
+ /**
139
+ * 免登录 360P 那条不走 amagi(URL 上多一个 `platform=html5`),拿到的也不是登录态那种
140
+ * `dash` 形状,所以这里仍用手写快照树的 `BiliBiliVideoPlayurlNoLogin` —— 它不是任何端点的响应。
141
+ */
142
+ const nockData = (await new utils_1.Networks({
143
+ url: amagi_1.bilibiliApiUrls.getVideoStream({
144
+ avid: infoData.data.data.aid,
145
+ cid: iddata.p ? (infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid) : infoData.data.data.cid
146
+ }) + '&platform=html5',
147
+ headers: this.headers
148
+ }).getData());
149
+ // 如果配置项不存在或长度为0,则不显示任何内容
150
+ // fromPanel:面板里已经发过这张卡片了,别再发一遍
151
+ if (!fromPanel && Config_1.Config.bilibili.sendContent.some((content) => content === 'info')) {
152
+ if (Config_1.Config.bilibili.videoInfoMode === 'text') {
153
+ // 构建回复内容数组
154
+ const replyContent = [];
155
+ const { coin, like, share, view, favorite, danmaku } = infoData.data.data.stat;
156
+ const coverUrl = await (0, utils_1.processImageUrl)(infoData.data.data.pic, infoData.data.data.title);
157
+ const contentMap = {
158
+ cover: node_karin_1.segment.image(coverUrl),
159
+ title: node_karin_1.segment.text(`\n📺 标题: ${infoData.data.data.title}\n`),
160
+ author: node_karin_1.segment.text(`\n👤 作者: ${infoData.data.data.owner.name}\n`),
161
+ stats: node_karin_1.segment.text(formatVideoStats(view, danmaku, like, coin, share, favorite)),
162
+ desc: node_karin_1.segment.text(`\n\n📝 简介: ${infoData.data.data.desc}`)
163
+ };
164
+ // 重新排序
165
+ const fixedOrder = ['cover', 'title', 'author', 'stats', 'desc'];
166
+ fixedOrder.forEach((item) => {
167
+ if (Config_1.Config.bilibili.displayContent.includes(item) && contentMap[item]) {
168
+ replyContent.push(contentMap[item]);
169
+ }
170
+ });
171
+ if (replyContent.length > 0) {
172
+ this.e.reply(replyContent);
173
+ }
174
+ }
175
+ else {
176
+ // 渲染为图片
177
+ const userProfileData = await this.amagi.bilibili.fetcher.fetchUserCard({
178
+ host_mid: infoData.data.data.owner.mid
179
+ });
180
+ // 获取弹幕并统计出现次数最多的几条,用于模板展示(仅当配置开启时)
181
+ let hotDanmaku;
182
+ if (Config_1.Config.bilibili.showDanmakuInVideoInfo) {
183
+ const danmakuCid = iddata.p ? (infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid) : infoData.data.data.cid;
184
+ const danmakuDuration = iddata.p
185
+ ? (infoData.data.data.pages[iddata.p - 1]?.duration ?? infoData.data.data.duration)
186
+ : infoData.data.data.duration;
187
+ const infoDanmakuList = await this.fetchVideoDanmakuList(danmakuCid, danmakuDuration);
188
+ hotDanmaku = (0, danmaku_1.getHotDanmaku)(infoDanmakuList, 20);
189
+ }
190
+ const img = await (0, utils_1.Render)(this.e, 'bilibili/videoInfo', {
191
+ share_url: 'https://b23.tv/' + infoData.data.data.bvid,
192
+ title: infoData.data.data.title,
193
+ desc: infoData.data.data.desc_v2?.length
194
+ ? (0, dynamic_text_1.buildBilibiliVideoDescRichText)(infoData.data.data.desc_v2)
195
+ : (0, dynamic_text_1.buildBilibiliDynamicRichText)(infoData.data.data.desc || '', []),
196
+ stat: infoData.data.data.stat,
197
+ bvid: infoData.data.data.bvid,
198
+ ctime: infoData.data.data.ctime,
199
+ pic: infoData.data.data.pic,
200
+ hotDanmaku,
201
+ owner: {
202
+ ...infoData.data.data.owner,
203
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
204
+ frame: userProfileData.data.data.card.pendant?.image || ''
205
+ }
206
+ });
207
+ this.e.reply(img);
208
+ }
209
+ }
210
+ let videoSize = '';
211
+ let correctList;
212
+ /**
213
+ * 选流:**已登录时一律按画质筛选**。
214
+ *
215
+ * 上游这里判断的是「画质 > 64 或自动」,于是请求低画质(360P/480P)时会掉进 else 分支 ——
216
+ * 那边不筛流,直接用 dash.video[0],而 B站返回的列表是**按画质从高到低**的,
217
+ * 结果就是要 360P 却下了个 4K:文件巨大、手机还解不出来,表现就是「只有声音没有画面」。
218
+ */
219
+ if (this.islogin && !useAnonymousQuality) {
220
+ /** 提取出视频流信息对象,并排除清晰度重复的视频流 */
221
+ const simplify = playUrlData.data.data.dash.video.filter((item, index, self) => {
222
+ return (self.findIndex((t) => {
223
+ return t.id === item.id;
224
+ }) === index);
225
+ });
226
+ /** 替换原始的视频信息对象 */
227
+ playUrlData.data.data.dash.video = simplify;
228
+ /** 没有音频流(如纯视频稿件)时拿不到音频地址,按只统计视频流大小处理 */
229
+ const audioUrl = playUrlData.data.data.dash.audio?.[0]?.base_url;
230
+ /** 给视频信息对象删除不符合条件的视频流 */
231
+ correctList = await (0, exports.bilibiliProcessVideos)({
232
+ accept_description: playUrlData.data.data.accept_description,
233
+ bvid: infoData.data.data.bvid,
234
+ qn: Config_1.Config.bilibili.videoQuality
235
+ }, simplify, audioUrl);
236
+ playUrlData.data.data.dash.video = correctList.videoList;
237
+ playUrlData.data.data.accept_description = correctList.accept_description;
238
+ /** 获取第一个视频流的大小 */
239
+ videoSize = await (0, exports.getvideosize)(correctList.videoList[0].base_url, audioUrl, infoData.data.data.bvid);
240
+ }
241
+ else {
242
+ /**
243
+ * 免登录直链分支:体积取自 html5 播放接口的 \`durl[0].size\`。
244
+ * 这个接口会偶发拿不到 durl(风控、字段变化、超时),原来这里直接下标取值,
245
+ * 一旦为空就抛 TypeError,**整条解析直接失败**(用户侧表现就是「提示开始解析,然后没下文」)。
246
+ * 体积只是展示信息,取不到就按 0 处理,不要拖垮解析。
247
+ */
248
+ videoSize = ((nockData?.data?.durl?.[0]?.size ?? 0) / (1024 * 1024)).toFixed(2);
249
+ }
250
+ if (Config_1.Config.bilibili.sendContent.some((content) => content === 'comment')) {
251
+ const commentsData = await (0, amagiClient_1.softFetch)(() => this.amagi.bilibili.fetcher.fetchComments({
252
+ number: Config_1.Config.bilibili.numcomment,
253
+ type: 1,
254
+ oid: infoData.data.data.aid.toString()
255
+ }, bilibiliAnonymousRequestConfig), [amagiClient_1.SOFT_ERROR_CODES.BILIBILI_COMMENTS_DISABLED]);
256
+ if ((0, amagiClient_1.isSoftFailure)(commentsData, amagiClient_1.SOFT_ERROR_CODES.BILIBILI_COMMENTS_DISABLED)) {
257
+ this.e.reply('UP主已关闭评论区,无法获取评论');
258
+ }
259
+ else {
260
+ // 诊断:评论接口回来了但列表为空时,把真实结构打出来(历史上多次踩到「多包一层 data」)
261
+ node_karin_1.logger.mark('[B站] 评论接口返回: 顶层键=' + JSON.stringify(Object.keys(commentsData?.data ?? {}).slice(0, 8)) +
262
+ ' replies=' + (commentsData?.data?.replies?.length ?? commentsData?.data?.data?.replies?.length ?? 0) +
263
+ ' numcomment=' + Config_1.Config.bilibili.numcomment);
264
+ const { comments: commentsdata, image_urls } = (0, bilibili_1.bilibiliComments)(commentsData.data, infoData.data.data.owner.mid.toString());
265
+ if (!commentsdata?.length) {
266
+ this.e.reply('这个视频没有评论 ~');
267
+ }
268
+ else {
269
+ /**
270
+ * 评论区图片**不再单独发**(按需求关闭):评论卡片里本来就会展示这些图,
271
+ * 再发一遍合并转发既刷屏又慢。这里保留解析(image_urls 仍在用),只是不发出去。
272
+ */
273
+ const messageElements = [];
274
+ if (false && Config_1.Config.bilibili.commentImageCollection && image_urls.length > 0) {
275
+ for (const [index, v] of image_urls.entries()) {
276
+ const imageUrl = await (0, utils_1.processImageUrl)(v, infoData.data.data.title, index);
277
+ messageElements.push(node_karin_1.segment.image(imageUrl));
278
+ }
279
+ /**
280
+ * 评论图片收集:**合并成一条 markdown**(QQ 官方 bot 上合并转发经常发不出去)。
281
+ * md 里连续图片紧贴渲染,一条消息装完整套图;失败再退回转发。
282
+ */
283
+ const mdMessage = await (0, QqPanel_1.buildMarkdownImageMessage)(messageElements.map((item) => String(item?.attrs?.src ?? '')).filter(Boolean));
284
+ if (mdMessage) {
285
+ await this.e.reply(mdMessage);
286
+ }
287
+ else {
288
+ const res = node_karin_1.common.makeForward(messageElements, Config_1.Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId, Config_1.Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name);
289
+ await this.e.bot.sendForwardMsg(this.e.contact, res, {
290
+ source: '评论图片收集',
291
+ summary: `查看${messageElements.length}张图片`,
292
+ prompt: 'B站评论解析结果',
293
+ news: [{ text: '点击查看解析结果' }]
294
+ });
295
+ }
296
+ }
297
+ img = await (0, utils_1.Render)(this.e, 'bilibili/comment', {
298
+ Type: '视频',
299
+ CommentsData: commentsdata,
300
+ CommentLength: Config_1.Config.bilibili.realCommentCount ? (0, utils_1.Count)(infoData.data.data.stat.reply) : String(commentsdata.length),
301
+ share_url: 'https://b23.tv/' + infoData.data.data.bvid,
302
+ Clarity: useAnonymousQuality
303
+ ? (nockData?.data?.accept_description?.slice(-1)[0] ?? '免登录 360P')
304
+ : (playUrlData.data?.data?.accept_description?.[0] ?? '未知画质'),
305
+ VideoSize: useAnonymousQuality
306
+ ? utils_1.Common.formatFileSize(((nockData?.data?.durl?.[0]?.size ?? 0) / (1024 * 1024)).toFixed(2))
307
+ : utils_1.Common.formatFileSize(videoSize),
308
+ ImageLength: 0,
309
+ shareurl: 'https://b23.tv/' + infoData.data.data.bvid,
310
+ Resolution: useAnonymousQuality
311
+ ? null
312
+ : `${playUrlData.data.data.dash.video[0].width} x ${playUrlData.data.data.dash.video[0].height}`
313
+ });
314
+ // 评论卡可能极长(实测 2880x40000),交给切片+md 拼接发送,避免 QQ 拒收
315
+ await (0, ImageSlice_1.sendSlicedImage)(this.e, img);
316
+ }
317
+ }
318
+ }
319
+ if (Config_1.Config.bilibili.sendContent.some((content) => content === 'video')) {
320
+ if (Config_1.Config.app.usefilelimit && Number(videoSize) > Number(Config_1.Config.app.filelimit) && !Config_1.Config.app.compress) {
321
+ this.e.reply(`设定的最大上传大小为 ${Config_1.Config.app.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n` +
322
+ '视频太大了,还是去B站看吧~', { reply: true });
323
+ }
324
+ else {
325
+ if (useAnonymousQuality) {
326
+ this.islogin = false;
327
+ }
328
+ // 获取弹幕数据
329
+ let danmakuList = [];
330
+ if (this.forceBurnDanmaku || Config_1.Config.bilibili.burnDanmaku) {
331
+ const cid = iddata.p ? (infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid) : infoData.data.data.cid;
332
+ const duration = iddata.p
333
+ ? (infoData.data.data.pages[iddata.p - 1]?.duration ?? infoData.data.data.duration)
334
+ : infoData.data.data.duration;
335
+ danmakuList = await this.fetchVideoDanmakuList(cid, duration);
336
+ }
337
+ await this.getvideo(
338
+ // Koishi 移植修正:原实现这里传的是 \`nockData.data\`,但 getvideo() 读的是
339
+ // \`playUrlData.data.durl\`(与上面的 \`nockData.data.durl\` 同一层),传内层会取不到 durl。
340
+ useAnonymousQuality
341
+ // 免登录分支优先用 html5 播放接口的 durl;那个请求偶发失败(风控/超时),
342
+ // 此时退回 amagi 拿到的 durl,别让「提示开始解析然后没下文」再发生
343
+ ? { playUrlData: (nockData?.data?.durl?.length ? nockData : playUrlData), danmakuList }
344
+ : { infoData: infoData.data, playUrlData: playUrlData.data, danmakuList });
345
+ }
346
+ }
347
+ break;
348
+ }
349
+ case 'bangumi_video_info': {
350
+ const videoInfo = await this.amagi.bilibili.fetcher.fetchBangumiInfo({
351
+ [iddata.isEpid ? 'ep_id' : 'season_id']: iddata.realid
352
+ });
353
+ this.islogin = (await (0, bilibili_1.checkCk)()).Status === 'isLogin';
354
+ this.isVIP = (await (0, bilibili_1.checkCk)()).isVIP;
355
+ /**
356
+ * QQ 平台:先发「卡片 + 分集表格」面板。
357
+ * 点某一集 → 带着那一集的链接回到画质面板 → 选清晰度 → 才真正解析。
358
+ * 好处是选集不再依赖「120 秒内发第 N 集」的文字匹配(群里别人说话就串了)。
359
+ * 面板发不出去(非 QQ / 没有 assets / 渲染失败)时继续走下面的原流程。
360
+ */
361
+ const bangumiEpisodes = (videoInfo.data.result.episodes ?? []).map((item) => ({
362
+ cover: item.cover,
363
+ bvid: item.bvid,
364
+ link: item.short_link,
365
+ long_title: item.long_title,
366
+ pub_time: item.pub_time,
367
+ badge: item.badge === '' ? '限免' : item.badge,
368
+ badge_info: item.badge_info
369
+ }));
370
+ // 面板上的「上一页 / 下一页」会把页码带过来(--bgp),这里直接出对应那一页
371
+ const bangumiPage = Math.max(1, Number((0, ParseOverride_1.getParseOverride)()?.bangumiPage) || 1);
372
+ if (bangumiEpisodes.length && await (0, QqPanel_2.sendBangumiPanel)(this.e, bangumiEpisodes, {
373
+ mainCover: videoInfo.data.result.cover,
374
+ Actors: videoInfo.data.result.actors,
375
+ Evaluate: videoInfo.data.result.evaluate,
376
+ Link: videoInfo.data.result.link,
377
+ newEP: videoInfo.data.result.new_ep,
378
+ Title: videoInfo.data.result.title,
379
+ Styles: videoInfo.data.result.styles,
380
+ seasonID: videoInfo.data.result.season_id,
381
+ subtitle: videoInfo.data.result.subtitle,
382
+ UPInfo: videoInfo.data.result.up_info,
383
+ Copyright: videoInfo.data.result.rights.copyright,
384
+ Stat: videoInfo.data.result.stat
385
+ }, bangumiPage)) {
386
+ return true;
387
+ }
388
+ const Episodes = [];
389
+ for (const item of videoInfo.data.result.episodes) {
390
+ Episodes.push({
391
+ cover: item.cover,
392
+ bvid: item.bvid,
393
+ link: item.short_link,
394
+ long_title: item.long_title,
395
+ pub_time: item.pub_time,
396
+ badge: item.badge === '' ? '限免' : item.badge,
397
+ badge_info: item.badge_info
398
+ });
399
+ }
400
+ img = await (0, utils_1.Render)(this.e, 'bilibili/bangumi', {
401
+ mainCover: videoInfo.data.result.cover,
402
+ Actors: videoInfo.data.result.actors,
403
+ Evaluate: videoInfo.data.result.evaluate,
404
+ Link: videoInfo.data.result.link,
405
+ newEP: videoInfo.data.result.new_ep,
406
+ Title: videoInfo.data.result.title,
407
+ Styles: videoInfo.data.result.styles,
408
+ seasonID: videoInfo.data.result.season_id,
409
+ subtitle: videoInfo.data.result.subtitle,
410
+ UPInfo: videoInfo.data.result.up_info,
411
+ Copyright: videoInfo.data.result.rights.copyright,
412
+ Stat: videoInfo.data.result.stat,
413
+ Episodes,
414
+ length: videoInfo.data.result.episodes.length
415
+ });
416
+ this.e.reply([...img, node_karin_1.segment.text('请在120秒内输入 第?集 选择集数')]);
417
+ const context = await node_karin_1.default.ctx(this.e, { reply: true, throwOnTimeout: false });
418
+ if (!context)
419
+ return true;
420
+ const regex = /第([一二三四五六七八九十百千万0-9]+)集/.exec(context.msg);
421
+ let Episode;
422
+ if (regex && regex[1]) {
423
+ Episode = regex[1];
424
+ // 检查是否为中文数字,如果是则转换为阿拉伯数字
425
+ if (/^[一二三四五六七八九十百千万]+$/.test(Episode)) {
426
+ Episode = utils_1.Common.chineseToArabic(Episode).toString();
427
+ }
428
+ this.downloadfilename = videoInfo.data.result.episodes[Number(Episode) - 1].share_copy
429
+ .substring(0, 50)
430
+ .replace(/[\\/:*?"<>|\r\n\s]/g, ' ');
431
+ this.e.reply(`收到请求,第${Episode}集\n${this.downloadfilename}\n正在下载中`);
432
+ }
433
+ else {
434
+ node_karin_1.logger.debug(Episode);
435
+ this.e.reply('匹配内容失败,请重新发送链接再次解析');
436
+ return true;
437
+ }
438
+ const bangumidataBASEURL = amagi_1.bilibiliApiUrls.getBangumiStream({
439
+ cid: videoInfo.data.result.episodes[Number(Episode) - 1].cid,
440
+ ep_id: videoInfo.data.result.episodes[Number(Episode) - 1].ep_id.toString()
441
+ });
442
+ const Params = await (0, bilibili_1.genParams)(bangumidataBASEURL);
443
+ if (!this.islogin)
444
+ this.e.reply('B站ck未配置或已失效,无法获取视频流,可尝试【#B站登录】以配置新ck');
445
+ const playUrlData = await new utils_1.Networks({
446
+ url: bangumidataBASEURL + Params,
447
+ headers: this.headers
448
+ }).getData();
449
+ if (videoInfo.data.result.episodes[Number(Episode) - 1].badge === '会员' && !this.isVIP) {
450
+ node_karin_1.logger.warn('该CK不是大会员,无法获取视频流');
451
+ return true;
452
+ }
453
+ if (Config_1.Config.bilibili.videoQuality === 0) {
454
+ /** 提取出视频流信息对象,并排除清晰度重复的视频流 */
455
+ const simplify = playUrlData.result.dash.video.filter((item, index, self) => {
456
+ return (self.findIndex((t) => {
457
+ return t.id === item.id;
458
+ }) === index);
459
+ });
460
+ /** 替换原始的视频信息对象 */
461
+ playUrlData.result.dash.video = simplify;
462
+ /** 给视频信息对象删除不符合条件的视频流 */
463
+ const correctList = await (0, exports.bilibiliProcessVideos)({
464
+ accept_description: playUrlData.result.accept_description,
465
+ bvid: videoInfo.data.result.season_id.toString(),
466
+ qn: Config_1.Config.bilibili.videoQuality
467
+ }, simplify, playUrlData.result.dash.audio?.[0]?.base_url);
468
+ playUrlData.result.dash.video = correctList.videoList;
469
+ playUrlData.result.cept_description = correctList.accept_description;
470
+ }
471
+ /**
472
+ * 番剧分支原来没有拉弹幕名单 —— 所以「弹幕解析」一部番剧,出来的是**没有弹幕的视频**。
473
+ * 这里和普通视频分支一样,按当前这一集的 cid 拉一份。
474
+ */
475
+ let bangumiDanmakuList = [];
476
+ if (this.forceBurnDanmaku || Config_1.Config.bilibili.burnDanmaku) {
477
+ const currentEpisode = videoInfo.data.result.episodes[Number(Episode) - 1];
478
+ const epDuration = Number(currentEpisode?.duration ?? 0) || 0;
479
+ bangumiDanmakuList = await this.fetchVideoDanmakuList(currentEpisode.cid, epDuration);
480
+ node_karin_1.logger.debug('[番剧] 第' + Episode + '集弹幕: ' + bangumiDanmakuList.length + ' 条');
481
+ }
482
+ await this.getvideo({
483
+ infoData: videoInfo.data,
484
+ playUrlData,
485
+ danmakuList: bangumiDanmakuList
486
+ });
487
+ break;
488
+ }
489
+ case 'dynamic_info': {
490
+ const dynamicInfo = await this.amagi.bilibili.fetcher.fetchDynamicDetail({
491
+ dynamic_id: iddata.dynamic_id
492
+ });
493
+ /**
494
+ * 动态类型取成枚举再用。
495
+ *
496
+ * 生成树的判别联合目前只录到 AV / DRAW / FORWARD 三种形状,其余(WORD / LIVE_RCMD /
497
+ * ARTICLE)落在兜底支上,判别字段声明成 `?: never` —— 拿 `item.type` 直接 switch 时,
498
+ * 枚举里那些没录到的取值会被判成「与判别字段无重叠」,连带把已知支一起收窄成 never。
499
+ * 转成枚举后各支照旧按运行时的 `type` 字符串走,字段读取仍走每层的索引签名。
500
+ */
501
+ const dynamicType = String(dynamicInfo.data.data.item.type);
502
+ const userProfileData = await this.amagi.bilibili.fetcher.fetchUserCard({
503
+ host_mid: dynamicInfo.data.data.item.modules.module_author.mid
504
+ });
505
+ switch (dynamicType) {
506
+ /** 图文、纯图 */
507
+ case amagi_1.DynamicType.DRAW: {
508
+ const imgArray = [];
509
+ const temp = [];
510
+ let hasGeneratedLivePhoto = false; // 标记是否生成了实况图
511
+ const title = dynamicInfo.data.data.item.modules.module_dynamic.major.opus.title || 'bilibili_dynamic';
512
+ for (const [index, img] of dynamicInfo.data.data.item.modules.module_dynamic.major.opus.pics.entries()) {
513
+ if (img.url) {
514
+ // Check if this is a live image with live_url
515
+ if (img.live_url) {
516
+ // Process live image similar to douyin
517
+ const livePhoto = await (0, utils_1.downloadFile)(img.live_url, {
518
+ title: `Bilibili_tmp_V_${Date.now()}_${index}.mp4`,
519
+ headers: utils_1.baseHeaders
520
+ });
521
+ if (livePhoto.filepath) {
522
+ const outputPath = utils_1.Common.tempDri.video + `Bilibili_Live_${Date.now()}_${index}.mp4`;
523
+ // 下载原图用于静态显示
524
+ const staticImg = await (0, utils_1.downloadFile)(img.url, {
525
+ title: `Bilibili_static_${Date.now()}_${index}.jpg`,
526
+ headers: utils_1.baseHeaders,
527
+ filepath: utils_1.Common.tempDri.images + `Bilibili_static_${Date.now()}_${index}.jpg`
528
+ });
529
+ if (staticImg.filepath) {
530
+ temp.push({ filepath: staticImg.filepath, totalBytes: 0 });
531
+ }
532
+ // 根据 livePhotoMode 配置决定处理方式
533
+ const livePhotoMode = Config_1.Config.app.livePhotoMode ?? 'video_and_livephoto';
534
+ const shouldGenerateVideo = livePhotoMode === 'video_and_livephoto' || livePhotoMode === 'video_only';
535
+ const shouldGenerateLivePhoto = livePhotoMode === 'video_and_livephoto' || livePhotoMode === 'livephoto_only';
536
+ // Loop the live image 3 times with Live Photo effect
537
+ const loopCount = 3;
538
+ if (!staticImg.filepath) {
539
+ await utils_1.Common.removeFile(livePhoto.filepath, true);
540
+ continue;
541
+ }
542
+ // 生成视频
543
+ if (shouldGenerateVideo) {
544
+ const result = await (0, utils_1.loopVideoWithTransition)({
545
+ inputPath: livePhoto.filepath,
546
+ outputPath,
547
+ loopCount,
548
+ staticImagePath: staticImg.filepath,
549
+ transitionEnabled: loopCount > 1
550
+ });
551
+ const success = result.success;
552
+ if (success) {
553
+ const filePath = utils_1.Common.tempDri.video + `tmp_${Date.now()}.mp4`;
554
+ node_fs_1.default.renameSync(outputPath, filePath);
555
+ node_karin_1.logger.mark(`视频文件重命名完成: ${outputPath.split('/').pop()} -> ${filePath.split('/').pop()}`);
556
+ temp.push({ filepath: filePath, totalBytes: 0 });
557
+ const videoPath = Config_1.Config.app.videoSendMode === 'base64'
558
+ ? `base64://${node_fs_1.default.readFileSync(filePath).toString('base64')}`
559
+ : `file://${filePath}`;
560
+ imgArray.push(node_karin_1.segment.video(videoPath));
561
+ }
562
+ }
563
+ // 生成实况图
564
+ if (shouldGenerateLivePhoto) {
565
+ let hasPushedMotionPhotoCover = false;
566
+ if (staticImg.filepath) {
567
+ const motionPhotoCoverPath = utils_1.Common.tempDri.images + `MVIMG_${(0, date_fns_1.format)(new Date(), 'yyyyMMdd_HHmmss_SSS')}_${index}.jpg`;
568
+ const motionPhotoCreated = await (0, utils_1.buildGoogleMotionPhoto)({
569
+ imagePath: staticImg.filepath,
570
+ videoPath: livePhoto.filepath,
571
+ outputPath: motionPhotoCoverPath
572
+ });
573
+ if (motionPhotoCreated) {
574
+ temp.push({ filepath: motionPhotoCoverPath, totalBytes: 0 });
575
+ const motionPhotoCover = (0, utils_1.processLocalImageFile)(motionPhotoCoverPath);
576
+ imgArray.push(node_karin_1.segment.image(motionPhotoCover));
577
+ hasPushedMotionPhotoCover = true;
578
+ }
579
+ }
580
+ if (!hasPushedMotionPhotoCover) {
581
+ const imageUrl = await (0, utils_1.processImageUrl)(img.url, title, index);
582
+ imgArray.push(node_karin_1.segment.image(imageUrl));
583
+ }
584
+ else {
585
+ hasGeneratedLivePhoto = true; // 标记已生成实况图
586
+ }
587
+ }
588
+ node_karin_1.logger.mark('正在尝试删除缓存文件');
589
+ await utils_1.Common.removeFile(livePhoto.filepath, true);
590
+ }
591
+ }
592
+ else {
593
+ // Regular static image
594
+ const imageUrl = await (0, utils_1.processImageUrl)(img.url, title, index);
595
+ imgArray.push(node_karin_1.segment.image(imageUrl));
596
+ }
597
+ }
598
+ }
599
+ // 如果生成了实况图,添加提示文字
600
+ if (hasGeneratedLivePhoto) {
601
+ const tipImg = await (0, utils_1.Render)(this.e, 'other/live-photo-tip', {
602
+ title: '实况照片已生成',
603
+ description: '保存原图到相册即可识别为实况图'
604
+ });
605
+ imgArray.push(...tipImg);
606
+ }
607
+ if (imgArray.length === 1)
608
+ this.e.reply(imgArray[0]);
609
+ if (imgArray.length > 1) {
610
+ const forwardMsg = node_karin_1.common.makeForward(imgArray, Config_1.Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId, Config_1.Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name);
611
+ try {
612
+ await this.e.bot.sendForwardMsg(this.e.contact, forwardMsg, {
613
+ source: '图片合集',
614
+ summary: `查看${imgArray.length}张图片消息`,
615
+ prompt: 'B站图文动态解析结果',
616
+ news: [{ text: '点击查看解析结果' }]
617
+ });
618
+ }
619
+ finally {
620
+ for (const item of temp) {
621
+ await utils_1.Common.removeFile(item.filepath, true);
622
+ }
623
+ }
624
+ }
625
+ const md = dynamicInfo.data.data.item.modules.module_dynamic;
626
+ if (md.topic) {
627
+ const { name } = md.topic;
628
+ const summary = md.major.opus.summary;
629
+ summary.rich_text_nodes ??= [];
630
+ summary.rich_text_nodes.unshift({ orig_text: name, jump_url: '', text: name, type: 'topic' });
631
+ summary.text = summary.text ? `${name}\n${summary.text}` : name;
632
+ }
633
+ this.e.reply(await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_DRAW', {
634
+ // 生成类型在判别联合里把 `pics` 记成 `any`(各支索引签名),`Object.values` 于是推出
635
+ // `unknown[]` —— 谓词里先把元素当成「可能有 url 的对象」再判,形状仍然照旧收窄
636
+ image_url: Object.values(dynamicInfo.data.data.item.modules.module_dynamic.major.opus.pics)
637
+ .filter((item) => typeof item?.url === 'string')
638
+ .map((item) => ({ image_src: item.url })),
639
+ // TIP: 2025/08/20, 动态卡片数据中,图文动态的描述文本在 major.opus.summary 中
640
+ title: dynamicInfo.data.data.item.modules.module_dynamic.major.opus.title ?? undefined,
641
+ text: dynamicInfo.data.data.item.modules.module_dynamic.major
642
+ ? (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.modules.module_dynamic.major.opus?.summary?.text ?? '', dynamicInfo.data.data.item.modules.module_dynamic.major.opus?.summary?.rich_text_nodes ?? [])
643
+ : null,
644
+ dianzan: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.like.count),
645
+ pinglun: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.comment.count),
646
+ share: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.forward.count),
647
+ create_time: exports.TimeFormatter.toRelative(dynamicInfo.data.data.item.modules.module_author.pub_ts),
648
+ avatar_url: dynamicInfo.data.data.item.modules.module_author.face,
649
+ frame: dynamicInfo.data.data.item.modules.module_author.pendant.image,
650
+ share_url: 'https://t.bilibili.com/' + dynamicInfo.data.data.item.id_str,
651
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
652
+ fans: (0, utils_1.Count)(userProfileData.data.data.follower),
653
+ user_shortid: dynamicInfo.data.data.item.modules.module_author.mid,
654
+ total_favorited: (0, utils_1.Count)(userProfileData.data.data.like_num),
655
+ following_count: (0, utils_1.Count)(userProfileData.data.data.card.attention),
656
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.modules.module_author.decoration_card),
657
+ render_time: exports.TimeFormatter.now(),
658
+ dynamicTYPE: '图文动态解析',
659
+ imageLayout: Config_1.Config.bilibili.imageLayout,
660
+ additional: (0, exports.parseAdditionalCard)(dynamicInfo.data.data.item.modules.module_dynamic.additional),
661
+ dynamic_id: dynamicInfo.data.data.item.id_str
662
+ }));
663
+ break;
664
+ }
665
+ /** 纯文 */
666
+ case amagi_1.DynamicType.WORD: {
667
+ // 处理话题
668
+ const md = dynamicInfo.data.data.item.modules.module_dynamic;
669
+ if (md.topic) {
670
+ const { name } = md.topic;
671
+ const summary = md.major.opus.summary;
672
+ summary.rich_text_nodes ??= [];
673
+ summary.rich_text_nodes.unshift({ orig_text: name, jump_url: '', text: name, type: 'topic' });
674
+ summary.text = summary.text ? `${name}\n\n${summary.text}` : name;
675
+ }
676
+ const text = (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.modules.module_dynamic.major.opus?.summary?.text ?? '', dynamicInfo.data.data.item.modules.module_dynamic.major.opus?.summary?.rich_text_nodes ?? []);
677
+ this.e.reply(await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_WORD', {
678
+ text,
679
+ dianzan: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.like.count),
680
+ pinglun: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.comment.count),
681
+ share: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.forward.count),
682
+ create_time: exports.TimeFormatter.toRelative(dynamicInfo.data.data.item.modules.module_author.pub_ts),
683
+ avatar_url: dynamicInfo.data.data.item.modules.module_author.face,
684
+ frame: dynamicInfo.data.data.item.modules.module_author.pendant.image,
685
+ share_url: 'https://t.bilibili.com/' + dynamicInfo.data.data.item.id_str,
686
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
687
+ fans: (0, utils_1.Count)(userProfileData.data.data.follower),
688
+ user_shortid: dynamicInfo.data.data.item.modules.module_author.mid,
689
+ total_favorited: (0, utils_1.Count)(userProfileData.data.data.like_num),
690
+ following_count: (0, utils_1.Count)(userProfileData.data.data.card.attention),
691
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.modules.module_author.decoration_card),
692
+ render_time: exports.TimeFormatter.now(),
693
+ dynamicTYPE: '纯文动态解析',
694
+ additional: (0, exports.parseAdditionalCard)(dynamicInfo.data.data.item.modules.module_dynamic.additional),
695
+ dynamic_id: dynamicInfo.data.data.item.id_str
696
+ }));
697
+ break;
698
+ }
699
+ /** 转发动态 */
700
+ case amagi_1.DynamicType.FORWARD: {
701
+ // 处理话题
702
+ const md = dynamicInfo.data.data.item.modules.module_dynamic;
703
+ if (md.topic) {
704
+ const { name } = md.topic;
705
+ const desc = md.desc;
706
+ desc.rich_text_nodes ??= [];
707
+ desc.rich_text_nodes.unshift({ orig_text: name, jump_url: '', text: name, type: 'topic' });
708
+ desc.text = desc.text ? `${name}\n\n${desc.text}` : name;
709
+ }
710
+ const text = (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.modules.module_dynamic.desc.text, dynamicInfo.data.data.item.modules.module_dynamic.desc.rich_text_nodes);
711
+ // 富文本节点:查看图片
712
+ const imgList = [];
713
+ for (const richTxtItem of dynamicInfo.data.data.item.modules.module_dynamic.desc.rich_text_nodes) {
714
+ if (richTxtItem.type === 'RICH_TEXT_NODE_TYPE_VIEW_PICTURE') {
715
+ for (const pic of richTxtItem.pics) {
716
+ imgList.push(pic.src);
717
+ }
718
+ }
719
+ }
720
+ let original_content = {};
721
+ switch (dynamicInfo.data.data.item.orig.type) {
722
+ // 转发视频动态
723
+ case amagi_1.DynamicType.AV: {
724
+ const desc = dynamicInfo.data.data.item.orig.modules.module_dynamic?.desc || {
725
+ text: '',
726
+ rich_text_nodes: []
727
+ };
728
+ original_content = {
729
+ DYNAMIC_TYPE_AV: {
730
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(dynamicInfo.data.data.item.orig.modules.module_author),
731
+ avatar_url: dynamicInfo.data.data.item.orig.modules.module_author.face,
732
+ duration_text: dynamicInfo.data.data.item.orig.modules.module_dynamic.major.archive.duration_text,
733
+ text: (0, dynamic_text_1.buildBilibiliDynamicRichText)(desc.text, desc.rich_text_nodes),
734
+ title: (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.orig.modules.module_dynamic.major.archive.title, []),
735
+ danmaku: dynamicInfo.data.data.item.orig.modules.module_dynamic.major.archive.stat.danmaku,
736
+ play: dynamicInfo.data.data.item.orig.modules.module_dynamic.major.archive.stat.play,
737
+ cover: dynamicInfo.data.data.item.orig.modules.module_dynamic.major.archive.cover,
738
+ create_time: exports.TimeFormatter.toDateTime(dynamicInfo.data.data.item.orig.modules.module_author.pub_ts),
739
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.orig.modules.module_author.decoration_card),
740
+ frame: dynamicInfo.data.data.item.orig.modules.module_author.pendant.image
741
+ }
742
+ };
743
+ break;
744
+ }
745
+ // 转发图文动态
746
+ case amagi_1.DynamicType.DRAW: {
747
+ // 处理话题
748
+ const origMd = dynamicInfo.data.data.item.orig.modules.module_dynamic;
749
+ if (origMd.topic) {
750
+ const { name } = origMd.topic;
751
+ const summary = origMd.major?.opus?.summary;
752
+ if (summary) {
753
+ summary.rich_text_nodes ??= [];
754
+ summary.rich_text_nodes.unshift({
755
+ orig_text: name,
756
+ jump_url: '',
757
+ text: name,
758
+ type: 'topic',
759
+ rid: '',
760
+ style: { '1114514': '1919810' }
761
+ });
762
+ summary.text = summary.text ? `${name}\n${summary.text}` : name;
763
+ }
764
+ }
765
+ original_content = {
766
+ DYNAMIC_TYPE_DRAW: {
767
+ title: dynamicInfo.data.data.item.orig.modules.module_dynamic.major?.opus?.title ?? undefined,
768
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(dynamicInfo.data.data.item.orig.modules.module_author),
769
+ create_time: exports.TimeFormatter.toDateTime(dynamicInfo.data.data.item.orig.modules.module_author.pub_ts),
770
+ avatar_url: dynamicInfo.data.data.item.orig.modules.module_author.face,
771
+ text: (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.orig.modules.module_dynamic.major.opus.summary.text, dynamicInfo.data.data.item.orig.modules.module_dynamic.major.opus.summary.rich_text_nodes),
772
+ // 同上:生成类型的 `pics` 是 `any`,`Object.values` 出 `unknown[]`
773
+ image_url: Object.values(dynamicInfo.data.data.item.orig.modules.module_dynamic.major.opus.pics)
774
+ .filter((item) => typeof item?.url === 'string')
775
+ .map((item) => ({ image_src: item.url })),
776
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.orig.modules.module_author.decoration_card),
777
+ frame: dynamicInfo.data.data.item.orig.modules.module_author.pendant.image
778
+ }
779
+ };
780
+ break;
781
+ }
782
+ // 转发纯文动态
783
+ case amagi_1.DynamicType.WORD: {
784
+ // 处理话题
785
+ const origMd = dynamicInfo.data.data.item.orig.modules.module_dynamic;
786
+ if (origMd.topic) {
787
+ const { name } = origMd.topic;
788
+ const summary = origMd.major?.opus?.summary;
789
+ if (summary) {
790
+ summary.rich_text_nodes ??= [];
791
+ summary.rich_text_nodes.unshift({ orig_text: name, jump_url: '', text: name, type: 'topic' });
792
+ summary.text = summary.text ? `${name}\n${summary.text}` : name;
793
+ }
794
+ }
795
+ original_content = {
796
+ DYNAMIC_TYPE_WORD: {
797
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(dynamicInfo.data.data.item.orig.modules.module_author),
798
+ create_time: exports.TimeFormatter.toDateTime(dynamicInfo.data.data.item.orig.modules.module_author.pub_ts),
799
+ avatar_url: dynamicInfo.data.data.item.orig.modules.module_author.face,
800
+ text: (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.orig.modules.module_dynamic.major.opus.summary.text, dynamicInfo.data.data.item.orig.modules.module_dynamic.major.opus.summary.rich_text_nodes),
801
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.orig.modules.module_author.decoration_card),
802
+ frame: dynamicInfo.data.data.item.orig.modules.module_author.pendant.image,
803
+ additional: (0, exports.parseAdditionalCard)(dynamicInfo.data.data.item.orig.modules.module_dynamic.additional)
804
+ }
805
+ };
806
+ break;
807
+ }
808
+ // 转发直播开始动态
809
+ case amagi_1.DynamicType.LIVE_RCMD: {
810
+ const liveData = JSON.parse(dynamicInfo.data.data.item.orig.modules.module_dynamic.major.live_rcmd.content);
811
+ original_content = {
812
+ DYNAMIC_TYPE_LIVE_RCMD: {
813
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(dynamicInfo.data.data.item.orig.modules.module_author),
814
+ create_time: exports.TimeFormatter.toDateTime(dynamicInfo.data.data.item.orig.modules.module_author.pub_ts),
815
+ avatar_url: dynamicInfo.data.data.item.orig.modules.module_author.face,
816
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.orig.modules.module_author.decoration_card),
817
+ frame: dynamicInfo.data.data.item.orig.modules.module_author.pendant.image,
818
+ cover: liveData.live_play_info.cover,
819
+ text_large: liveData.live_play_info.watched_show.text_large,
820
+ area_name: liveData.live_play_info.area_name,
821
+ title: (0, dynamic_text_1.buildBilibiliDynamicRichText)(liveData.live_play_info.title, []),
822
+ online: liveData.live_play_info.online
823
+ }
824
+ };
825
+ break;
826
+ }
827
+ // 其他类型动态未适配
828
+ default: {
829
+ node_karin_1.logger.warn(`UP主:${userProfileData.data.data.card.name}的${node_karin_1.logger.green('转发动态')}转发的原动态类型为「${node_karin_1.logger.yellow(dynamicInfo.data.item.orig.type)}」暂未支持解析`);
830
+ break;
831
+ }
832
+ }
833
+ this.e.reply(await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_FORWARD', {
834
+ text,
835
+ imgList: imgList.length > 0 ? imgList : null,
836
+ dianzan: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.like.count),
837
+ pinglun: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.comment.count),
838
+ share: (0, utils_1.Count)(dynamicInfo.data.data.item.modules.module_stat.forward.count),
839
+ create_time: exports.TimeFormatter.toRelative(dynamicInfo.data.data.item.modules.module_author.pub_ts),
840
+ avatar_url: dynamicInfo.data.data.item.modules.module_author.face,
841
+ frame: dynamicInfo.data.data.item.modules.module_author.pendant.image,
842
+ share_url: 'https://t.bilibili.com/' + dynamicInfo.data.data.item.id_str,
843
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
844
+ fans: (0, utils_1.Count)(userProfileData.data.data.follower),
845
+ user_shortid: dynamicInfo.data.data.item.modules.module_author.mid,
846
+ total_favorited: (0, utils_1.Count)(userProfileData.data.data.like_num),
847
+ following_count: (0, utils_1.Count)(userProfileData.data.data.card.attention),
848
+ dynamicTYPE: '转发动态解析',
849
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.modules.module_author.decoration_card),
850
+ render_time: exports.TimeFormatter.now(),
851
+ original_content,
852
+ dynamic_id: dynamicInfo.data.data.item.id_str
853
+ }));
854
+ break;
855
+ }
856
+ /** 视频动态 */
857
+ case amagi_1.DynamicType.AV: {
858
+ if (dynamicInfo.data.data.item.modules.module_dynamic.major.type === 'MAJOR_TYPE_ARCHIVE') {
859
+ const bvid = dynamicInfo.data.data.item.modules.module_dynamic.major.archive.bvid;
860
+ const INFODATA = await amagiClient_1.bilibiliFetcher.fetchVideoInfo({ bvid });
861
+ // 处理共创者信息
862
+ let staff = undefined;
863
+ if (INFODATA.data.data.staff && Array.isArray(INFODATA.data.data.staff)) {
864
+ const currentMid = dynamicInfo.data.data.item.modules.module_author.mid;
865
+ // 提取共创者信息
866
+ staff = INFODATA.data.data.staff.map((member) => ({
867
+ mid: member.mid,
868
+ title: member.title,
869
+ name: member.name,
870
+ face: member.face,
871
+ follower: member.follower
872
+ }));
873
+ // 如果当前动态发布者是共创者之一,将其排到最前面
874
+ const currentUserIndex = staff.findIndex((member) => member.mid === currentMid);
875
+ if (currentUserIndex > 0) {
876
+ const currentUser = staff.splice(currentUserIndex, 1)[0];
877
+ staff.unshift(currentUser);
878
+ }
879
+ }
880
+ // 处理话题
881
+ const md = dynamicInfo.data.data.item.modules.module_dynamic;
882
+ if (md.topic) {
883
+ const { name } = md.topic;
884
+ md.desc ??= { rich_text_nodes: [], text: '' };
885
+ md.desc.rich_text_nodes.unshift({ orig_text: name, jump_url: '', text: name, type: 'topic' });
886
+ md.desc.text = md.desc.text ? `${name}\n\n${md.desc.text}` : name;
887
+ }
888
+ const dynamicText = (0, dynamic_text_1.buildBilibiliDynamicRichText)(dynamicInfo.data.data.item.modules.module_dynamic.desc?.text ?? '', dynamicInfo.data.data.item.modules.module_dynamic.desc?.rich_text_nodes ?? []);
889
+ img = await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_AV', {
890
+ image_url: INFODATA.data.data.pic,
891
+ text: (0, dynamic_text_1.buildBilibiliDynamicRichText)(INFODATA.data.data.title, []),
892
+ desc: INFODATA.data.data.desc_v2?.length
893
+ ? (0, dynamic_text_1.buildBilibiliVideoDescRichText)(INFODATA.data.data.desc_v2)
894
+ : (0, dynamic_text_1.buildBilibiliDynamicRichText)(INFODATA.data.data.desc || '', []),
895
+ dynamic_text: dynamicText,
896
+ dianzan: (0, utils_1.Count)(INFODATA.data.data.stat.like),
897
+ pinglun: (0, utils_1.Count)(INFODATA.data.data.stat.reply),
898
+ share: (0, utils_1.Count)(INFODATA.data.data.stat.share),
899
+ view: (0, utils_1.Count)(INFODATA.data.data.stat.view),
900
+ coin: (0, utils_1.Count)(INFODATA.data.data.stat.coin),
901
+ duration_text: dynamicInfo.data.data.item.modules.module_dynamic.major.archive.duration_text,
902
+ page_length: INFODATA.data.data.pages.length,
903
+ create_time: exports.TimeFormatter.toRelative(dynamicInfo.data.data.item.modules.module_author.pub_ts),
904
+ avatar_url: dynamicInfo.data.data.item.modules.module_author.face,
905
+ frame: dynamicInfo.data.data.item.modules.module_author.pendant.image,
906
+ share_url: 'https://www.bilibili.com/video/' + bvid,
907
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
908
+ fans: (0, utils_1.Count)(userProfileData.data.data.follower),
909
+ user_shortid: userProfileData.data.data.card.mid,
910
+ total_favorited: (0, utils_1.Count)(userProfileData.data.data.like_num),
911
+ following_count: (0, utils_1.Count)(userProfileData.data.data.card.attention),
912
+ decoration_card: (0, exports.generateDecorationCard)(dynamicInfo.data.data.item.modules.module_author.decoration_card),
913
+ render_time: exports.TimeFormatter.now(),
914
+ dynamicTYPE: '视频动态解析',
915
+ dynamic_id: dynamicInfo.data.data.item.id_str,
916
+ staff
917
+ });
918
+ this.e.reply(img);
919
+ }
920
+ break;
921
+ }
922
+ /** 直播动态 */
923
+ case amagi_1.DynamicType.LIVE_RCMD: {
924
+ const userINFO = await amagiClient_1.bilibiliFetcher.fetchUserCard({
925
+ host_mid: dynamicInfo.data.data.item.modules.module_author.mid
926
+ });
927
+ const liveInfo = JSON.parse(dynamicInfo.data.data.item.modules.module_dynamic.major.live_rcmd.content);
928
+ img = await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD', {
929
+ image_url: liveInfo.live_play_info.cover,
930
+ text: (0, dynamic_text_1.buildBilibiliDynamicRichText)(liveInfo.live_play_info.title, []),
931
+ liveinf: br(`${liveInfo.live_play_info.area_name} | 房间号: ${liveInfo.live_play_info.room_id}`),
932
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userINFO.data.data.card),
933
+ avatar_url: userINFO.data.data.card.face,
934
+ frame: dynamicInfo.data.data.item.modules.module_author.pendant.image,
935
+ fans: (0, utils_1.Count)(userINFO.data.data.follower),
936
+ create_time: exports.TimeFormatter.toDateTime(dynamicInfo.data.data.item.modules.module_author.pub_ts),
937
+ now_time: exports.TimeFormatter.now(),
938
+ share_url: 'https://live.bilibili.com/' + liveInfo.live_play_info.room_id,
939
+ dynamicTYPE: '直播动态解析'
940
+ });
941
+ this.e.reply(img);
942
+ break;
943
+ }
944
+ /** 文章/专栏动态 */
945
+ case amagi_1.DynamicType.ARTICLE: {
946
+ const articleInfoBase = await this.amagi.bilibili.fetcher.fetchArticleInfo({
947
+ id: dynamicInfo.data.data.item.basic.rid_str
948
+ });
949
+ const articleInfo = await this.amagi.bilibili.fetcher.fetchArticleContent({
950
+ id: dynamicInfo.data.data.item.basic.rid_str
951
+ });
952
+ // 提取专栏基本信息
953
+ const articleData = articleInfoBase.data.data;
954
+ // 提取专栏正文内容(生成类型里 `data` 可空:样本里就有一条只有 code/message 的响应)
955
+ const articleContent = articleInfo.data.data;
956
+ if (!articleContent) {
957
+ this.e.reply('获取专栏正文失败,该专栏可能已被删除或设为私密');
958
+ break;
959
+ }
960
+ // TODO: 还未完全支持B站的富文本格式,后续需要根据实际情况补充更多类型的节点解析
961
+ // 构建富文本文档
962
+ const body = (0, dynamic_text_1.buildBilibiliArticleRichText)(articleContent.opus, articleContent.content, utils_1.Common.useDarkTheme());
963
+ const title = articleData.title || 'bilibili_article';
964
+ const shareUrl = articleContent.dyn_id_str
965
+ ? `https://www.bilibili.com/opus/${articleContent.dyn_id_str}`
966
+ : `https://www.bilibili.com/read/cv${articleContent.id}`;
967
+ const messageElements = await (0, dynamic_text_1.buildBilibiliRichTextForwardMessage)(body, {
968
+ title: articleData.title,
969
+ summary: articleData.summary,
970
+ shareUrl,
971
+ imageResolver: (src, index) => (0, utils_1.processImageUrl)(src, title, index)
972
+ });
973
+ if (messageElements.length > 0) {
974
+ const forwardMsg = node_karin_1.common.makeForward(messageElements, Config_1.Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId, Config_1.Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name);
975
+ await this.e.bot.sendForwardMsg(this.e.contact, forwardMsg, {
976
+ source: '专栏内容',
977
+ summary: `查看${messageElements.length}条专栏内容`,
978
+ prompt: 'B站专栏动态解析结果',
979
+ news: [{ text: '点击查看解析结果' }]
980
+ });
981
+ }
982
+ // 构建渲染数据
983
+ const img = await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_ARTICLE', {
984
+ // 用户信息
985
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
986
+ avatar_url: userProfileData.data.data.card.face,
987
+ frame: dynamicInfo.data.data.item.modules.module_author.pendant.image,
988
+ create_time: exports.TimeFormatter.toDateTime(dynamicInfo.data.data.item.modules.module_author.pub_ts),
989
+ // 专栏内容信息
990
+ title: articleData.title,
991
+ summary: articleData.summary,
992
+ banner_url: articleData.banner_url || (articleData.image_urls && articleData.image_urls[0]) || '',
993
+ categories: articleData.categories || [],
994
+ words: articleData.words || 0,
995
+ // 专栏正文内容(richtext 格式)
996
+ body,
997
+ // 统计信息
998
+ stats: articleData.stats,
999
+ render_time: exports.TimeFormatter.now(),
1000
+ // 分享链接
1001
+ share_url: shareUrl,
1002
+ dynamicTYPE: '专栏动态解析',
1003
+ // 用户统计信息
1004
+ user_shortid: userProfileData.data.data.card.mid,
1005
+ total_favorited: (0, utils_1.Count)(userProfileData.data.data.like_num),
1006
+ following_count: (0, utils_1.Count)(userProfileData.data.data.card.friend),
1007
+ fans: (0, utils_1.Count)(userProfileData.data.data.card.fans)
1008
+ });
1009
+ this.e.reply(img);
1010
+ break;
1011
+ }
1012
+ default: {
1013
+ const unknownItem = dynamicInfo.data.data.item;
1014
+ this.e.reply(`该动态类型「${unknownItem.type}」暂未支持解析,可通过 https://github.com/ikenxuan/karin-plugin-kkk/issues/new/choose 提交反馈`);
1015
+ break;
1016
+ }
1017
+ }
1018
+ // 统一处理评论(直播动态除外)
1019
+ if (Config_1.Config.bilibili.sendContent.some((content) => content === 'comment') && dynamicType !== amagi_1.DynamicType.LIVE_RCMD) {
1020
+ const commentsData = await (0, amagiClient_1.softFetch)(() => this.amagi.bilibili.fetcher.fetchComments({
1021
+ type: mapping_table(dynamicType),
1022
+ oid: oid(dynamicType, dynamicInfo.data),
1023
+ number: Config_1.Config.bilibili.numcomment
1024
+ }, bilibiliAnonymousRequestConfig), [amagiClient_1.SOFT_ERROR_CODES.BILIBILI_COMMENTS_DISABLED]);
1025
+ if ((0, amagiClient_1.isSoftFailure)(commentsData, amagiClient_1.SOFT_ERROR_CODES.BILIBILI_COMMENTS_DISABLED)) {
1026
+ this.e.reply('UP主已关闭评论区,无法获取评论');
1027
+ }
1028
+ else {
1029
+ const { comments: commentsdata, image_urls } = (0, bilibili_1.bilibiliComments)(commentsData.data, dynamicInfo.data.data.item.modules.module_author.mid.toString());
1030
+ if (commentsdata && commentsdata.length > 0) {
1031
+ // 收集评论区图片
1032
+ if (Config_1.Config.bilibili.commentImageCollection && image_urls.length > 0) {
1033
+ const messageElements = [];
1034
+ // 获取动态标题用于图片命名
1035
+ let title = 'bilibili_dynamic';
1036
+ if (dynamicType === amagi_1.DynamicType.DRAW) {
1037
+ title = dynamicInfo.data.data.item.modules.module_dynamic.major.opus.title || 'bilibili_dynamic';
1038
+ }
1039
+ else if (dynamicType === amagi_1.DynamicType.AV) {
1040
+ title = dynamicInfo.data.data.item.modules.module_dynamic.major.archive.title || 'bilibili_dynamic';
1041
+ }
1042
+ for (const [index, v] of image_urls.entries()) {
1043
+ const imageUrl = await (0, utils_1.processImageUrl)(v, title, index);
1044
+ messageElements.push(node_karin_1.segment.image(imageUrl));
1045
+ }
1046
+ /**
1047
+ * 评论图片收集:**合并成一条 markdown**(QQ 官方 bot 上合并转发经常发不出去)。
1048
+ * md 里连续图片紧贴渲染,一条消息装完整套图;失败再退回转发。
1049
+ */
1050
+ const mdMessage = await (0, QqPanel_1.buildMarkdownImageMessage)(messageElements.map((item) => String(item?.attrs?.src ?? '')).filter(Boolean));
1051
+ if (mdMessage) {
1052
+ await this.e.reply(mdMessage);
1053
+ }
1054
+ else {
1055
+ const res = node_karin_1.common.makeForward(messageElements, Config_1.Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId, Config_1.Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name);
1056
+ await this.e.bot.sendForwardMsg(this.e.contact, res, {
1057
+ source: '评论图片收集',
1058
+ summary: `查看${messageElements.length}张图片`,
1059
+ prompt: 'B站评论解析结果',
1060
+ news: [{ text: '点击查看解析结果' }]
1061
+ });
1062
+ }
1063
+ }
1064
+ // 渲染评论图
1065
+ const img = await (0, utils_1.Render)(this.e, 'bilibili/comment', {
1066
+ Type: '动态',
1067
+ CommentsData: commentsdata,
1068
+ CommentLength: String(commentsdata.length),
1069
+ share_url: dynamicType === amagi_1.DynamicType.AV
1070
+ ? `https://www.bilibili.com/video/${dynamicInfo.data.data.item.modules.module_dynamic.major.archive.bvid}`
1071
+ : `https://t.bilibili.com/${dynamicInfo.data.data.item.id_str}`,
1072
+ ImageLength: dynamicInfo.data.data.item.modules?.module_dynamic?.major?.draw?.items?.length ?? 0,
1073
+ shareurl: '动态分享链接',
1074
+ Resolution: null
1075
+ });
1076
+ this.e.reply(img);
1077
+ }
1078
+ else {
1079
+ this.e.reply('这条动态暂时还没有评论~');
1080
+ }
1081
+ }
1082
+ }
1083
+ break;
1084
+ }
1085
+ case 'live_room_detail': {
1086
+ const liveInfo = await this.amagi.bilibili.fetcher.fetchLiveRoomInfo({
1087
+ room_id: iddata.room_id
1088
+ });
1089
+ const roomInitInfo = await this.amagi.bilibili.fetcher.fetchLiveRoomInitInfo({
1090
+ room_id: iddata.room_id
1091
+ });
1092
+ const userProfileData = await this.amagi.bilibili.fetcher.fetchUserCard({
1093
+ host_mid: roomInitInfo.data.data.uid
1094
+ });
1095
+ if (roomInitInfo.data.data.live_status === 0) {
1096
+ this.e.reply(`「${userProfileData.data.data.card.name}」\n未开播,正在休息中~`);
1097
+ return true;
1098
+ }
1099
+ const img = await (0, utils_1.Render)(this.e, 'bilibili/dynamic/DYNAMIC_TYPE_LIVE_RCMD', {
1100
+ image_url: liveInfo.data.data.user_cover,
1101
+ text: (0, dynamic_text_1.buildBilibiliDynamicRichText)(liveInfo.data.data.title, []),
1102
+ liveinf: br(`${liveInfo.data.data.area_name} | 房间号: ${liveInfo.data.data.room_id}`),
1103
+ usernameMeta: (0, dynamic_text_1.getUsernameMetadata)(userProfileData.data.data.card),
1104
+ avatar_url: userProfileData.data.data.card.face,
1105
+ frame: userProfileData.data.data.card.pendant.image,
1106
+ fans: (0, utils_1.Count)(userProfileData.data.data.card.fans),
1107
+ create_time: liveInfo.data.data.live_time === '-62170012800' ? '获取失败' : liveInfo.data.data.live_time,
1108
+ now_time: exports.TimeFormatter.now(),
1109
+ share_url: 'https://live.bilibili.com/' + liveInfo.data.data.room_id,
1110
+ dynamicTYPE: '直播动态解析'
1111
+ });
1112
+ this.e.reply(img);
1113
+ break;
1114
+ }
1115
+ default:
1116
+ break;
1117
+ }
1118
+ }
1119
+ /**
1120
+ * 获取视频弹幕列表(按每 6 分钟一段并行拉取所有分段)
1121
+ * @param cid 视频分P的 cid
1122
+ * @param duration 视频时长(秒)
1123
+ * @returns 合并后的弹幕列表
1124
+ */
1125
+ async fetchVideoDanmakuList(cid, duration) {
1126
+ try {
1127
+ const segmentCount = Math.ceil(duration / 360); // 360秒 = 6分钟
1128
+ node_karin_1.logger.debug(`视频时长: ${duration}秒, 需要获取 ${segmentCount} 个弹幕分段`);
1129
+ const danmakuPromises = Array.from({ length: segmentCount }, (_, i) => this.amagi.bilibili.fetcher
1130
+ .fetchVideoDanmaku({ cid, segment_index: i + 1 })
1131
+ .then((res) => res.data?.data?.elems || [])
1132
+ .catch(() => []));
1133
+ const danmakuSegments = await Promise.all(danmakuPromises);
1134
+ const danmakuList = danmakuSegments.flat();
1135
+ node_karin_1.logger.debug(`获取到 ${danmakuList.length} 条弹幕(${segmentCount} 个分段)`);
1136
+ return danmakuList;
1137
+ }
1138
+ catch (err) {
1139
+ node_karin_1.logger.warn('获取弹幕失败', err);
1140
+ return [];
1141
+ }
1142
+ }
1143
+ async getvideo({ infoData, playUrlData, danmakuList = [] }) {
1144
+ /** 获取视频 => FFmpeg合成 */
1145
+ node_karin_1.logger.debug('是否登录:', this.islogin);
1146
+ switch (this.islogin) {
1147
+ case true: {
1148
+ node_karin_1.logger.debug('视频 URL:', this.Type === 'one_video' ? playUrlData.data?.dash?.video[0].base_url : playUrlData.result.dash.video[0].base_url);
1149
+ // B站 CDN 需要正确的 Referer
1150
+ const downloadHeaders = {
1151
+ ...this.headers,
1152
+ Referer: 'https://www.bilibili.com'
1153
+ };
1154
+ const bmp4Raw = await (0, utils_1.downloadFile)(this.Type === 'one_video' ? playUrlData.data?.dash?.video[0].base_url : playUrlData.result.dash.video[0].base_url, {
1155
+ title: `Bil_V_${this.Type === 'one_video' ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.m4s`,
1156
+ headers: downloadHeaders
1157
+ });
1158
+ // 修复 m4s 文件为标准 MP4
1159
+ const videoPath = utils_1.Common.tempDri.video +
1160
+ `Bil_V_${this.Type === 'one_video' ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.mp4`;
1161
+ const videoFixed = await (0, utils_1.fixM4sFile)(bmp4Raw.filepath, videoPath);
1162
+ if (!videoFixed) {
1163
+ node_karin_1.logger.error('视频文件修复失败');
1164
+ return false;
1165
+ }
1166
+ // 删除原始 m4s 文件
1167
+ await utils_1.Common.removeFile(bmp4Raw.filepath, true);
1168
+ const audioUrl = this.Type === 'one_video' ? playUrlData.data?.dash?.audio?.[0]?.base_url : playUrlData.result.dash.audio?.[0]?.base_url;
1169
+ node_karin_1.logger.debug('音频 URL:', audioUrl);
1170
+ /** 没有音频流(如纯视频稿件)时为 undefined,此时无从合成,直接发视频流 */
1171
+ let bmp3;
1172
+ if (audioUrl) {
1173
+ const bmp3Raw = await (0, utils_1.downloadFile)(audioUrl, {
1174
+ title: `Bil_A_${this.Type === 'one_video' ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.m4s`,
1175
+ headers: downloadHeaders
1176
+ });
1177
+ // 修复音频 m4s 文件为 m4a(AAC 音频不能直接转为 MP3 容器)
1178
+ const audioPath = utils_1.Common.tempDri.video +
1179
+ `Bil_A_${this.Type === 'one_video' ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.m4a`;
1180
+ const audioFixed = await (0, utils_1.fixM4sFile)(bmp3Raw.filepath, audioPath);
1181
+ if (!audioFixed) {
1182
+ node_karin_1.logger.error('音频文件修复失败');
1183
+ return false;
1184
+ }
1185
+ // 删除原始 m4s 文件
1186
+ await utils_1.Common.removeFile(bmp3Raw.filepath, true);
1187
+ bmp3 = { filepath: audioPath, totalBytes: bmp3Raw.totalBytes };
1188
+ }
1189
+ const bmp4 = { filepath: videoPath, totalBytes: bmp4Raw.totalBytes };
1190
+ if (bmp4.filepath) {
1191
+ // 根据是否有弹幕数据选择合成方式
1192
+ const hasDanmaku = (this.forceBurnDanmaku || Config_1.Config.bilibili.burnDanmaku) && danmakuList.length > 0;
1193
+ const resultPath = utils_1.Common.tempDri.video +
1194
+ `Bil_Result_${this.Type === 'one_video' ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.mp4`;
1195
+ let success;
1196
+ /** 最终要上传的文件:合成/烧录的产物,或没有音频流时直接用的视频流 */
1197
+ let sourcePath = bmp4.filepath;
1198
+ /**
1199
+ * 弹幕解析的中间状态提示:下载已经完成、接下来是合成 + 烧录(要等一两分钟)。
1200
+ * 这时把「收到请求,开始下载」那条撤掉,换成「下载完成,正在添加弹幕」,
1201
+ * 让用户知道进度到哪了。
1202
+ */
1203
+ if (hasDanmaku) {
1204
+ // 用统一出口:它会撤掉上一条(「收到请求,开始下载」)**并把自己记下来**,
1205
+ // 等视频真正发出去时再被撤回(Base.ts 的发送流程会调 recallLastPanel)
1206
+ await (0, QqPanel_2.replyReplacing)(this.e, '下载完成,正在添加弹幕…');
1207
+ }
1208
+ if (!bmp3) {
1209
+ if (hasDanmaku) {
1210
+ node_karin_1.logger.debug(`开始烧录 ${danmakuList.length} 条弹幕...`);
1211
+ success = await (0, danmaku_1.burnBiliDanmaku)(bmp4.filepath, danmakuList, resultPath, {
1212
+ danmakuArea: Config_1.Config.bilibili.danmakuArea,
1213
+ verticalMode: Config_1.Config.bilibili.verticalMode,
1214
+ videoCodec: Config_1.Config.bilibili.videoCodec,
1215
+ danmakuFontSize: Config_1.Config.bilibili.danmakuFontSize,
1216
+ danmakuOpacity: Config_1.Config.bilibili.danmakuOpacity
1217
+ });
1218
+ sourcePath = resultPath;
1219
+ }
1220
+ else {
1221
+ success = true;
1222
+ }
1223
+ }
1224
+ else if (hasDanmaku) {
1225
+ node_karin_1.logger.debug(`开始合成视频并烧录 ${danmakuList.length} 条弹幕...`);
1226
+ success = await (0, danmaku_1.mergeAndBurnBili)(bmp4.filepath, bmp3.filepath, danmakuList, resultPath, {
1227
+ danmakuArea: Config_1.Config.bilibili.danmakuArea,
1228
+ verticalMode: Config_1.Config.bilibili.verticalMode,
1229
+ videoCodec: Config_1.Config.bilibili.videoCodec,
1230
+ danmakuFontSize: Config_1.Config.bilibili.danmakuFontSize,
1231
+ danmakuOpacity: Config_1.Config.bilibili.danmakuOpacity
1232
+ });
1233
+ sourcePath = resultPath;
1234
+ }
1235
+ else {
1236
+ success = await (0, utils_1.mergeVideoAudio)(bmp4.filepath, bmp3.filepath, resultPath);
1237
+ sourcePath = resultPath;
1238
+ }
1239
+ if (success) {
1240
+ const filePath = utils_1.Common.tempDri.video + `${Config_1.Config.app.removeCache ? 'tmp_' + Date.now() : this.downloadfilename}.mp4`;
1241
+ node_fs_1.default.renameSync(sourcePath, filePath);
1242
+ node_karin_1.logger.mark(`视频文件重命名完成: ${sourcePath.split('/').pop()} -> ${filePath.split('/').pop()}`);
1243
+ node_karin_1.logger.mark('正在尝试删除缓存文件');
1244
+ if (node_fs_1.default.existsSync(bmp4.filepath))
1245
+ await utils_1.Common.removeFile(bmp4.filepath, true);
1246
+ if (bmp3 && node_fs_1.default.existsSync(bmp3.filepath))
1247
+ await utils_1.Common.removeFile(bmp3.filepath, true);
1248
+ const stats = node_fs_1.default.statSync(filePath);
1249
+ const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2));
1250
+ if (fileSizeInMB > Config_1.Config.app.groupfilevalue) {
1251
+ // 使用文件上传
1252
+ await (0, utils_1.uploadFile)(this.e, { filepath: filePath, totalBytes: fileSizeInMB, originTitle: this.downloadfilename }, '', {
1253
+ useGroupFile: true
1254
+ });
1255
+ }
1256
+ else {
1257
+ /** 因为本地合成,没有视频直链 */
1258
+ await (0, utils_1.uploadFile)(this.e, { filepath: filePath, totalBytes: fileSizeInMB, originTitle: this.downloadfilename }, '');
1259
+ }
1260
+ }
1261
+ else {
1262
+ await utils_1.Common.removeFile(bmp4.filepath, true);
1263
+ if (bmp3)
1264
+ await utils_1.Common.removeFile(bmp3.filepath, true);
1265
+ }
1266
+ }
1267
+ break;
1268
+ }
1269
+ case false: {
1270
+ /**
1271
+ * 没登录(没配置 ck)时直接发直链。
1272
+ *
1273
+ * 注意 `durl` 不一定在 `playUrlData.data` 下:html5 直链接口偶发失败时会退回 amagi 的形状,
1274
+ * 那时候对象层级不一样 —— 直接写 `playUrlData.data.durl[0].url` 会抛
1275
+ * `Cannot read properties of undefined (reading '0')`(登录状态下选 360P 就会中招)。
1276
+ */
1277
+ const anonymousInner = playUrlData?.data?.data ?? playUrlData?.data;
1278
+ const directUrl = playUrlData?.data?.durl?.[0]?.url || anonymousInner?.durl?.[0]?.url;
1279
+ node_karin_1.logger.debug('视频 URL:', directUrl);
1280
+ if (!directUrl) {
1281
+ await this.e.reply('没有拿到可用的视频直链(未登录时部分稿件拿不到),可尝试【#B站登录】后再解析');
1282
+ return false;
1283
+ }
1284
+ // 如果需要烧录弹幕,先下载视频再烧录
1285
+ if ((this.forceBurnDanmaku || Config_1.Config.bilibili.burnDanmaku) && danmakuList.length > 0) {
1286
+ const videoFile = await (0, utils_1.downloadFile)(directUrl, {
1287
+ title: `Bil_V_tmp_${Date.now()}.mp4`,
1288
+ headers: this.headers
1289
+ });
1290
+ if (videoFile.filepath) {
1291
+ const resultPath = utils_1.Common.tempDri.video + `Bil_Result_${Date.now()}.mp4`;
1292
+ node_karin_1.logger.mark(`开始烧录 ${danmakuList.length} 条弹幕...`);
1293
+ const success = await (0, danmaku_1.burnBiliDanmaku)(videoFile.filepath, danmakuList, resultPath, {
1294
+ danmakuArea: Config_1.Config.bilibili.danmakuArea,
1295
+ verticalMode: Config_1.Config.bilibili.verticalMode,
1296
+ videoCodec: Config_1.Config.bilibili.videoCodec,
1297
+ danmakuFontSize: Config_1.Config.bilibili.danmakuFontSize,
1298
+ danmakuOpacity: Config_1.Config.bilibili.danmakuOpacity
1299
+ });
1300
+ if (success) {
1301
+ const filePath = utils_1.Common.tempDri.video + `${Config_1.Config.app.removeCache ? 'tmp_' + Date.now() : this.downloadfilename}.mp4`;
1302
+ node_fs_1.default.renameSync(resultPath, filePath);
1303
+ await utils_1.Common.removeFile(videoFile.filepath, true);
1304
+ const stats = node_fs_1.default.statSync(filePath);
1305
+ const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2));
1306
+ if (fileSizeInMB > Config_1.Config.app.groupfilevalue) {
1307
+ await (0, utils_1.uploadFile)(this.e, { filepath: filePath, totalBytes: fileSizeInMB, originTitle: this.downloadfilename }, '', {
1308
+ useGroupFile: true
1309
+ });
1310
+ }
1311
+ else {
1312
+ await (0, utils_1.uploadFile)(this.e, { filepath: filePath, totalBytes: fileSizeInMB, originTitle: this.downloadfilename }, '');
1313
+ }
1314
+ }
1315
+ else {
1316
+ await utils_1.Common.removeFile(videoFile.filepath, true);
1317
+ }
1318
+ }
1319
+ }
1320
+ else {
1321
+ await (0, utils_1.downloadVideo)(this.e, {
1322
+ video_url: directUrl,
1323
+ title: { timestampTitle: `tmp_${Date.now()}.mp4`, originTitle: `${this.downloadfilename}.mp4` }
1324
+ });
1325
+ }
1326
+ break;
1327
+ }
1328
+ default:
1329
+ break;
1330
+ }
1331
+ }
1332
+ }
1333
+ exports.Bilibili = Bilibili;
1334
+ const br = (data) => {
1335
+ return (data = data.replace(/\n/g, '<br>'));
1336
+ };
1337
+ /**
1338
+ * 时间格式化工具函数集合
1339
+ */
1340
+ exports.TimeFormatter = {
1341
+ /**
1342
+ * 格式化Unix时间戳为相对时间(如"2小时前")
1343
+ * @param timestamp Unix时间戳(秒)
1344
+ * @returns 格式化后的相对时间字符串
1345
+ */
1346
+ toRelative: (timestamp) => {
1347
+ try {
1348
+ const date = (0, date_fns_1.fromUnixTime)(timestamp);
1349
+ return (0, date_fns_1.formatDistanceToNow)(date, {
1350
+ addSuffix: true,
1351
+ locale: locale_1.zhCN
1352
+ });
1353
+ }
1354
+ catch (error) {
1355
+ node_karin_1.logger.warn('相对时间格式化失败:', error);
1356
+ return exports.TimeFormatter.toDateTime(timestamp);
1357
+ }
1358
+ },
1359
+ /**
1360
+ * 格式化Unix时间戳为日期时间(yyyy-MM-dd HH:mm)
1361
+ * @param timestamp Unix时间戳(秒)
1362
+ * @returns 格式化后的日期时间字符串
1363
+ */
1364
+ toDateTime: (timestamp) => {
1365
+ try {
1366
+ return (0, date_fns_1.format)((0, date_fns_1.fromUnixTime)(timestamp), 'yyyy-MM-dd HH:mm');
1367
+ }
1368
+ catch (error) {
1369
+ node_karin_1.logger.warn('日期时间格式化失败:', error);
1370
+ return '时间格式错误';
1371
+ }
1372
+ },
1373
+ /**
1374
+ * 格式化当前时间为日期时间(yyyy-MM-dd HH:mm:ss)
1375
+ * @returns 格式化后的当前时间字符串
1376
+ */
1377
+ now: () => {
1378
+ try {
1379
+ return (0, date_fns_1.format)(new Date(), 'yyyy-MM-dd HH:mm:ss');
1380
+ }
1381
+ catch (error) {
1382
+ node_karin_1.logger.warn('当前时间格式化失败:', error);
1383
+ return new Date().toISOString();
1384
+ }
1385
+ }
1386
+ };
1387
+ const qnd = {
1388
+ 6: '极速 240P',
1389
+ 16: '流畅 360P',
1390
+ 32: '清晰480P',
1391
+ 64: '高清720P',
1392
+ 74: '高帧率 720P60',
1393
+ 80: '高清 1080P',
1394
+ 112: '高码率 1080P+',
1395
+ 116: '高帧率 1080P60',
1396
+ 120: '超清 4K',
1397
+ 125: '真彩色 HDR ',
1398
+ 126: '杜比视界',
1399
+ 127: '超高清 8K'
1400
+ };
1401
+ /**
1402
+ * 将给定的图片源数组转换为一个新的对象数组,每个对象包含单个图片源
1403
+ * @param pic 一个包含图片源字符串的数组
1404
+ * @returns 返回一个对象数组,每个对象包含单个图片源
1405
+ */
1406
+ const cover = (pic) => {
1407
+ const imgArray = [];
1408
+ for (const i of pic) {
1409
+ const obj = {
1410
+ image_src: i.img_src
1411
+ };
1412
+ imgArray.push(obj);
1413
+ }
1414
+ return imgArray;
1415
+ };
1416
+ exports.cover = cover;
1417
+ /**
1418
+ * 生成装饰卡片数据
1419
+ * @param decorate 装饰对象,包含卡片的URL和颜色信息
1420
+ * @returns 返回装饰卡片数据对象或undefined
1421
+ */
1422
+ const generateDecorationCard = (decorate) => {
1423
+ if (!decorate)
1424
+ return undefined;
1425
+ return {
1426
+ card_url: decorate.card_url,
1427
+ colors: decorate.fan?.color_format?.colors || [],
1428
+ text: decorate.fan?.num_str || decorate.fan?.num_desc || ''
1429
+ };
1430
+ };
1431
+ exports.generateDecorationCard = generateDecorationCard;
1432
+ /**
1433
+ * 处理B站动态中的相关内容卡片(additional)
1434
+ * @param additional 动态中的 additional 字段
1435
+ * @returns 处理后的卡片数据,如果不支持则返回 undefined
1436
+ * @see https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/dynamic/all.md
1437
+ */
1438
+ const parseAdditionalCard = (additional) => {
1439
+ if (!additional)
1440
+ return undefined;
1441
+ switch (additional.type) {
1442
+ // 预约卡片(直播预约/视频预约)
1443
+ case 'ADDITIONAL_TYPE_RESERVE': {
1444
+ const reserve = additional.reserve;
1445
+ if (!reserve)
1446
+ return undefined;
1447
+ // button.type: 1-直播预约 2-视频预约
1448
+ let buttonText = '';
1449
+ if (reserve.button.type === 1) {
1450
+ // 直播预约:使用 jump_style.text(如"去观看"、"已结束")
1451
+ buttonText = reserve.button.jump_style?.text ?? '预约';
1452
+ }
1453
+ else {
1454
+ // 视频预约:使用 uncheck.text(如"预约")或 check.text(如"已预约")
1455
+ buttonText = reserve.button.uncheck?.text ?? reserve.button.check?.text ?? '预约';
1456
+ }
1457
+ return {
1458
+ type: 'ADDITIONAL_TYPE_RESERVE',
1459
+ reserve: {
1460
+ title: reserve.title,
1461
+ desc1: reserve.desc1?.text ?? '',
1462
+ desc2: reserve.desc2?.text ?? '',
1463
+ desc3: reserve.desc3?.text,
1464
+ buttonText
1465
+ }
1466
+ };
1467
+ }
1468
+ // 投票卡片
1469
+ case 'ADDITIONAL_TYPE_VOTE': {
1470
+ const vote = additional.vote;
1471
+ if (!vote)
1472
+ return undefined;
1473
+ return {
1474
+ type: 'ADDITIONAL_TYPE_VOTE',
1475
+ vote: {
1476
+ title: vote.title,
1477
+ desc: vote.desc,
1478
+ status: vote.status
1479
+ }
1480
+ };
1481
+ }
1482
+ // 通用卡片(游戏、活动等)
1483
+ case 'ADDITIONAL_TYPE_COMMON': {
1484
+ if (!additional.common)
1485
+ return undefined;
1486
+ return {
1487
+ type: 'ADDITIONAL_TYPE_COMMON',
1488
+ common: {
1489
+ cover: additional.common.cover,
1490
+ title: additional.common.title,
1491
+ desc1: additional.common.desc1,
1492
+ desc2: additional.common.desc2,
1493
+ button_text: additional.common.button?.jump_style?.text,
1494
+ head_text: additional.common.head_text,
1495
+ sub_type: additional.common.sub_type
1496
+ }
1497
+ };
1498
+ }
1499
+ // 视频跳转卡片(UGC)
1500
+ case 'ADDITIONAL_TYPE_UGC': {
1501
+ const ugc = additional.ugc;
1502
+ if (!ugc)
1503
+ return undefined;
1504
+ return {
1505
+ type: 'ADDITIONAL_TYPE_UGC',
1506
+ ugc: {
1507
+ cover: ugc.cover,
1508
+ title: ugc.title,
1509
+ duration: ugc.duration,
1510
+ play: ugc.stat?.play ?? ugc.desc_second?.split(' ')[0]?.replace('观看', '播放') ?? '',
1511
+ danmaku: ugc.stat?.danmaku ?? ugc.desc_second?.split(' ')[1]?.replace('弹幕', '') ?? ''
1512
+ }
1513
+ };
1514
+ }
1515
+ // 商品卡片
1516
+ case 'ADDITIONAL_TYPE_GOODS': {
1517
+ // TODO: 商品卡片暂未实现
1518
+ node_karin_1.logger.error('商品卡片暂未实现,请将这次解析的内容反馈给开发者进行适配!');
1519
+ return undefined;
1520
+ }
1521
+ // 充电专属抽奖
1522
+ case 'ADDITIONAL_TYPE_UPOWER_LOTTERY': {
1523
+ // TODO: 充电专属抽奖暂未实现
1524
+ node_karin_1.logger.error('充电专属抽奖暂未实现,请将这次解析的内容反馈给开发者进行适配!');
1525
+ return undefined;
1526
+ }
1527
+ default:
1528
+ node_karin_1.logger.error('此卡片内容暂未实现,请将这次解析的内容反馈给开发者进行适配!');
1529
+ return undefined;
1530
+ }
1531
+ };
1532
+ exports.parseAdditionalCard = parseAdditionalCard;
1533
+ const mapping_table = (type) => {
1534
+ const Array = {
1535
+ 1: ['DYNAMIC_TYPE_AV', 'DYNAMIC_TYPE_PGC', 'DYNAMIC_TYPE_UGC_SEASON'],
1536
+ 11: ['DYNAMIC_TYPE_DRAW'],
1537
+ 12: ['DYNAMIC_TYPE_ARTICLE'],
1538
+ 17: ['DYNAMIC_TYPE_LIVE_RCMD', 'DYNAMIC_TYPE_FORWARD', 'DYNAMIC_TYPE_WORD', 'DYNAMIC_TYPE_COMMON_SQUARE'],
1539
+ 19: ['DYNAMIC_TYPE_MEDIALIST']
1540
+ };
1541
+ for (const key in Array) {
1542
+ if (Array[key].includes(type)) {
1543
+ return parseInt(key, 10);
1544
+ }
1545
+ }
1546
+ return 1;
1547
+ };
1548
+ /**
1549
+ * 根据动态类型获取对应的oid(对象ID),用于后续评论接口调用
1550
+ * @param dynamicType 动态类型
1551
+ * @param dynamicData 动态数据
1552
+ * @returns
1553
+ */
1554
+ const oid = (dynamicType, dynamicData) => {
1555
+ switch (dynamicType) {
1556
+ case amagi_1.DynamicType.WORD:
1557
+ case amagi_1.DynamicType.FORWARD: {
1558
+ return dynamicData.data.item.id_str;
1559
+ }
1560
+ default: {
1561
+ return dynamicData.data.item.basic.rid_str.toString();
1562
+ }
1563
+ }
1564
+ };
1565
+ /**
1566
+ * 检出符合大小的视频流信息对象
1567
+ * @param accept_description 视频流清晰度列表
1568
+ * @param videoList 包含所有清晰度的视频流信息对象
1569
+ * @param audioUrl 音频流地址
1570
+ * @param bvid 视频bvid(BV号)
1571
+ * @returns
1572
+ */
1573
+ const bilibiliProcessVideos = async (qualityOptions, videoList, audioUrl) => {
1574
+ // 如果不是自动选择模式,直接根据配置的清晰度选择视频
1575
+ if (qualityOptions.qn !== 0 || Config_1.Config.bilibili.videoQuality !== 0) {
1576
+ const targetQuality = qualityOptions.qn ?? Config_1.Config.bilibili.videoQuality;
1577
+ // 尝试找到完全匹配的清晰度
1578
+ let matchedVideo = videoList.find((video) => video.id === targetQuality);
1579
+ // 如果没有完全匹配的清晰度,找最接近的
1580
+ if (!matchedVideo) {
1581
+ // 按照清晰度ID排序
1582
+ const sortedVideos = [...videoList].sort((a, b) => a.id - b.id);
1583
+ // 找到小于目标清晰度的最大值
1584
+ const lowerVideos = sortedVideos.filter((video) => video.id < targetQuality);
1585
+ const higherVideos = sortedVideos.filter((video) => video.id > targetQuality);
1586
+ if (lowerVideos.length > 0) {
1587
+ // 有小于目标清晰度的,取最大的
1588
+ matchedVideo = lowerVideos[lowerVideos.length - 1];
1589
+ }
1590
+ else if (higherVideos.length > 0) {
1591
+ // 没有小于目标清晰度的,取最小的
1592
+ matchedVideo = higherVideos[0];
1593
+ }
1594
+ else {
1595
+ // 如果都没有,取第一个(应该不会发生)
1596
+ matchedVideo = sortedVideos[0];
1597
+ }
1598
+ }
1599
+ // 更新视频列表和清晰度描述
1600
+ // accept_description 在免登录 / 只给 durl 的画质下可能是 undefined,
1601
+ // 直接 [0] 会抛 "Cannot read properties of undefined (reading '0')"(登录状态选 360P 就会中招)
1602
+ const matchedQuality = qnd[matchedVideo.id] || qualityOptions.accept_description?.[0] || ('qn' + matchedVideo.id);
1603
+ qualityOptions.accept_description = [matchedQuality];
1604
+ videoList = [matchedVideo];
1605
+ return {
1606
+ accept_description: qualityOptions.accept_description,
1607
+ videoList
1608
+ };
1609
+ }
1610
+ // 自动选择逻辑(videoQuality === 0)
1611
+ const results = {};
1612
+ for (const video of videoList) {
1613
+ const size = await (0, exports.getvideosize)(video.base_url, audioUrl, qualityOptions.bvid);
1614
+ results[video.id] = size;
1615
+ }
1616
+ // 将结果对象的值转换为数字,并找到最接近但不超过 qualityOptions.maxAutoVideoSize 或 Config.bilibili.maxAutoVideoSize 的值
1617
+ const sizes = Object.values(results).map((size) => parseFloat(size.replace('MB', '')));
1618
+ let closestId = null;
1619
+ let smallestDifference = Infinity;
1620
+ sizes.forEach((size, index) => {
1621
+ if (size <= (qualityOptions?.maxAutoVideoSize ?? Config_1.Config.bilibili.maxAutoVideoSize)) {
1622
+ const difference = Math.abs(size - (qualityOptions?.maxAutoVideoSize ?? Config_1.Config.bilibili.maxAutoVideoSize));
1623
+ if (difference < smallestDifference) {
1624
+ smallestDifference = difference;
1625
+ closestId = Object.keys(results)[index];
1626
+ }
1627
+ }
1628
+ });
1629
+ if (closestId !== null) {
1630
+ // 找到最接近但不超过文件大小限制的视频清晰度
1631
+ const closestQuality = qnd[Number(closestId)];
1632
+ // 更新 OBJECT.DATA.data.accept_description
1633
+ qualityOptions.accept_description = qualityOptions.accept_description.filter((desc) => desc === closestQuality);
1634
+ if (qualityOptions.accept_description.length === 0) {
1635
+ qualityOptions.accept_description = [closestQuality];
1636
+ }
1637
+ // 找到对应的视频对象
1638
+ const video = videoList.find((video) => video.id === Number(closestId));
1639
+ // 更新 OBJECT.DATA.data.dash.video 数组
1640
+ videoList = [video];
1641
+ }
1642
+ else {
1643
+ // 如果没有找到符合条件的视频,使用最低画质的视频对象
1644
+ videoList = [[...videoList].pop()];
1645
+ // 更新 OBJECT.DATA.data.accept_description 为最低画质的描述
1646
+ qualityOptions.accept_description = [[...qualityOptions.accept_description].pop()];
1647
+ }
1648
+ return {
1649
+ accept_description: qualityOptions.accept_description,
1650
+ videoList
1651
+ };
1652
+ };
1653
+ exports.bilibiliProcessVideos = bilibiliProcessVideos;
1654
+ /**
1655
+ * [bilibili] 获取视频和音频的总大小
1656
+ * @param videourl - 视频流URL
1657
+ * @param audiourl - 音频流URL,没有音频流(如纯视频稿件)时传 undefined,此时只统计视频流大小
1658
+ * @param bvid - 视频BV号
1659
+ * @returns 返回视频和音频总大小(MB),保留2位小数
1660
+ */
1661
+ const getvideosize = async (videourl, audiourl, bvid) => {
1662
+ try {
1663
+ const videoheaders = await new utils_1.Networks({
1664
+ url: videourl,
1665
+ headers: {
1666
+ ...utils_1.baseHeaders,
1667
+ Referer: `https://www.bilibili.com/video/${bvid}`,
1668
+ Cookie: Config_1.Config.amagi.cookies.bilibili
1669
+ }
1670
+ }).getHeaders();
1671
+ const audioheaders = audiourl
1672
+ ? await new utils_1.Networks({
1673
+ url: audiourl,
1674
+ headers: {
1675
+ ...utils_1.baseHeaders,
1676
+ Referer: `https://www.bilibili.com/video/${bvid}`,
1677
+ Cookie: Config_1.Config.amagi.cookies.bilibili
1678
+ }
1679
+ }).getHeaders()
1680
+ : undefined;
1681
+ const videoSize = (0, utils_1.extractTotalBytesFromHeaders)(videoheaders);
1682
+ const audioSize = audioheaders ? (0, utils_1.extractTotalBytesFromHeaders)(audioheaders) : 0;
1683
+ const videoSizeInMB = (videoSize / (1024 * 1024)).toFixed(2);
1684
+ const audioSizeInMB = (audioSize / (1024 * 1024)).toFixed(2);
1685
+ const totalSizeInMB = parseFloat(videoSizeInMB) + parseFloat(audioSizeInMB);
1686
+ return totalSizeInMB.toFixed(2);
1687
+ }
1688
+ catch (error) {
1689
+ node_karin_1.logger.warn(`[koishi-plugin-kkk] 获取视频大小失败: ${error instanceof Error ? error.message : String(error)}`);
1690
+ return '0.00';
1691
+ }
1692
+ };
1693
+ exports.getvideosize = getvideosize;
1694
+ /**
1695
+ * 格式化视频统计信息为三行,每行两个数据项,并保持对齐
1696
+ */
1697
+ const formatVideoStats = (view, danmaku, like, coin, share, favorite) => {
1698
+ // 计算每个数据项的文本
1699
+ const viewText = `📊 播放量: ${(0, utils_1.Count)(view)}`;
1700
+ const danmakuText = `💬 弹幕: ${(0, utils_1.Count)(danmaku)}`;
1701
+ const likeText = `👍 点赞: ${(0, utils_1.Count)(like)}`;
1702
+ const coinText = `🪙 投币: ${(0, utils_1.Count)(coin)}`;
1703
+ const shareText = `🔄 转发: ${(0, utils_1.Count)(share)}`;
1704
+ const favoriteText = `⭐ 收藏: ${(0, utils_1.Count)(favorite)}`;
1705
+ // 找出第一列中最长的项的长度
1706
+ const firstColItems = [viewText, likeText, shareText];
1707
+ const maxFirstColLength = Math.max(...firstColItems.map((item) => getStringDisplayWidth(item)));
1708
+ // 构建三行文本,确保第二列对齐
1709
+ const line1 = alignTwoColumns(viewText, danmakuText, maxFirstColLength);
1710
+ const line2 = alignTwoColumns(likeText, coinText, maxFirstColLength);
1711
+ const line3 = alignTwoColumns(shareText, favoriteText, maxFirstColLength);
1712
+ return `${line1}\n${line2}\n${line3}`;
1713
+ };
1714
+ /**
1715
+ * 对齐两列文本
1716
+ */
1717
+ const alignTwoColumns = (col1, col2, targetLength) => {
1718
+ // 计算需要添加的空格数量
1719
+ const col1Width = getStringDisplayWidth(col1);
1720
+ const spacesNeeded = targetLength - col1Width + 5; // 5是两列之间的固定间距
1721
+ // 添加空格使两列对齐
1722
+ return col1 + ' '.repeat(spacesNeeded) + col2;
1723
+ };
1724
+ /**
1725
+ * 获取字符串在显示时的实际宽度
1726
+ * 考虑到不同字符的显示宽度不同(如中文、emoji等)
1727
+ */
1728
+ const getStringDisplayWidth = (str) => {
1729
+ let width = 0;
1730
+ for (let i = 0; i < str.length; i++) {
1731
+ const code = str.codePointAt(i);
1732
+ if (!code)
1733
+ continue;
1734
+ // 处理emoji和特殊Unicode字符
1735
+ if (code > 0xffff) {
1736
+ width += 2; // emoji通常占用2个字符宽度
1737
+ i++; // 跳过代理对的后半部分
1738
+ }
1739
+ else if (
1740
+ // 处理中文字符和其他全角字符
1741
+ (code >= 0x3000 && code <= 0x9fff) || // 中文字符范围
1742
+ (code >= 0xff00 && code <= 0xffef) || // 全角ASCII、全角标点
1743
+ code === 0x2026 || // 省略号
1744
+ code === 0x2014 || // 破折号
1745
+ (code >= 0x2e80 && code <= 0x2eff) || // CJK部首补充
1746
+ (code >= 0x3000 && code <= 0x303f) || // CJK符号和标点
1747
+ (code >= 0x31c0 && code <= 0x31ef) || // CJK笔画
1748
+ (code >= 0x3200 && code <= 0x32ff) || // 封闭式CJK字母和月份
1749
+ (code >= 0x3300 && code <= 0x33ff) || // CJK兼容
1750
+ (code >= 0xac00 && code <= 0xd7af) || // 朝鲜文音节
1751
+ (code >= 0xf900 && code <= 0xfaff) || // CJK兼容表意文字
1752
+ (code >= 0xfe30 && code <= 0xfe4f) // CJK兼容形式
1753
+ ) {
1754
+ width += 2;
1755
+ }
1756
+ else if (code === 0x200d || (code >= 0xfe00 && code <= 0xfe0f) || (code >= 0x1f3fb && code <= 0x1f3ff)) {
1757
+ // emoji修饰符和连接符
1758
+ width += 0; // 这些字符不增加宽度,它们是修饰符
1759
+ }
1760
+ else {
1761
+ // 普通ASCII字符
1762
+ width += 1;
1763
+ }
1764
+ }
1765
+ return width;
1766
+ };
1767
+ /**
1768
+ * 提取专栏中的所有图片URL
1769
+ * @param content
1770
+ * @returns
1771
+ */
1772
+ const extractArticleImages = (content) => {
1773
+ const images = [];
1774
+ // 处理 opus 格式(结构化数据)
1775
+ if (content.opus?.content?.paragraphs) {
1776
+ for (const paragraph of content.opus.content.paragraphs) {
1777
+ // para_type === 2 表示图片段落
1778
+ if (paragraph.para_type === 2 && paragraph.pic?.pics) {
1779
+ for (const pic of paragraph.pic.pics) {
1780
+ if (pic.url) {
1781
+ // 确保使用 https 协议
1782
+ const url = pic.url.startsWith('//') ? `https:${pic.url}` : pic.url;
1783
+ images.push(url);
1784
+ }
1785
+ }
1786
+ }
1787
+ }
1788
+ }
1789
+ // 处理 content 格式(HTML字符串)
1790
+ if (content.content && typeof content.content === 'string') {
1791
+ // 使用正则提取所有 img 标签的 src
1792
+ const imgRegex = /<img[^>]+src="([^"]+)"/gi;
1793
+ let match;
1794
+ while ((match = imgRegex.exec(content.content)) !== null) {
1795
+ let url = match[1];
1796
+ // 修复协议
1797
+ if (url.startsWith('//')) {
1798
+ url = `https:${url}`;
1799
+ }
1800
+ images.push(url);
1801
+ }
1802
+ }
1803
+ return images;
1804
+ };
1805
+ exports.extractArticleImages = extractArticleImages;