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,383 @@
1
+ /**
2
+ * 超长图片的「切片 + markdown 拼接」发送。
3
+ *
4
+ * 背景:评论区卡片会把「主评论 + 楼中楼」全部渲染进一张图,实测能长到 **2880×40000**
5
+ * (1.15 亿像素、7MB)—— QQ 直接拒收(报 `[40093011] 上传文件大小超过限制`),
6
+ * 表现就是「评论卡发不出来」。
7
+ *
8
+ * 解法:把长图按固定高度切成若干片,每片单独上传,再用 **一条 markdown 消息** 拼起来:
9
+ *
10
+ * ![#2880px #6000px](url1)
11
+ * ![#2880px #6000px](url2)
12
+ *
13
+ * QQ 对同一 markdown 里连续的图片是**紧贴渲染**的,所以视觉上仍然是一整张卡片(严丝合缝),
14
+ * 而每片都在 QQ 的预览尺寸内,不会再被拒。
15
+ */
16
+ import fs from 'node:fs'
17
+ import os from 'node:os'
18
+ import path from 'node:path'
19
+
20
+ import { logger, segment, type Message } from 'node-karin'
21
+
22
+ import { commandInvocation, tryGetRuntime } from '../../../compat/runtime'
23
+ import { getImageMetadata } from '@/module/utils/Render'
24
+
25
+ /**
26
+ * 每片的**目标**高度。
27
+ *
28
+ * 一开始用的 6000:QQ 会把这么高的图整体缩小显示,结果前几片字小到看不清;
29
+ * 而最后一片因为短,反而被按宽度铺满,看起来比别的「宽」。
30
+ * 现在改成 2000 —— 每片都在 QQ 的舒适区内,字够大;同时下面的算法保证**每片等长**。
31
+ */
32
+ const SLICE_HEIGHT = 2000
33
+
34
+ /** 切片前先把宽度压到这个值(卡片本来 2880 宽,QQ 显示用不到,缩一半解码量降到 1/4) */
35
+ const SLICE_WIDTH = 1440
36
+
37
+ /** 把 data URI 或本地路径读成 Buffer */
38
+ const readImage = (source: string): Buffer | null => {
39
+ try {
40
+ if (source.startsWith('data:')) {
41
+ const comma = source.indexOf(',')
42
+ return Buffer.from(source.slice(comma + 1), 'base64')
43
+ }
44
+ if (fs.existsSync(source)) return fs.readFileSync(source)
45
+ return null
46
+ } catch {
47
+ return null
48
+ }
49
+ }
50
+
51
+ /** 用 ffmpeg 裁一段出来(项目本来就依赖 ffmpeg,不额外引图像库) */
52
+ const cropWithFfmpeg = async (
53
+ input: string,
54
+ output: string,
55
+ width: number,
56
+ height: number,
57
+ y: number,
58
+ /** 自定义 filter(给缩放步骤用);不传就是按宽度/高度/偏移裁剪 */
59
+ customFilter?: string
60
+ ): Promise<boolean> => {
61
+ const filter = customFilter ?? ('crop=' + width + ':' + height + ':0:' + y)
62
+ const runtime: any = tryGetRuntime()
63
+ const ffmpeg: any = runtime?.ctx?.ffmpeg
64
+ const args = ['-y', '-i', input, '-vf', filter, '-frames:v', '1', output]
65
+ /**
66
+ * **不要用宿主的 ffmpeg 服务** —— 实测 `ffmpeg.builder()...run()` 会**卡住不返回**
67
+ * (既没有 ffmpeg 进程、也没有 CPU 占用、更没有任何日志,表现就是「怎么没反应了」)。
68
+ * 命令行方式已经验证可用,直接用它。
69
+ */
70
+ try {
71
+ const { spawn } = await import('node:child_process')
72
+ const bin = process.env.FFMPEG_PATH || 'ffmpeg'
73
+ await new Promise<void>((resolve, reject) => {
74
+ const proc = spawn(bin, args, { stdio: 'ignore' })
75
+ proc.on('error', reject)
76
+ proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error('ffmpeg exit ' + code))))
77
+ })
78
+ return true
79
+ } catch (error: any) {
80
+ logger.warn('[图片切片] ffmpeg 裁剪失败: ' + String(error?.message ?? error))
81
+ return false
82
+ }
83
+ }
84
+
85
+ /** 上传到宿主 assets,拿到 QQ 能访问的 https 地址 */
86
+ const uploadSlice = async (buffer: Buffer, name: string, attempts = 3): Promise<string | null> => {
87
+ for (let attempt = 1; attempt <= attempts; attempt++) {
88
+ const url = await uploadSliceOnce(buffer, name)
89
+ if (url) return url
90
+ if (attempt < attempts) {
91
+ logger.mark('[图片切片] 上传第 ' + attempt + ' 次失败,' + attempt * 1000 + 'ms 后重试')
92
+ await new Promise((resolve) => setTimeout(resolve, attempt * 1000))
93
+ }
94
+ }
95
+ return null
96
+ }
97
+
98
+ /** 单次上传(重试逻辑在外面) */
99
+ const uploadSliceOnce = async (buffer: Buffer, name: string): Promise<string | null> => {
100
+ try {
101
+ const assets: any = (tryGetRuntime() as any)?.ctx?.assets
102
+ if (!assets?.upload) return null
103
+ const uploaded: any = await assets.upload('data:image/jpeg;base64,' + buffer.toString('base64'), name)
104
+ const url = typeof uploaded === 'string' ? uploaded : uploaded?.url
105
+ return url && /^https?:\/\//i.test(String(url)) ? String(url) : null
106
+ } catch (error: any) {
107
+ logger.warn('[图片切片] 上传失败: ' + String(error?.message ?? error))
108
+ return null
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 发送可能超长的图片:短图直接发,长图切片后用一条 markdown 拼接。
114
+ *
115
+ * @param e 消息事件
116
+ * @param source data URI 或本地图片路径
117
+ * @returns 是否已发送
118
+ */
119
+ export const sendSlicedImage = async (e: Message, input: any): Promise<boolean> => {
120
+ /**
121
+ * 入参形态有三种,全都要认:
122
+ * 1. data URI / 本地路径(字符串)
123
+ * 2. segment.image(...) 造出来的元素(src 在 attrs 上)
124
+ * 3. 上面两种的**数组** —— Render() 对多图模板返回的就是数组。
125
+ * 之前只处理前两种,数组进来时取不到 src 会静默失败(切片一直没生效就是这个原因)。
126
+ */
127
+ const first = Array.isArray(input) ? input[0] : input
128
+ const source: string = typeof first === 'string'
129
+ ? first
130
+ : String(first?.attrs?.src ?? first?.data?.file ?? first?.data?.url ?? '')
131
+ const buffer = readImage(source)
132
+ if (!buffer) {
133
+ logger.warn('[图片切片] 取不到图片内容(入参 ' + (Array.isArray(input) ? 'array' : typeof input) + '),回退原逻辑')
134
+ return false
135
+ }
136
+ /**
137
+ * **按需切片**(默认开,配置项 sliceImageOnDemand):
138
+ * - 图片 ≤ 20MB 且不超高 → 先按普通图片发一次;
139
+ * - 发出去**没有消息 ID**(QQ 拒收时就是这样,不抛异常)或抛异常 → 才切片重发;
140
+ * - 图片本身就超过 20MB → 直接切片,不用白试一次。
141
+ * 关掉开关就一律普通发送(超高的卡会被 QQ 拒收,但这是用户的选择)。
142
+ */
143
+ const runtimeConfig: any = (tryGetRuntime()?.config as any) ?? {}
144
+ /**
145
+ * **切片只在 QQ 适配器上生效**(用户要求)。
146
+ *
147
+ * 切片是为了绕开 QQ 官方 bot 的图片上传限制(单图体积/像素上限),
148
+ * 其它平台没这个问题,硬切只会增加消息条数、破坏观感。
149
+ */
150
+ const platform = String((e as any)?.bot?.bot?.platform ?? (e as any)?.platform ?? (e as any)?.bot?.platform ?? '')
151
+ if (platform && !/qq/i.test(platform)) {
152
+ logger.debug('[图片切片] 当前平台 ' + platform + ' 不是 QQ,按普通图片发送')
153
+ await e.reply(segment.image(source))
154
+ return true
155
+ }
156
+ const onDemand = runtimeConfig.sliceImageOnDemand !== false
157
+ const sliceHeight = Math.max(300, Number(runtimeConfig.sliceImageHeight) || SLICE_HEIGHT)
158
+ const IMAGE_SIZE_LIMIT = 20 * 1024 * 1024
159
+
160
+ const meta = getImageMetadata(buffer)
161
+ let width = Number(meta.width) || 0
162
+ let height = Number(meta.height) || 0
163
+ // 读不出尺寸时用 ffprobe 兜一次,绝不因为读不到尺寸就放弃切片
164
+ if (!width || !height) {
165
+ try {
166
+ const { spawnSync } = await import('node:child_process')
167
+ const probe = spawnSync('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'csv=p=0', '-'], { input: buffer })
168
+ const parts = String(probe.stdout ?? '').trim().split(',')
169
+ width = Number(parts[0]) || 0
170
+ height = Number(parts[1]) || 0
171
+ } catch { /* 兜底失败按 0 处理 */ }
172
+ }
173
+ logger.mark('[图片切片] 收到图片 ' + width + 'x' + height + '(' + (buffer.length / 1024).toFixed(0) + ' KB)')
174
+ /**
175
+ * 普通发送:**收到消息 ID 才算成功**(QQ 拒收时不抛异常、只返回空)。
176
+ *
177
+ * 另外加了超时 —— 超高图片(实测 2880×15520)上传时适配器会**长时间卡住不返回**,
178
+ * 不加超时整条流程就停在这里,用户看到的就是「没反应」。15 秒没结果就当作失败去切片。
179
+ */
180
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
181
+
182
+ const tryNormalSendOnce = async (): Promise<boolean> => {
183
+ const sendPromise = (async () => {
184
+ try {
185
+ const result: any = await e.reply(segment.image(source))
186
+ /**
187
+ * 失败有三种表现,缺一不可(用户提醒:**不一定只是拿不到消息 ID**,QQ 会直接返回错误信息):
188
+ * 1. 抛异常(网络/TLS 问题最常见,比如证书校验失败)
189
+ * 2. 拿不到 messageId(适配器静默失败)
190
+ * 3. **返回体里带错误**(result.error / message 里含 error/失败/超过限制 之类)
191
+ */
192
+ const errorText = String(result?.error?.message ?? result?.error ?? result?.message ?? result?.data?.error ?? '')
193
+ if (errorText && /error|fail|失败|超过|限制|拒绝|invalid|denied/i.test(errorText)) {
194
+ logger.mark('[图片切片] 普通发送返回错误信息: ' + errorText.slice(0, 120))
195
+ return false
196
+ }
197
+ return Boolean(result?.messageId)
198
+ } catch (error: any) {
199
+ logger.mark('[图片切片] 普通发送抛错,改走切片: ' + String(error?.message ?? error).slice(0, 120))
200
+ return false
201
+ }
202
+ })()
203
+ const timeoutPromise = new Promise<boolean>((resolve) => {
204
+ const timer = setTimeout(() => resolve(false), 15000)
205
+ timer.unref?.()
206
+ })
207
+ return await Promise.race([sendPromise, timeoutPromise])
208
+ }
209
+
210
+ /**
211
+ * 带**重试**的普通发送(用户要求)。
212
+ *
213
+ * 实测失败大多是**瞬时**的:DNS 抖动、连接被重置、TLS 证书对不上
214
+ * (你环境里就有把 COS 域名解析到错服务器、返回「只有 IP 的证书」的情况)。
215
+ * 这种一失败就切片属于过度反应,先退避重试几次,成功就不切。
216
+ */
217
+ const tryNormalSend = async (attempts = 3): Promise<boolean> => {
218
+ for (let attempt = 1; attempt <= attempts; attempt++) {
219
+ if (await tryNormalSendOnce()) return true
220
+ if (attempt < attempts) {
221
+ logger.mark('[图片切片] 普通发送第 ' + attempt + ' 次失败,' + attempt * 800 + 'ms 后重试')
222
+ await sleep(attempt * 800)
223
+ }
224
+ }
225
+ return false
226
+ }
227
+
228
+ // 开关关闭 → 永远普通发送
229
+ if (!onDemand) {
230
+ await e.reply(segment.image(source))
231
+ return true
232
+ }
233
+ /**
234
+ * 顺序就是用户要的:**先按普通图片发一次**,
235
+ * - 拿到消息 ID → 成功,结束(大多数卡片都走这里,不会多此一举切片)
236
+ * - 拿不到消息 ID(QQ 拒收就是这样,不抛异常)/ 抛异常 → 再切片重发
237
+ * 只有图片本身就超过 20MB 才跳过这次尝试,直接切。
238
+ */
239
+ const oversized = buffer.length > IMAGE_SIZE_LIMIT
240
+ if (oversized) {
241
+ logger.mark('[图片切片] 图片 ' + (buffer.length / 1024 / 1024).toFixed(1) + 'MB 超过 20MB 限制,直接切片')
242
+ } else if (await tryNormalSend()) {
243
+ return true
244
+ } else {
245
+ logger.mark('[图片切片] 普通发送没有拿到消息 ID(多为 QQ 拒收),改走切片')
246
+ }
247
+ if (!width || !height || height <= sliceHeight) {
248
+ // 尺寸本身就在范围内、只是发不出去:没得切,原样再试一次
249
+ await e.reply(segment.image(source))
250
+ return true
251
+ }
252
+
253
+ const runtime: any = tryGetRuntime()
254
+ const tmpDir = path.join(os.tmpdir(), 'kkk-slice-' + Date.now())
255
+ try {
256
+ fs.mkdirSync(tmpDir, { recursive: true })
257
+ const original = path.join(tmpDir, 'full.jpg')
258
+ fs.writeFileSync(original, buffer)
259
+
260
+ /**
261
+ * **先整体缩一次再从缩略图切片** —— 这一步是性能关键。
262
+ *
263
+ * 卡片实测 2880×40000(1.15 亿像素):直接在原图上裁 7 刀,每刀都要把整图重新解码一遍,
264
+ * 实测要跑好几分钟(用户会看到「怎么没反应了」)。缩到一半宽再裁,
265
+ * 解码量降到 1/4,7 刀总共一两秒,而且 QQ 显示尺寸本来就不需要 2880 宽。
266
+ */
267
+ const scaleWidth = Math.min(width, SLICE_WIDTH)
268
+ const input = path.join(tmpDir, 'small.jpg')
269
+ const scaled = scaleWidth < width
270
+ ? await cropWithFfmpeg(original, input, 0, 0, 0, 'scale=' + scaleWidth + ':-1')
271
+ : true
272
+ const useInput = scaled && fs.existsSync(input) ? input : original
273
+ const useHeight = scaled && scaleWidth < width ? Math.round((height * scaleWidth) / width) : height
274
+ const useWidth = scaled && scaleWidth < width ? scaleWidth : width
275
+ if (useInput !== original) {
276
+ logger.mark('[图片切片] 已缩放到 ' + useWidth + 'x' + useHeight + ' 再切片')
277
+ }
278
+
279
+ const parts: string[] = []
280
+ /**
281
+ * **每片等长**:先按最大高度算出片数,再把总高平均分配。
282
+ * 这样最后一片不会因为矮而被 QQ 按宽度铺开(用户说的「最后一张太宽」)。
283
+ * 除不尽时最后一片会与前一片重叠几个像素,视觉上几乎看不出来,比留白干净。
284
+ */
285
+ const total = Math.max(1, Math.ceil(useHeight / sliceHeight))
286
+ const each = Math.ceil(useHeight / total)
287
+ for (let index = 0; index < total; index++) {
288
+ const sliceHeight = each
289
+ // 最后一片贴底裁,保证高度一致(宁可重叠几像素,也不要短一截)
290
+ const offset = index === total - 1 ? Math.max(0, useHeight - each) : index * each
291
+ const output = path.join(tmpDir, 'slice-' + index + '.jpg')
292
+ const ok = await cropWithFfmpeg(useInput, output, useWidth, sliceHeight, offset)
293
+ if (!ok || !fs.existsSync(output)) break
294
+ const url = await uploadSlice(fs.readFileSync(output), 'kkk-slice-' + index + '.jpg')
295
+ if (!url) break
296
+ // 尺寸写死成实际像素:QQ 会按这个尺寸渲染,连续图片紧贴 = 视觉上仍是一整张
297
+ parts.push('![#' + useWidth + 'px #' + sliceHeight + 'px](' + url + ')')
298
+ }
299
+
300
+ if (!parts.length) {
301
+ // 切片失败就退回原图,至少不是完全没反应
302
+ await e.reply(segment.image(source))
303
+ return true
304
+ }
305
+ logger.mark('[图片切片] 长图 ' + width + 'x' + height + ' 已切成 ' + parts.length + ' 片,用 markdown 拼接发送')
306
+ await e.reply(segment.markdown(parts.join(String.fromCharCode(10))))
307
+ return true
308
+ } finally {
309
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }) } catch { /* 清理失败无所谓 */ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * 只切片、不发送:把可能超长的图片变成**一个 markdown 元素**(调用方自己决定怎么发)。
315
+ *
316
+ * 错误卡片就用它 —— 实测错误卡片能到 2880x40000 / 45MB(堆栈越长越夸张),
317
+ * 直接发必然被 QQ 拒收,等于「报错本身也发不出来」。
318
+ */
319
+ export const sliceImageToMarkdown = async (input: any): Promise<any | null> => {
320
+ const first = Array.isArray(input) ? input[0] : input
321
+ const source: string = typeof first === 'string'
322
+ ? first
323
+ : String(first?.attrs?.src ?? first?.data?.file ?? first?.data?.url ?? '')
324
+ if (!source) return null
325
+ const runtimeConfig: any = (tryGetRuntime()?.config as any) ?? {}
326
+ // 合并成一条 markdown 也是 QQ 专属优化(绕过图片上传限制):其它平台逐张发即可
327
+ const platform = String((e as any)?.bot?.bot?.platform ?? (e as any)?.platform ?? (e as any)?.bot?.platform ?? '')
328
+ if (platform && !/qq/i.test(platform)) {
329
+ for (const one of valid) await e.reply(segment.image(one))
330
+ return true
331
+ }
332
+ const sliceHeight = Math.max(300, Number(runtimeConfig.sliceImageHeight) || SLICE_HEIGHT)
333
+ const tmpDir = path.join(os.tmpdir(), 'kkk-sliceonly-' + Date.now())
334
+ try {
335
+ fs.mkdirSync(tmpDir, { recursive: true })
336
+ const buffer = readImage(source)
337
+ if (!buffer) return null
338
+ const meta = getImageMetadata(buffer)
339
+ let width = Number(meta.width) || 0
340
+ let height = Number(meta.height) || 0
341
+ if (!width || !height) {
342
+ try {
343
+ const { spawnSync } = await import('node:child_process')
344
+ const probe = spawnSync('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'csv=p=0', '-'], { input: buffer })
345
+ const ps = String(probe.stdout ?? '').trim().split(',')
346
+ width = Number(ps[0]) || 0
347
+ height = Number(ps[1]) || 0
348
+ } catch { /* 忽略 */ }
349
+ }
350
+ if (!width || !height) return null
351
+ // 不高就直接一段
352
+ if (height <= sliceHeight) {
353
+ const url = await uploadSlice(buffer, 'kkk-one.jpg')
354
+ return url ? segment.markdown('![#' + width + 'px #' + height + 'px](' + url + ')') : null
355
+ }
356
+ // 超高:先缩放再等分切片
357
+ const original = path.join(tmpDir, 'full.jpg')
358
+ fs.writeFileSync(original, buffer)
359
+ const scaleWidth = Math.min(width, SLICE_WIDTH)
360
+ const small = path.join(tmpDir, 'small.jpg')
361
+ const scaled = scaleWidth < width ? await cropWithFfmpeg(original, small, 0, 0, 0, 'scale=' + scaleWidth + ':-1') : false
362
+ const useInput = scaled && fs.existsSync(small) ? small : original
363
+ const useWidth = scaled ? scaleWidth : width
364
+ const useHeight = scaled ? Math.round((height * scaleWidth) / width) : height
365
+ const total = Math.max(1, Math.ceil(useHeight / sliceHeight))
366
+ const each = Math.ceil(useHeight / total)
367
+ const parts: string[] = []
368
+ for (let index = 0; index < total; index++) {
369
+ const offset = index === total - 1 ? Math.max(0, useHeight - each) : index * each
370
+ const output = path.join(tmpDir, 'slice-' + index + '.jpg')
371
+ const ok = await cropWithFfmpeg(useInput, output, useWidth, each, offset)
372
+ if (!ok || !fs.existsSync(output)) break
373
+ const url = await uploadSlice(fs.readFileSync(output), 'kkk-slice-' + index + '.jpg')
374
+ if (!url) break
375
+ parts.push('![#' + useWidth + 'px #' + each + 'px](' + url + ')')
376
+ }
377
+ if (!parts.length) return null
378
+ logger.mark('[图片切片] 错误卡片等超长图已切成 ' + parts.length + ' 段(markdown)')
379
+ return segment.markdown(parts.join(String.fromCharCode(10)))
380
+ } finally {
381
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }) } catch { /* 忽略 */ }
382
+ }
383
+ }
@@ -0,0 +1,297 @@
1
+ // 参考文章 https://blog.0to1.cf/posts/cn-motion-photo-format
2
+ // 参考文章 https://blog.zzbd.org/motion-live-photo-webui
3
+ // 参考项目 https://github.com/flashlab/motion-live-photo
4
+ import fs from 'node:fs'
5
+ import path from 'node:path'
6
+
7
+ import { ffmpeg, logger } from 'node-karin'
8
+
9
+ import { Common } from './Common'
10
+ import { Config } from './Config'
11
+
12
+ const xmpHeaderBuffer = Buffer.from('http://ns.adobe.com/xap/1.0/\u0000', 'utf8')
13
+ const oppoExifHex =
14
+ 'FFE100724578696600004D4D002A0000000800040100000400000001000005A001010004000000010000043C87690004000000010000003E011200030000000100000000000000000002928600020000000E0000005C920800040000000100000000000000006F706C75735F3833383836303800'
15
+ const xiaomiExifHex =
16
+ 'FFE1007E4578696600004D4D002A0000000800040100000400000001000005A001010004000000010000043C01120003000000010000000087690004000000010000003E000000000003889700010000000101000000920800040000000100000000928600020000000E00000068000000006F706C75735F3833383836303800'
17
+ const huaweiHonorLiveIdFallback = 1915884
18
+
19
+ type MotionPhotoSystem = 'google' | 'xiaomi' | 'oppo' | 'huawei_honor'
20
+
21
+ const isJpegBuffer = (fileBuffer: Buffer): boolean => {
22
+ return fileBuffer.length > 2 && fileBuffer[0] === 0xff && fileBuffer[1] === 0xd8
23
+ }
24
+
25
+ const getJpegDimensions = (jpegBuffer: Buffer): { width: number; height: number } | null => {
26
+ let offset = 2
27
+ while (offset + 9 < jpegBuffer.length) {
28
+ if (jpegBuffer[offset] !== 0xff) {
29
+ offset += 1
30
+ continue
31
+ }
32
+ const marker = jpegBuffer[offset + 1]
33
+ if (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
34
+ offset += 2
35
+ continue
36
+ }
37
+ if (offset + 3 >= jpegBuffer.length) return null
38
+ const segmentLength = jpegBuffer.readUInt16BE(offset + 2)
39
+ if (segmentLength < 2 || offset + 2 + segmentLength > jpegBuffer.length) return null
40
+ const isSofMarker =
41
+ (marker >= 0xc0 && marker <= 0xc3) ||
42
+ (marker >= 0xc5 && marker <= 0xc7) ||
43
+ (marker >= 0xc9 && marker <= 0xcb) ||
44
+ (marker >= 0xcd && marker <= 0xcf)
45
+ if (isSofMarker && segmentLength >= 7) {
46
+ const height = jpegBuffer.readUInt16BE(offset + 5)
47
+ const width = jpegBuffer.readUInt16BE(offset + 7)
48
+ if (width > 0 && height > 0) return { width, height }
49
+ return null
50
+ }
51
+ offset += 2 + segmentLength
52
+ }
53
+ return null
54
+ }
55
+
56
+ const hasExifApp1 = (jpegBuffer: Buffer): boolean => {
57
+ return jpegBuffer.includes(Buffer.from('Exif\u0000\u0000', 'binary'))
58
+ }
59
+
60
+ const buildExifSegment = (hex: string, width: number, height: number): Buffer => {
61
+ const exifBuffer = Buffer.from(hex, 'hex')
62
+ exifBuffer[28] = (width >> 24) & 0xff
63
+ exifBuffer[29] = (width >> 16) & 0xff
64
+ exifBuffer[30] = (width >> 8) & 0xff
65
+ exifBuffer[31] = width & 0xff
66
+ exifBuffer[40] = (height >> 24) & 0xff
67
+ exifBuffer[41] = (height >> 16) & 0xff
68
+ exifBuffer[42] = (height >> 8) & 0xff
69
+ exifBuffer[43] = height & 0xff
70
+ return exifBuffer
71
+ }
72
+
73
+ const getSystemExifHex = (system: MotionPhotoSystem): string | null => {
74
+ if (system === 'oppo') return oppoExifHex
75
+ if (system === 'xiaomi') return xiaomiExifHex
76
+ return null
77
+ }
78
+
79
+ const resolveMotionPhotoSystem = (): MotionPhotoSystem => {
80
+ const system = Config.app.livePhotoSystem
81
+ if (system === 'google' || system === 'xiaomi' || system === 'oppo' || system === 'huawei_honor') {
82
+ return system
83
+ }
84
+ return 'google'
85
+ }
86
+
87
+ const buildMotionPhotoXmp = (
88
+ videoLength: number,
89
+ presentationTimestampUs: number,
90
+ system: MotionPhotoSystem,
91
+ hdrGainMapLength?: number
92
+ ): string => {
93
+ if (system === 'oppo') {
94
+ // OPPO 支持 HDR GainMap 的完整 XMP 数据
95
+ const containerItems = [
96
+ '<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li>'
97
+ ]
98
+
99
+ // 如果有 HDR GainMap 数据,添加 GainMap 项
100
+ if (hdrGainMapLength && hdrGainMapLength > 0) {
101
+ containerItems.push(
102
+ `<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="GainMap" Item:Length="${hdrGainMapLength}" Item:Padding="0" /></rdf:li>`
103
+ )
104
+ }
105
+
106
+ // 添加视频项
107
+ containerItems.push(
108
+ `<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" /></rdf:li>`
109
+ )
110
+
111
+ return (
112
+ '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003">' +
113
+ '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
114
+ '<rdf:Description rdf:about="" ' +
115
+ 'xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" ' +
116
+ 'xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" ' +
117
+ 'xmlns:OpCamera="http://ns.oplus.com/photos/1.0/camera/" ' +
118
+ 'xmlns:Container="http://ns.google.com/photos/1.0/container/" ' +
119
+ 'xmlns:Item="http://ns.google.com/photos/1.0/container/item/" ' +
120
+ 'hdrgm:Version="1.0" ' +
121
+ `GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}" ` +
122
+ `OpCamera:MotionPhotoPrimaryPresentationTimestampUs="${presentationTimestampUs}" OpCamera:MotionPhotoOwner="oplus" OpCamera:OLivePhotoVersion="2" OpCamera:VideoLength="${videoLength}">` +
123
+ '<Container:Directory><rdf:Seq>' +
124
+ containerItems.join('') +
125
+ '</rdf:Seq></Container:Directory>' +
126
+ '</rdf:Description>' +
127
+ '</rdf:RDF>' +
128
+ '</x:xmpmeta>'
129
+ )
130
+ }
131
+
132
+ if (system === 'xiaomi') {
133
+ return (
134
+ '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003">' +
135
+ '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
136
+ '<rdf:Description rdf:about="" ' +
137
+ 'xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" ' +
138
+ 'xmlns:MiCamera="http://ns.xiaomi.com/photos/1.0/camera/" ' +
139
+ 'xmlns:Container="http://ns.google.com/photos/1.0/container/" ' +
140
+ 'xmlns:Item="http://ns.google.com/photos/1.0/container/item/" ' +
141
+ `GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}" ` +
142
+ `GCamera:MicroVideo="1" GCamera:MicroVideoVersion="1" GCamera:MicroVideoOffset="${videoLength}" GCamera:MicroVideoPresentationTimestampUs="${presentationTimestampUs}" ` +
143
+ 'MiCamera:XMPMeta="&lt;?xml version=&apos;1.0&apos; encoding=&apos;UTF-8&apos; standalone=&apos;yes&apos; ?&gt;">' +
144
+ '<Container:Directory><rdf:Seq>' +
145
+ '<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li>' +
146
+ `<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li>` +
147
+ '</rdf:Seq></Container:Directory>' +
148
+ '</rdf:Description>' +
149
+ '</rdf:RDF>' +
150
+ '</x:xmpmeta>'
151
+ )
152
+ }
153
+
154
+ return (
155
+ '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003">' +
156
+ '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
157
+ '<rdf:Description rdf:about="" ' +
158
+ 'xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" ' +
159
+ 'xmlns:Container="http://ns.google.com/photos/1.0/container/" ' +
160
+ 'xmlns:Item="http://ns.google.com/photos/1.0/container/item/" ' +
161
+ `GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}">` +
162
+ '<Container:Directory><rdf:Seq>' +
163
+ '<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li>' +
164
+ `<rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li>` +
165
+ '</rdf:Seq></Container:Directory>' +
166
+ '</rdf:Description>' +
167
+ '</rdf:RDF>' +
168
+ '</x:xmpmeta>'
169
+ )
170
+ }
171
+
172
+ const injectXmpToJpeg = (jpegBuffer: Buffer, xmpPacket: string, system: MotionPhotoSystem): Buffer => {
173
+ if (!isJpegBuffer(jpegBuffer)) {
174
+ throw new Error('输入图片不是 JPEG 格式')
175
+ }
176
+
177
+ const xmpPayload = Buffer.concat([xmpHeaderBuffer, Buffer.from(xmpPacket, 'utf8')])
178
+ const app1Length = xmpPayload.length + 2
179
+ if (app1Length > 65535) {
180
+ throw new Error('XMP 数据过大,无法写入 JPEG APP1')
181
+ }
182
+
183
+ const app1Segment = Buffer.alloc(4)
184
+ app1Segment[0] = 0xff
185
+ app1Segment[1] = 0xe1
186
+ app1Segment.writeUInt16BE(app1Length, 2)
187
+
188
+ const dimensions = getJpegDimensions(jpegBuffer)
189
+ const exifHex = getSystemExifHex(system)
190
+ const needExif = !hasExifApp1(jpegBuffer) && dimensions !== null && exifHex !== null
191
+ const exifSegment = needExif ? buildExifSegment(exifHex, dimensions.width, dimensions.height) : null
192
+
193
+ return Buffer.concat([jpegBuffer.subarray(0, 2), ...(exifSegment ? [exifSegment] : []), app1Segment, xmpPayload, jpegBuffer.subarray(2)])
194
+ }
195
+
196
+ const readOrConvertToJpeg = async (imagePath: string): Promise<Buffer> => {
197
+ const sourceBuffer = fs.readFileSync(imagePath)
198
+ if (isJpegBuffer(sourceBuffer)) {
199
+ return sourceBuffer
200
+ }
201
+
202
+ const tempJpegPath = path.join(Common.tempDri.images, `MotionPhoto_${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`)
203
+ const result = await ffmpeg(`-y -i "${imagePath}" -frames:v 1 -q:v 2 "${tempJpegPath}"`)
204
+ if (!result.status) {
205
+ throw new Error(`图片转换 JPEG 失败: ${imagePath}`)
206
+ }
207
+
208
+ try {
209
+ return fs.readFileSync(tempJpegPath)
210
+ } finally {
211
+ fs.rmSync(tempJpegPath, { force: true })
212
+ }
213
+ }
214
+
215
+ /**
216
+ * 检测图片是否包含HDR GainMap数据
217
+ * @param imageBuffer JPEG图片缓冲区
218
+ * @returns HDR GainMap长度,如果没有则返回0
219
+ */
220
+ const detectHdrGainMap = (imageBuffer: Buffer): number => {
221
+ try {
222
+ // 查找XMP数据段
223
+ const xmpStart = imageBuffer.indexOf('http://ns.adobe.com/xap/1.0/')
224
+ if (xmpStart === -1) return 0
225
+
226
+ // 查找HDR GainMap相关标记
227
+ const hdrGainMapMarker = 'Item:Semantic="GainMap"'
228
+ const gainMapIndex = imageBuffer.indexOf(hdrGainMapMarker, xmpStart)
229
+ if (gainMapIndex === -1) return 0
230
+
231
+ // 尝试提取GainMap长度
232
+ const lengthPattern = /Item:Length="(\d+)"/
233
+ const xmpSection = imageBuffer.subarray(xmpStart, xmpStart + 4096).toString('utf8')
234
+ const gainMapSection = xmpSection.substring(xmpSection.indexOf(hdrGainMapMarker))
235
+ const lengthMatch = gainMapSection.match(lengthPattern)
236
+
237
+ if (lengthMatch && lengthMatch[1]) {
238
+ const length = parseInt(lengthMatch[1], 10)
239
+ logger.debug(`检测到HDR GainMap,长度: ${length}`)
240
+ return length
241
+ }
242
+
243
+ // 如果找到GainMap标记但无法提取长度,返回默认值
244
+ logger.debug('检测到HDR GainMap标记,但无法提取长度,使用默认值')
245
+ return 463255 // 使用你提供的示例中的默认长度
246
+ } catch (error) {
247
+ logger.debug('HDR GainMap检测失败:', error)
248
+ return 0
249
+ }
250
+ }
251
+
252
+ export interface GoogleMotionPhotoOptions {
253
+ imagePath: string
254
+ videoPath: string
255
+ outputPath: string
256
+ presentationTimestampUs?: number
257
+ hdrGainMapLength?: number // HDR GainMap 长度,用于 OPPO HDR 支持
258
+ }
259
+
260
+ export const buildGoogleMotionPhoto = async (options: GoogleMotionPhotoOptions): Promise<boolean> => {
261
+ const { imagePath, videoPath, outputPath, presentationTimestampUs, hdrGainMapLength } = options
262
+ try {
263
+ const system = resolveMotionPhotoSystem()
264
+ const imageBuffer = await readOrConvertToJpeg(imagePath)
265
+ const videoBuffer = fs.readFileSync(videoPath)
266
+ const resolvedPresentationTimestampUs =
267
+ presentationTimestampUs === undefined || presentationTimestampUs < 0 ? 0 : presentationTimestampUs
268
+
269
+ // 自动检测HDR GainMap(仅对OPPO系统)
270
+ const detectedHdrGainMapLength = system === 'oppo' ? (hdrGainMapLength ?? detectHdrGainMap(imageBuffer)) : 0
271
+
272
+ const huaweiHonorFooter = Buffer.from(
273
+ `v2_f35 409:1000 LIVE_${resolvedPresentationTimestampUs > 0 ? Math.floor(resolvedPresentationTimestampUs) : huaweiHonorLiveIdFallback}`,
274
+ 'utf8'
275
+ )
276
+ const outputBuffer =
277
+ system === 'huawei_honor'
278
+ ? Buffer.concat([imageBuffer, huaweiHonorFooter])
279
+ : (() => {
280
+ const xmpPacket = buildMotionPhotoXmp(videoBuffer.length, resolvedPresentationTimestampUs, system, detectedHdrGainMapLength)
281
+ const jpegWithXmp = injectXmpToJpeg(imageBuffer, xmpPacket, system)
282
+ return Buffer.concat([jpegWithXmp, videoBuffer])
283
+ })()
284
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true })
285
+ fs.writeFileSync(outputPath, outputBuffer)
286
+
287
+ if (system === 'oppo' && detectedHdrGainMapLength > 0) {
288
+ logger.debug(`Motion Photo 封面生成成功(${system} with HDR): ${outputPath}`)
289
+ } else {
290
+ logger.debug(`Motion Photo 封面生成成功(${system}): ${outputPath}`)
291
+ }
292
+ return true
293
+ } catch (error) {
294
+ logger.error('Motion Photo 封面生成失败', error)
295
+ return false
296
+ }
297
+ }