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,1064 @@
1
+ /**
2
+ * `node-karin` 的 Koishi 兼容实现(迁移核心)。
3
+ *
4
+ * 目标:让 packages/core/src 里的业务代码几乎原样运行 —— 只把框架 API 换成 Koishi 的实现。
5
+ * 覆盖范围按原插件的真实调用面收敛:
6
+ * karin.command / task / getBot / getAllBot(ID|List) / contactGroup / sendMsg / sendMaster / on / ctx
7
+ * logger(见 ./logger)、segment(见 ./segment)、common.makeForward、config.master、db(KV)、render、sqlite3、root
8
+ */
9
+ import { spawn } from 'node:child_process'
10
+ import { pathToFileURL } from 'node:url'
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
13
+
14
+ import { h } from 'koishi'
15
+ import type { Bot, Context, Session } from 'koishi'
16
+
17
+ import { logger } from './logger'
18
+ import { commandQueue, eventQueue, getRuntime, taskQueue, tryGetRuntime } from './runtime'
19
+ import { segment } from './segment'
20
+ import { syncUpstreamToKoishi } from './syncConfig'
21
+ import { normalizeMessageText } from './text'
22
+
23
+ /** 取当前 Koishi 上下文(移植代码访问 Koishi 服务用) */
24
+ export const getKoishiContext = (): Context => getRuntime().ctx
25
+
26
+ export const BOT_CONNECT = 'bot-connect'
27
+ export const BOT_DISCONNECT = 'bot-disconnect'
28
+ export const BOT_READY = 'bot-ready'
29
+
30
+ /** 命令处理器调用 next() 时返回的哨兵,表示继续走后续中间件 */
31
+ export const NEXT = Symbol('kkk.next')
32
+
33
+ /** Karin 的 Contact 形状 */
34
+ export interface Contact {
35
+ /** 会话 id:群聊是群号,私聊是 private:用户号 */
36
+ peer: string
37
+ /** 群号 */
38
+ guildId?: string
39
+ /** 私聊用户号 */
40
+ userId?: string
41
+ isGroup?: boolean
42
+ }
43
+
44
+ export type SendMessage = any[]
45
+ export type Elements = any
46
+ export type ElementTypes = any
47
+ export type ImageElement = any
48
+ export type Package = Record<string, any>
49
+
50
+ /* ------------------------------------------------------------------ *
51
+ * Bot 适配
52
+ * ------------------------------------------------------------------ */
53
+
54
+ const MIME_BY_EXT: Record<string, string> = {
55
+ '.png': 'image/png',
56
+ '.jpg': 'image/jpeg',
57
+ '.jpeg': 'image/jpeg',
58
+ '.gif': 'image/gif',
59
+ '.webp': 'image/webp',
60
+ '.mp4': 'video/mp4',
61
+ '.mp3': 'audio/mpeg',
62
+ '.amr': 'audio/amr',
63
+ '.silk': 'audio/silk',
64
+ '.bin': 'application/octet-stream'
65
+ }
66
+
67
+ function guessMime (filePath: string): string {
68
+ const ext = path.extname(filePath).toLowerCase()
69
+ return MIME_BY_EXT[ext] ?? 'application/octet-stream'
70
+ }
71
+
72
+ /** 仿 karin 的 AdapterType(机器人实例) */
73
+ export class KkkBot {
74
+ constructor (public bot: Bot) {}
75
+
76
+ get selfId (): string {
77
+ return this.bot.selfId
78
+ }
79
+
80
+ get account () {
81
+ return {
82
+ selfId: this.bot.selfId,
83
+ uid: this.bot.user?.id ?? this.bot.selfId,
84
+ uin: this.bot.selfId,
85
+ name: this.bot.user?.name ?? this.bot.selfId,
86
+ avatar: this.bot.user?.avatar
87
+ }
88
+ }
89
+
90
+ get adapter () {
91
+ const platform = String(this.bot.platform ?? '')
92
+ const raw: any = (this.bot as any).adapter || {}
93
+ const cfg: any = raw.config ?? {}
94
+ return {
95
+ name: platform,
96
+ protocol: platform,
97
+ standard: platform,
98
+ // 错误卡片模板还会读这三项,之前没给 → 卡片上显示 undefined 和一个空的「v」
99
+ platform: String(raw.platform ?? cfg.platform ?? (platform || '未知')),
100
+ communication: String(raw.communication ?? cfg.communication ?? cfg.protocol ?? '未知'),
101
+ version: String(raw.version ?? cfg.version ?? ''),
102
+ raw: this.bot.adapter
103
+ }
104
+ }
105
+
106
+ get ctx () {
107
+ return this.bot.ctx
108
+ }
109
+
110
+ /** 好友列表 */
111
+ /**
112
+ * 好友列表。
113
+ *
114
+ * Koishi 的 Bot 没有统一的 getFriendList,拿不到就返回空数组 ——
115
+ * 上游用它来「找能给主人发消息的 bot」,空数组会自然走到后面的兜底逻辑(getAllBotID),
116
+ * 所以这里**不能抛错**,否则整条推送链路会断在第一步。
117
+ */
118
+ async getFriendList (): Promise<any[]> {
119
+ try {
120
+ return await (this.bot as any).getFriendList()
121
+ } catch (error) {
122
+ logger.debug('getFriendList 失败: ' + String(error))
123
+ return []
124
+ }
125
+ }
126
+
127
+ /** 群信息,karin 侧字段为 groupName */
128
+ async getGroupInfo (groupId: string): Promise<any> {
129
+ try {
130
+ const guild = await this.bot.getGuild(groupId)
131
+ return { ...guild, groupId, groupName: (guild as any)?.name ?? '' }
132
+ } catch (error) {
133
+ logger.debug('getGroupInfo 失败: ' + String(error))
134
+ return undefined
135
+ }
136
+ }
137
+
138
+ async getGroupAvatarUrl (groupId: string): Promise<string> {
139
+ try {
140
+ return await (this.bot as any).getGroupAvatarUrl?.(groupId) ?? ''
141
+ } catch {
142
+ return ''
143
+ }
144
+ }
145
+
146
+ async getAvatarUrl (userId: string): Promise<string> {
147
+ try {
148
+ return await (this.bot as any).getAvatarUrl?.(userId) ?? ''
149
+ } catch {
150
+ return ''
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 合并转发。
156
+ *
157
+ * 上游是把内容装进「合并转发」发出去;但**部分适配器根本没有合并转发能力**
158
+ * (例如 QQ 官方适配器 koishi-plugin-adapter-qq-crack,platform=\`qqguild\`,
159
+ * 整个包里没有一处 forward),硬发 \`<message>\` 元素只会失败或变成一条空消息。
160
+ * 所以这里先问适配器支不支持,不支持就**退化成直接发送这些元素**(视觉上是普通消息)。
161
+ */
162
+ async sendForwardMsg (contact: Contact | string, elements: any, _options?: any): Promise<{ messageId: string }> {
163
+ const channelId = typeof contact === 'string' ? contact : contact.peer
164
+ const payload = elements instanceof ForwardPayload ? elements : new ForwardPayload(normalizeContent(elements))
165
+
166
+ if (supportsForward(this.bot)) {
167
+ try {
168
+ const ids = await this.bot.sendMessage(channelId, [h('message', ...payload.elements)] as any)
169
+ return { messageId: ids[ids.length - 1] ?? '' }
170
+ } catch (error) {
171
+ logger.warn('合并转发发送失败,改为直接发送内容: ' + String((error as any)?.message ?? error))
172
+ }
173
+ } else {
174
+ logger.debug('当前适配器(' + this.bot.platform + ')不支持合并转发,改为直接发送内容')
175
+ }
176
+
177
+ // 退化路径:整条发一次;失败再逐个元素发,尽量把内容送出去
178
+ try {
179
+ const ids = await this.bot.sendMessage(channelId, payload.elements as any)
180
+ return { messageId: ids[ids.length - 1] ?? '' }
181
+ } catch (error) {
182
+ logger.warn('整条发送失败,改为逐个元素发送: ' + String((error as any)?.message ?? error))
183
+ let lastId = ''
184
+ for (const item of payload.elements) {
185
+ const ids = await this.bot.sendMessage(channelId, [item] as any)
186
+ lastId = ids[ids.length - 1] ?? lastId
187
+ }
188
+ return { messageId: lastId }
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 上传群文件。
194
+ *
195
+ * 上游会把「文件」写成各种形式:本地路径、`file://`、`base64://xxx`(Base.ts 生成视频文件时就是这种)。
196
+ * 之前这里只认路径,base64 串会直接抛「上传文件不存在」或者被当成 URL 交给适配器去 fetch
197
+ * (日志表现:`QQ 消息发送失败 fetch base64://AAAAHGZ0eXBpc29t…`)。
198
+ * 现在统一交给 `segment.file` 处理:它能吃路径 / file:// / base64:// / Buffer。
199
+ */
200
+ async uploadFile (contact: Contact | string, file: string | Buffer, name?: string): Promise<any> {
201
+ const channelId = typeof contact === 'string' ? contact : contact.peer
202
+ let element: any
203
+ if (Buffer.isBuffer(file)) {
204
+ element = segment.file(file, name)
205
+ } else if (typeof file === 'string' && (file.startsWith('base64://') || file.startsWith('data:'))) {
206
+ element = segment.file(file, name)
207
+ } else {
208
+ const filePath = String(file).startsWith('file://') ? String(file).slice('file://'.length) : String(file)
209
+ if (!fs.existsSync(filePath)) throw new Error('上传文件不存在: ' + filePath)
210
+ element = segment.file(fs.readFileSync(filePath), name ?? path.basename(filePath))
211
+ }
212
+ return this.bot.sendMessage(channelId, [element] as any)
213
+ }
214
+
215
+ /** 群成员信息,karin 侧字段:userId/nick/card/role */
216
+ async getGroupMemberInfo (groupId: string, userId: string): Promise<any> {
217
+ const bot: any = this.bot
218
+ try {
219
+ const member = await bot.getGuildMember?.(groupId, userId)
220
+ if (!member) return undefined
221
+ return { ...member, userId: member.user?.id ?? userId, nick: member.user?.name ?? member.nick ?? '', card: member.nick ?? '' }
222
+ } catch (error) {
223
+ logger.debug('getGroupMemberInfo 失败: ' + String(error))
224
+ return undefined
225
+ }
226
+ }
227
+
228
+ /** 群列表,karin 侧字段:group_id/group_name */
229
+ async getGroupList (): Promise<any[]> {
230
+ const bot: any = this.bot
231
+ try {
232
+ const list = await bot.getGuildList?.()
233
+ return (list ?? []).map((item: any) => ({ ...item, group_id: item.id, group_name: item.name }))
234
+ } catch (error) {
235
+ logger.debug('getGroupList 失败: ' + String(error))
236
+ return []
237
+ }
238
+ }
239
+
240
+ /** 取消息(karin 的 bot.getMsg),结果按 karin 的 elements 形状返回 */
241
+ async getMsg (contact: Contact | string, messageId: string): Promise<any> {
242
+ const channelId = typeof contact === 'string' ? contact : contact.peer
243
+ try {
244
+ const message = await (this.bot as any).getMessage(channelId, messageId)
245
+ if (!message) return { message_id: messageId, elements: [], raw_message: '' }
246
+ const elements = (message.elements ?? []).map((el: any) => {
247
+ switch (el.type) {
248
+ case 'text': return { type: 'text', text: el.attrs?.content ?? '' }
249
+ case 'image': return { type: 'image', file: el.attrs?.src ?? '' }
250
+ case 'at': return { type: 'at', qq: el.attrs?.id ?? '' }
251
+ case 'quote': return { type: 'reply', id: el.attrs?.id ?? '' }
252
+ default: return { type: el.type, data: JSON.stringify(el.attrs ?? {}) }
253
+ }
254
+ })
255
+ return { message_id: message.id ?? messageId, raw_message: message.content ?? '', elements }
256
+ } catch (error) {
257
+ logger.debug('getMsg 失败: ' + String(error))
258
+ return { message_id: messageId, elements: [], raw_message: '' }
259
+ }
260
+ }
261
+
262
+ /** 发送消息(karin 的 bot.sendMsg) */
263
+ async sendMsg (contact: Contact | string, content: any, _options?: any): Promise<{ messageId: string }> {
264
+ const channelId = typeof contact === 'string' ? contact : contact.peer
265
+ const ids = await this.bot.sendMessage(channelId, normalizeContent(content) as any)
266
+ return { messageId: ids[ids.length - 1] ?? '' }
267
+ }
268
+
269
+ /**
270
+ * 消息表情回应(karin 的 bot.setMsgReaction)。
271
+ * Satori 机器人没有统一接口,能调则调,不能调就静默返回 false —— 原插件只用它做「处理中/成功」提示。
272
+ */
273
+ async setMsgReaction (_contact: Contact | string, messageId: string, emojiId: string | number, isAdd = true): Promise<boolean> {
274
+ const bot: any = this.bot
275
+ const fn = bot.setMessageReaction ?? bot.setMsgReaction
276
+ if (typeof fn !== 'function') return false
277
+ try {
278
+ await fn.call(bot, messageId, String(emojiId), isAdd)
279
+ return true
280
+ } catch {
281
+ return false
282
+ }
283
+ }
284
+
285
+ /**
286
+ * 撤回消息。
287
+ *
288
+ * 必须带上频道号:QQ 适配器的 `deleteMessage(channelId, messageId)` 会在内部对
289
+ * channelId 调 `.startsWith`,传 undefined 直接抛
290
+ * `TypeError: Cannot read properties of undefined (reading 'startsWith')`(撤回全部失败)。
291
+ * @param messageId 要撤回的消息 id
292
+ * @param channelId 频道/群 id(karin 侧调用一般不带,这时用最近一次事件的频道)
293
+ */
294
+ async recallMsg (messageId: string, channelId?: string): Promise<any> {
295
+ try {
296
+ const channel = channelId || (this as any).lastChannelId || ''
297
+ return await this.bot.deleteMessage(channel, messageId)
298
+ } catch (error) {
299
+ logger.debug('recallMsg 失败: ' + String(error))
300
+ return undefined
301
+ }
302
+ }
303
+ }
304
+
305
+ export type AdapterType = KkkBot
306
+
307
+ /* ------------------------------------------------------------------ *
308
+ * Message 适配
309
+ * ------------------------------------------------------------------ */
310
+
311
+ /** 仿 karin 的 Message(事件对象) */
312
+ export class Message {
313
+ /** 消息文本(原插件会直接改写这个字段) */
314
+ msg: string
315
+ selfId: string
316
+ userId: string
317
+ groupId: string
318
+ isGroup: boolean
319
+ sender: { userId: string; nick: string; role?: string }
320
+ contact: Contact
321
+ bot: KkkBot
322
+ messageId: string
323
+ /** 引用的消息 ID(karin 的 e.replyId) */
324
+ replyId?: string
325
+ session?: Session
326
+ /** 原始 satori 事件载荷 */
327
+ raw?: any
328
+
329
+ constructor (init: Partial<Message> & { session?: Session; bot?: KkkBot | Bot }) {
330
+ this.msg = init.msg ?? ''
331
+ this.selfId = init.selfId ?? ''
332
+ this.userId = init.userId ?? ''
333
+ this.groupId = init.groupId ?? ''
334
+ this.isGroup = init.isGroup ?? false
335
+ this.sender = init.sender ?? { userId: this.userId, nick: '' }
336
+ this.contact = init.contact ?? { peer: this.groupId || ('private:' + this.userId), isGroup: this.isGroup }
337
+ this.bot = init.bot instanceof KkkBot ? init.bot : new KkkBot(init.bot as Bot)
338
+ this.messageId = init.messageId ?? ''
339
+ this.replyId = init.replyId ?? ''
340
+ this.session = init.session
341
+ this.raw = init.raw
342
+ }
343
+
344
+ /**
345
+ * 由 Koishi 会话构造。
346
+ * @param session Koishi 会话
347
+ * @param msg 已经归一化过的消息文本(QQ 卡片链路会在匹配前先做异步还原,见 karin/module/utils/QqCardResolve.ts);
348
+ * 不传时退回同步归一化(还原 JSON 转义 + 提取已有链接)
349
+ */
350
+ static fromSession (session: Session, msg?: string): Message {
351
+ const isGroup = !!session.guildId
352
+ const userId = session.userId ?? ''
353
+ return new Message({
354
+ // QQ 分享卡片在适配器里是「带 JSON 转义的一整段文本」,这里还原转义并补上挖出来的链接,
355
+ // 否则各平台的链接正则匹配不到(详见 compat/text.ts)
356
+ msg: msg ?? normalizeMessageText(session.content ?? ''),
357
+ selfId: session.selfId,
358
+ userId,
359
+ groupId: session.guildId ?? session.channelId ?? '',
360
+ isGroup,
361
+ sender: { userId, nick: session.author?.nick ?? session.username ?? '' },
362
+ contact: {
363
+ peer: session.channelId ?? (isGroup ? session.guildId! : 'private:' + userId),
364
+ guildId: session.guildId ?? undefined,
365
+ userId,
366
+ isGroup
367
+ },
368
+ bot: session.bot,
369
+ messageId: session.messageId ?? '',
370
+ replyId: (session as any).quote?.id ?? (session as any).event?.message?.quote?.id ?? '',
371
+ session,
372
+ raw: session.event
373
+ })
374
+ }
375
+
376
+ /**
377
+ * 回复消息。
378
+ *
379
+ * **被动回复兜底**:QQ 官方 bot 的「被动回复」有硬限制(一条消息只能回几次、还有时间窗),
380
+ * 而弹幕烧录这种操作动辄几分钟 —— 回来再 reply 就会撞
381
+ * `[40034128] 回复消息失败,被动回复时间或者次数超过限制`,视频明明下好了却发不出去。
382
+ * 这时自动改用**主动消息**(不带引用,直接往频道里发),失败才把原错误抛出去。
383
+ */
384
+ async reply (content: any, _options?: any): Promise<{ messageId: string; rawData?: any }> {
385
+ const elements = normalizeContent(content)
386
+
387
+ /**
388
+ * 主动消息兜底(重点)。
389
+ *
390
+ * QQ 适配器在「被动回复超限」时**不一定抛异常**:它只是发不出去、返回空数组,
391
+ * 于是 await send() 看起来是成功的 —— 视频就静默丢了。所以这里除了 catch,
392
+ * 还要看**有没有拿到消息 ID**:没拿到就换主动消息(不带引用)重发一次。
393
+ */
394
+ const sendActive = async (): Promise<{ messageId: string; rawData?: any }> => {
395
+ const activeIds = await this.bot.bot.sendMessage(this.contact.peer, elements as any)
396
+ return { messageId: activeIds?.[activeIds.length - 1] ?? '' }
397
+ }
398
+
399
+ try {
400
+ if (this.session) {
401
+ const ids = await this.session.send(elements as any)
402
+ const id = ids?.[ids.length - 1] ?? ''
403
+ if (!id) {
404
+ logger.mark('[compat] 回复没有返回消息 ID(多为被动回复超限),改用主动消息重发')
405
+ return await sendActive()
406
+ }
407
+ return { messageId: id }
408
+ }
409
+ const ids = await this.bot.bot.sendMessage(this.contact.peer, elements as any)
410
+ return { messageId: ids?.[ids.length - 1] ?? '' }
411
+ } catch (error: any) {
412
+ const text = String(error?.message ?? error)
413
+ // 被动回复额度/时间窗超了:换成主动消息再试一次
414
+ if (!/被动回复|40034128|timeout|次数超过/.test(text)) throw error
415
+ // 用 mark 级别:这是「视频明明下好了却发不出去」的关键兜底,日志里要看得见
416
+ logger.mark('[compat] 被动回复受限,改用主动消息发送: ' + text)
417
+ return await sendActive()
418
+ }
419
+ }
420
+
421
+ /** karin 的 e.bot 直接就是发送者 */
422
+ async sendMsg (content: any) {
423
+ return this.reply(content)
424
+ }
425
+ }
426
+
427
+ function normalizeContent (content: any): any[] {
428
+ if (content === undefined || content === null) return []
429
+ return Array.isArray(content) ? content : [content]
430
+ }
431
+
432
+ /**
433
+ * 合并转发的载荷。
434
+ *
435
+ * 上游 \`common.makeForward(elements, botId, botName)\` 的返回值只被 \`bot.sendForwardMsg()\` 消费,
436
+ * 所以这里用一个带类型的对象承载「哪些元素要转发」,由 sendForwardMsg 决定真转发还是退化成直发。
437
+ */
438
+ export class ForwardPayload {
439
+ constructor (
440
+ public elements: any[],
441
+ public botId?: string,
442
+ public botName?: string
443
+ ) {}
444
+ }
445
+
446
+ /** karin 的 common.makeForward */
447
+ export function makeForward (elements: any, botId?: string, botName?: string): ForwardPayload {
448
+ return new ForwardPayload(normalizeContent(elements), botId, botName)
449
+ }
450
+
451
+ /**
452
+ * 适配器是否支持「合并转发」。
453
+ *
454
+ * OneBot 系(platform 为 onebot / red / chronocat…)支持 Satori 的 \`<message>\` 元素;
455
+ * QQ 官方 API(qqguild / qq / qqbot)没有这个能力,必须退化。
456
+ */
457
+ function supportsForward (bot: any): boolean {
458
+ const platform = String(bot?.platform ?? '')
459
+ if (!platform) return false
460
+ return !/qqguild|qqbot|^qq$|official/i.test(platform)
461
+ }
462
+
463
+ /* ------------------------------------------------------------------ *
464
+ * karin 门面
465
+ * ------------------------------------------------------------------ */
466
+
467
+ let commandOrder = 0
468
+
469
+ export const contactGroup = (groupId: string): Contact => ({ peer: groupId, guildId: groupId, isGroup: true })
470
+ export const contactFriend = (userId: string): Contact => ({ peer: 'private:' + userId, userId, isGroup: false })
471
+
472
+ function resolveBot (selfId?: string): KkkBot | undefined {
473
+ const runtime = tryGetRuntime()
474
+ if (!runtime) return undefined
475
+ const bots = runtime.ctx.bots as unknown as Bot[]
476
+ if (!bots?.length) return undefined
477
+ const matched = selfId ? bots.find((bot) => bot.selfId === selfId || bot.user?.id === selfId) : undefined
478
+ return new KkkBot(matched ?? bots[0])
479
+ }
480
+
481
+ function command (reg: RegExp | string, handler: (...args: any[]) => any, options?: Record<string, any>) {
482
+ const registration = { reg, handler, options, order: commandOrder++ }
483
+ commandQueue.push(registration)
484
+ return registration
485
+ }
486
+
487
+ function task (name: string, cron: string, handler: (...args: any[]) => any, options?: Record<string, any>) {
488
+ const registration = { name, cron, handler, options }
489
+ taskQueue.push(registration)
490
+ return registration
491
+ }
492
+
493
+ function on (event: string, handler: (...args: any[]) => any) {
494
+ eventQueue.push({ event, handler })
495
+ return () => {
496
+ const index = eventQueue.findIndex((item) => item.handler === handler)
497
+ if (index >= 0) eventQueue.splice(index, 1)
498
+ }
499
+ }
500
+
501
+ async function sendMsg (selfId: string, contact: Contact | string, content: any, _options?: any) {
502
+ const bot = resolveBot(selfId)
503
+ if (!bot) throw new Error('[kkk] 没有可用的机器人实例,无法发送消息')
504
+ const channelId = typeof contact === 'string' ? contact : contact.peer
505
+ const ids = await bot.bot.sendMessage(channelId, normalizeContent(content) as any)
506
+ return { messageId: ids[ids.length - 1] ?? '', rawData: ids }
507
+ }
508
+
509
+ async function sendMaster (botId: string, master: string, content: any, _options?: any) {
510
+ const bot = resolveBot(botId)
511
+ if (!bot) throw new Error('[kkk] 没有可用的机器人实例,无法发送消息')
512
+ const elements = normalizeContent(content)
513
+ const target = bot.bot as any
514
+ // Satori 的 Bot 有 sendPrivateMessage;个别适配器没有,退化成私聊频道 id
515
+ const ids = typeof target.sendPrivateMessage === 'function'
516
+ ? await target.sendPrivateMessage(master, elements)
517
+ : await target.sendMessage('private:' + master, elements)
518
+ return { messageId: ids?.[ids.length - 1] ?? '', rawData: ids }
519
+ }
520
+
521
+ /**
522
+ * 等待同一会话中的下一条消息(karin 的 karin.ctx)。
523
+ */
524
+ function waitContext (event: Message, options: { time?: number; reply?: boolean; throwOnTimeout?: boolean } = {}): Promise<Message | undefined> {
525
+ const runtime = getRuntime()
526
+ const timeout = options.time ?? 60 * 1000
527
+ return new Promise((resolve, reject) => {
528
+ let settled = false
529
+ const dispose = runtime.ctx.on('message', (session: Session) => {
530
+ if (settled) return
531
+ if (session.channelId !== event.contact.peer) return
532
+ if (event.userId && session.userId !== event.userId) return
533
+ settled = true
534
+ dispose()
535
+ clearTimeout(timer)
536
+ resolve(Message.fromSession(session))
537
+ })
538
+ const timer = setTimeout(() => {
539
+ if (settled) return
540
+ settled = true
541
+ dispose()
542
+ if (options.throwOnTimeout) reject(new Error('等待用户输入超时'))
543
+ else resolve(undefined)
544
+ }, timeout)
545
+ })
546
+ }
547
+
548
+ /* ------------------------------------------------------------------ *
549
+ * KV 存储(karin 的 db.get/set/del)
550
+ * ------------------------------------------------------------------ */
551
+
552
+ const kvCache = new Map<string, any>()
553
+ let kvLoaded = false
554
+
555
+ function kvFile () {
556
+ return path.resolve(getRuntime().dataRoot, 'koishi-plugin-kkk', 'data', 'kv.json')
557
+ }
558
+
559
+ function loadKv () {
560
+ if (kvLoaded) return
561
+ kvLoaded = true
562
+ try {
563
+ const file = kvFile()
564
+ if (fs.existsSync(file)) {
565
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'))
566
+ for (const [key, value] of Object.entries(data)) kvCache.set(key, value)
567
+ }
568
+ } catch (error) {
569
+ logger.warn('读取 KV 存储失败: ' + String(error))
570
+ }
571
+ }
572
+
573
+ function saveKv () {
574
+ try {
575
+ const file = kvFile()
576
+ fs.mkdirSync(path.dirname(file), { recursive: true })
577
+ fs.writeFileSync(file, JSON.stringify(Object.fromEntries(kvCache), null, 2))
578
+ } catch (error) {
579
+ logger.warn('写入 KV 存储失败: ' + String(error))
580
+ }
581
+ }
582
+
583
+ export const db = {
584
+ async get<T = any> (key: string): Promise<T | undefined> {
585
+ loadKv()
586
+ return kvCache.get(key) as T | undefined
587
+ },
588
+ async set (key: string, value: any) {
589
+ loadKv()
590
+ kvCache.set(key, value)
591
+ saveKv()
592
+ },
593
+ async del (key: string) {
594
+ loadKv()
595
+ kvCache.delete(key)
596
+ saveKv()
597
+ },
598
+ async all () {
599
+ loadKv()
600
+ return Object.fromEntries(kvCache)
601
+ },
602
+ async clear () {
603
+ kvCache.clear()
604
+ saveKv()
605
+ }
606
+ }
607
+
608
+ /* ------------------------------------------------------------------ *
609
+ * render / app / 其它零散 API
610
+ * ------------------------------------------------------------------ */
611
+
612
+ export const render = {
613
+ /**
614
+ * karin 的 render.render:给 HTML 文件截图。
615
+ * 这里走 koishi-plugin-puppeteer(可选依赖)。
616
+ */
617
+ async render (options: {
618
+ name?: string
619
+ file: string
620
+ selector?: string
621
+ fullPage?: boolean
622
+ type?: string
623
+ omitBackground?: boolean
624
+ multiPage?: boolean | number
625
+ pageGotoParams?: Record<string, any>
626
+ }): Promise<string> {
627
+ const runtime = getRuntime()
628
+ const puppeteer: any = (runtime.ctx as any).puppeteer
629
+ if (!puppeteer) throw new Error('[kkk] 渲染失败:未安装 koishi-plugin-puppeteer')
630
+
631
+ const screenshotOptions: any = {
632
+ file: options.file,
633
+ selector: options.selector ?? '#container',
634
+ fullPage: options.fullPage ?? false,
635
+ omitBackground: options.omitBackground ?? true,
636
+ type: options.type ?? 'png',
637
+ pageGotoParams: options.pageGotoParams
638
+ }
639
+
640
+ /**
641
+ * 自己开页面对 HTML 文件截图。
642
+ *
643
+ * 不能走 `puppeteer.render(html, options)` —— 那个 API 的第二个参数是**回调函数**,
644
+ * 传截图配置进去会直接报 `callback is not a function`
645
+ * (抖音弹幕条渲染就是这么挂的,日志里一堆「弹幕条渲染失败,将按纯文字处理」)。
646
+ */
647
+ const target = options.selector ?? '#container'
648
+ const newPage = async (): Promise<any> => {
649
+ if (typeof puppeteer.page === 'function') return await puppeteer.page()
650
+ if (puppeteer.browser && typeof puppeteer.browser.newPage === 'function') return await puppeteer.browser.newPage()
651
+ return null
652
+ }
653
+ const page = await newPage()
654
+ if (!page) throw new Error('[kkk] 渲染失败:puppeteer 服务没有可用的页面')
655
+
656
+ try {
657
+ if (page.setViewport) await page.setViewport({ width: 1200, height: 900, deviceScaleFactor: 2 })
658
+ await page.goto(pathToFileURL(options.file).href, {
659
+ waitUntil: options.pageGotoParams?.waitUntil ?? 'load',
660
+ timeout: options.pageGotoParams?.timeout ?? 15000
661
+ })
662
+ const handle = (await page.$(target)) ?? (await page.$('body'))
663
+ const box = handle ? await handle.boundingBox() : null
664
+ if (box && box.height > 900 && page.setViewport) {
665
+ await page.setViewport({ width: Math.ceil(box.width) + 4, height: Math.ceil(box.height) + 4, deviceScaleFactor: 2 })
666
+ }
667
+ const buffer = await page.screenshot({
668
+ clip: box ?? undefined,
669
+ omitBackground: options.omitBackground ?? true,
670
+ type: (options.type as any) ?? 'png'
671
+ } as any)
672
+ return buffer.toString('base64')
673
+ } finally {
674
+ try { await page.close() } catch { /* 忽略 */ }
675
+ }
676
+
677
+ if (Buffer.isBuffer(result)) return result.toString('base64')
678
+ if (typeof result === 'string') return result.replace(/^data:image\/\w+;base64,/, '')
679
+ if (result?.data) return String(result.data).replace(/^data:image\/\w+;base64,/, '')
680
+ throw new Error('[kkk] 渲染失败:截图结果为空')
681
+ }
682
+ }
683
+
684
+ export const common = {
685
+ makeForward,
686
+ async sleep (ms: number) {
687
+ return new Promise((resolve) => setTimeout(resolve, ms))
688
+ },
689
+ get karinVersion () {
690
+ return karin.version
691
+ }
692
+ }
693
+
694
+ export const config = {
695
+ /** 主人账号列表 */
696
+ master (): string[] {
697
+ return getRuntime().config.masters
698
+ },
699
+ /** 读取任意目录下的配置文件(兼容占位) */
700
+ json (dir?: string) {
701
+ const runtime = getRuntime()
702
+ const file = dir ? path.resolve(runtime.dataRoot, dir) : path.resolve(runtime.dataRoot, 'config.json')
703
+ try {
704
+ return JSON.parse(fs.readFileSync(file, 'utf8'))
705
+ } catch {
706
+ return undefined
707
+ }
708
+ }
709
+ }
710
+
711
+ /**
712
+ * karin 的 hooks 是内置插件(更新检测等)暴露的钩子集合。
713
+ * Koishi 侧没有对应实现,这里做成深度空实现:任何属性都是可调用的 no-op,
714
+ * 避免移植代码在 import 阶段直接崩掉。
715
+ */
716
+ const noopProxy: any = new Proxy(function () {}, {
717
+ get: (_target, prop) => (prop === 'then' ? undefined : noopProxy),
718
+ apply: () => noopProxy
719
+ })
720
+
721
+ export const hooks: any = new Proxy({}, {
722
+ get: (_target, prop) => (prop === 'on' || prop === 'once'
723
+ ? (event: string, handler: (...args: any[]) => any) => on(event, handler)
724
+ : noopProxy)
725
+ })
726
+
727
+ export const watch = (_paths: any, _options: any, _handler?: any) => () => {}
728
+ export const restart = async () => {
729
+ logger.warn('[kkk] Koishi 环境下不支持插件重启,请手动重启进程')
730
+ }
731
+ /**
732
+ * 检查 npm 上的最新版本(对应 karin 的 checkPkgUpdate)。
733
+ * 返回 karin 约定的三种形状:yes(有更新) / no(无更新) / error。
734
+ * 未发布到 npm 的包(例如本地开发时的 koishi-plugin-kkk)会走到 error 分支,调用方按「无更新」处理。
735
+ */
736
+ export const checkPkgUpdate = async (name: string, _options?: { compare?: string }) => {
737
+ const local = readLocalVersion()
738
+ try {
739
+ const response = await fetch('https://registry.npmmirror.com/' + name, { signal: AbortSignal.timeout(10000) })
740
+ if (!response.ok) return { status: 'error' as const, error: new Error('HTTP ' + response.status) }
741
+ const meta: any = await response.json()
742
+ const remote = meta?.['dist-tags']?.latest
743
+ if (!remote) return { status: 'error' as const, error: new Error('响应缺少 dist-tags.latest') }
744
+ if (remote === local) return { status: 'no' as const, local }
745
+ return { status: 'yes' as const, local, remote }
746
+ } catch (error) {
747
+ return { status: 'error' as const, error: error as Error }
748
+ }
749
+ }
750
+
751
+ /** 读取插件自身 package.json 的版本号 */
752
+ function readLocalVersion (): string {
753
+ try {
754
+ const file = path.resolve(getRuntime().pluginRoot, 'package.json')
755
+ return JSON.parse(fs.readFileSync(file, 'utf8')).version ?? '0.0.0'
756
+ } catch {
757
+ return '0.0.0'
758
+ }
759
+ }
760
+ export const updatePkg = async (_name: string) => {
761
+ logger.warn('[kkk] Koishi 环境下请使用包管理器更新插件')
762
+ return false
763
+ }
764
+ export const mkdirSync = fs.mkdirSync
765
+ export const isDocker = () => false
766
+ /**
767
+ * 提取 CHANGELOG 中指定数量的版本记录(karin 的 \`logs\`)。
768
+ * 上游用法:\`logs({ version, data, length })\` → 返回最近 \`length\` 个版本的 markdown 片段。
769
+ */
770
+ export function logs (options: { version?: string; data: string; length?: number }): string {
771
+ const { data, length, version } = options ?? ({} as any)
772
+ if (typeof data !== 'string' || !data) return ''
773
+ const versions = parseChangelog(data)
774
+ const keys = Object.keys(versions)
775
+ if (!keys.length) return ''
776
+ let start = 0
777
+ if (version) {
778
+ const index = keys.findIndex((key) => compareVersion(key, version) === 0)
779
+ if (index >= 0) start = index
780
+ }
781
+ const take = Math.max(1, Number(length) || 1)
782
+ return keys.slice(start, start + take).map((key) => '## [' + key + ']\n' + versions[key]).join('\n\n')
783
+ }
784
+ /** 版本号比较(仅用于 changelog 过滤,够用即可) */
785
+ function compareVersion (a: string, b: string): number {
786
+ const pa = String(a).replace(/^v/i, '').split('-')[0].split('.').map((item) => Number(item) || 0)
787
+ const pb = String(b).replace(/^v/i, '').split('-')[0].split('.').map((item) => Number(item) || 0)
788
+ for (let i = 0; i < 3; i++) {
789
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0)
790
+ }
791
+ return 0
792
+ }
793
+
794
+ /**
795
+ * 解析 CHANGELOG.md:\`{ '1.2.3': '该版本的正文' }\`(karin 的 parseChangelog)。
796
+ * 标题形如 \`## [1.2.3](...) (2024-01-01)\` 或 \`## 1.2.3\`。
797
+ */
798
+ export function parseChangelog (text: string): Record<string, string> {
799
+ const result: Record<string, string> = {}
800
+ if (typeof text !== 'string' || !text) return result
801
+ const lines = text.split(/\r?\n/)
802
+ let current: string | null = null
803
+ let buffer: string[] = []
804
+ const flush = () => {
805
+ if (current) result[current] = buffer.join('\n').trim()
806
+ buffer = []
807
+ }
808
+ for (const line of lines) {
809
+ const matched = line.match(/^#{1,4}\s*\[?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\]?/)
810
+ if (matched) {
811
+ flush()
812
+ current = matched[1]
813
+ continue
814
+ }
815
+ if (current) buffer.push(line)
816
+ }
817
+ flush()
818
+ return result
819
+ }
820
+
821
+ /**
822
+ * 按版本区间裁剪 changelog(karin 的 range)。
823
+ * 上游用法:\`range({ data, startVersion, endVersion, compare: 'semver' })\`,返回裁剪后的 markdown 文本。
824
+ * 区间语义:\`startVersion < v <= endVersion\`;解析不出任何版本时原样返回。
825
+ */
826
+ export function range (options: { data: string; startVersion?: string; endVersion?: string; compare?: string }): string {
827
+ const { data, startVersion, endVersion } = options ?? ({} as any)
828
+ if (typeof data !== 'string') return data
829
+ const parsed = parseChangelog(data)
830
+ const versions = Object.keys(parsed)
831
+ if (!versions.length) return data
832
+
833
+ const selected = versions.filter((version) => {
834
+ if (startVersion && compareVersion(version, startVersion) <= 0) return false
835
+ if (endVersion && compareVersion(version, endVersion) > 0) return false
836
+ return true
837
+ })
838
+ if (!selected.length) return data
839
+ return selected.map((version) => '## [' + version + ']\n' + parsed[version]).join('\n\n')
840
+ }
841
+ export const checkPort = async (port: number) => port
842
+ export const defineConfig = (config: any) => config
843
+ /**
844
+ * 控制台 API 的鉴权(替代 karin 的 WebUI 会话鉴权)。
845
+ *
846
+ * - 插件配置里设了 \`apiToken\`:要求 \`Authorization: Bearer <token>\`(或 \`?token=\`);
847
+ * - 没设 token:只放行本机请求,避免把配置接口裸奔到公网。
848
+ */
849
+ export const authMiddleware = (req: any, res: any, next: any) => {
850
+ const expected = String(getRuntime().config.apiToken ?? '')
851
+ const provided = String(req?.headers?.authorization ?? '').replace(/^Bearer\s+/i, '')
852
+ || String(req?.query?.token ?? '')
853
+
854
+ if (expected) {
855
+ if (provided === expected) return next()
856
+ return respond(401, res, { code: 401, message: '未授权:apiToken 不正确' })
857
+ }
858
+
859
+ const address = String(req?.socket?.remoteAddress ?? req?.ip ?? '')
860
+ if (/^(::1|::ffff:127\.0\.0\.1|127\.0\.0\.1)$/.test(address)) return next()
861
+ return respond(401, res, { code: 401, message: '未授权:请在插件配置中设置 apiToken' })
862
+ }
863
+
864
+ /**
865
+ * karin 的响应助手:**第一个参数是 express 的 res**,会直接把响应写出去。
866
+ * 例如 `createSuccessResponse(res, data, 'ok')` / `createBadRequestResponse(res, '参数错误')`。
867
+ * 移植过来的 controllers 就是这么用的,签名必须对齐(否则请求会一直挂着不返回)。
868
+ */
869
+ const respond = (status: number, res: any, payload: Record<string, any>) => {
870
+ try {
871
+ if (res && typeof res.status === 'function' && typeof res.json === 'function') {
872
+ res.status(status).json(payload)
873
+ } else if (res && typeof res.send === 'function') {
874
+ res.status?.(status).send?.(payload)
875
+ }
876
+ } catch (error) {
877
+ logger.debug('[kkk] 写响应失败: ' + String(error))
878
+ }
879
+ return payload
880
+ }
881
+
882
+ export const createSuccessResponse = (res: any, data?: any, message = 'success') =>
883
+ respond(200, res, { code: 200, message, data })
884
+
885
+ export const createBadRequestResponse = (res: any, message = '请求参数错误') =>
886
+ respond(400, res, { code: 400, message })
887
+
888
+ export const createNotFoundResponse = (res: any, message = '资源不存在') =>
889
+ respond(404, res, { code: 404, message })
890
+
891
+ export const createServerErrorResponse = (res: any, message = '服务器内部错误') =>
892
+ respond(500, res, { code: 500, message })
893
+
894
+ /* ------------------------------------------------------------------ *
895
+ * FFmpeg / ffprobe
896
+ *
897
+ * 上游 karin 直接调它的 ffmpeg 封装;Koishi 兼容层原先是个**抛错的占位实现**,
898
+ * 结果所有要拼流/改封装的解析(登录态 B站 视频要先把 m4s 修成 mp4 再和音频合成)
899
+ * 都在第一步就炸/哑掉 —— 用户看到的就是「提示开始解析,然后没下文」。
900
+ *
901
+ * 这里给它一个真实现:子进程直接跑 ffmpeg / ffprobe,返回值形状保持 karin 的
902
+ * `{ status, stdout, stderr }`(`status` 为真表示成功,kkk 里到处这么判断)。
903
+ * 可执行文件来源优先级:karin 传入的 options.ffmpegPath → koishi-plugin-ffmpeg-path 服务
904
+ * → 环境变量 FFMPEG_PATH/FFPROBE_PATH → PATH 里的 ffmpeg/ffprobe。
905
+ * ------------------------------------------------------------------ */
906
+
907
+ function ffmpegRuntimeCtx (): any {
908
+ try {
909
+ return (tryGetRuntime() as any)?.ctx
910
+ } catch {
911
+ return null
912
+ }
913
+ }
914
+
915
+ /** 由 ffmpeg 路径推出同目录的 ffprobe(找不到就交给 PATH) */
916
+ function siblingFfprobe (ffmpegPath: string): string {
917
+ try {
918
+ if (!ffmpegPath || ffmpegPath === 'ffmpeg') return 'ffprobe'
919
+ const dir = path.dirname(ffmpegPath)
920
+ const name = process.platform === 'win32' ? 'ffprobe.exe' : 'ffprobe'
921
+ const candidate = path.join(dir, name)
922
+ return fs.existsSync(candidate) ? candidate : 'ffprobe'
923
+ } catch {
924
+ return 'ffprobe'
925
+ }
926
+ }
927
+
928
+ function resolveFfmpegBin (options: any = {}): string {
929
+ if (typeof options?.ffmpegPath === 'string' && options.ffmpegPath) return options.ffmpegPath
930
+ const ctx = ffmpegRuntimeCtx()
931
+ const servicePath = ctx?.ffmpeg?.executable ?? ctx?.ffmpeg?.path
932
+ if (typeof servicePath === 'string' && servicePath) return servicePath
933
+ return process.env.FFMPEG_PATH || process.env.FFMPEG_BIN || 'ffmpeg'
934
+ }
935
+
936
+ /** 把 karin 那种「一整条参数串」拆成 argv(引号内的空格要保留) */
937
+ function splitCommandArgs (input: string): string[] {
938
+ const args: string[] = []
939
+ const pattern = /"([^"]*)"|'([^']*)'|(\S+)/g
940
+ let match: RegExpExecArray | null
941
+ while ((match = pattern.exec(String(input ?? '')))) {
942
+ args.push(match[1] ?? match[2] ?? match[3])
943
+ }
944
+ return args
945
+ }
946
+
947
+ interface FfmpegResult {
948
+ status: boolean
949
+ stdout: string
950
+ stderr: string
951
+ code: number | null
952
+ }
953
+
954
+ function runFfmpegBinary (bin: string, args: string[], options: any = {}): Promise<FfmpegResult> {
955
+ return new Promise((resolve) => {
956
+ let child: any
957
+ try {
958
+ child = spawn(bin, args, { windowsHide: true })
959
+ } catch (error: any) {
960
+ resolve({ status: false, stdout: '', stderr: String(error?.message ?? error), code: null })
961
+ return
962
+ }
963
+ let stdout = ''
964
+ let stderr = ''
965
+ // 默认 10 分钟兜底,避免遇到损坏文件 / 网络流时永远挂着(kkk 里都是 await 调用)
966
+ const timeout = Number(options?.timeout ?? 600000)
967
+ const timer = timeout > 0 ? setTimeout(() => {
968
+ try {
969
+ child.kill('SIGKILL')
970
+ } catch { /* 忽略 */ }
971
+ }, timeout) : null
972
+ const done = (result: FfmpegResult) => {
973
+ if (timer) clearTimeout(timer)
974
+ resolve(result)
975
+ }
976
+ child.stdout?.on('data', (chunk: Buffer) => {
977
+ const text = String(chunk)
978
+ stdout += text
979
+ options?.onStdout?.(text)
980
+ })
981
+ child.stderr?.on('data', (chunk: Buffer) => {
982
+ const text = String(chunk)
983
+ stderr += text
984
+ options?.onStderr?.(text)
985
+ })
986
+ child.on('error', (error: any) => {
987
+ done({ status: false, stdout, stderr: stderr + String(error?.message ?? error), code: null })
988
+ })
989
+ child.on('close', (code: number | null) => {
990
+ done({ status: code === 0, stdout, stderr, code })
991
+ })
992
+ })
993
+ }
994
+
995
+ export const ffmpeg = (input: string, options: any = {}): Promise<FfmpegResult> =>
996
+ runFfmpegBinary(resolveFfmpegBin(options), splitCommandArgs(input), options)
997
+
998
+ export const ffprobe = (input: string, options: any = {}): Promise<FfmpegResult> => {
999
+ const bin = typeof options?.ffprobePath === 'string' && options.ffprobePath
1000
+ ? options.ffprobePath
1001
+ : (process.env.FFPROBE_PATH || siblingFfprobe(resolveFfmpegBin(options)))
1002
+ return runFfmpegBinary(bin, splitCommandArgs(input), options)
1003
+ }
1004
+
1005
+ /** 是否真的能用 ffmpeg(弹幕烧录 / 转码相关功能用它决定要不要提示「未接入」) */
1006
+ export const isFfmpegAvailable = (): boolean => {
1007
+ try {
1008
+ const bin = resolveFfmpegBin()
1009
+ if (bin === 'ffmpeg') {
1010
+ // PATH 里的 ffmpeg:查一下常见位置即可,不为了探测去启动进程
1011
+ return true
1012
+ }
1013
+ return fs.existsSync(bin)
1014
+ } catch {
1015
+ return false
1016
+ }
1017
+ }
1018
+
1019
+ /** express 应用实例(模块级懒加载,供 module/server 使用) */
1020
+ let expressApp: any
1021
+ export const app = new Proxy({}, {
1022
+ get (_target, prop) {
1023
+ if (!expressApp) {
1024
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1025
+ const express = require('express')
1026
+ expressApp = express()
1027
+ }
1028
+ return expressApp[prop]
1029
+ }
1030
+ })
1031
+
1032
+ export const karin = {
1033
+ version: '1.16.3-koishi-compat',
1034
+ command,
1035
+ task,
1036
+ on,
1037
+ getBot: (selfId: string) => resolveBot(selfId),
1038
+ /**
1039
+ * 取所有在线 bot 的 id。
1040
+ *
1041
+ * 不能只读 `bot.selfId` —— 实测 `ctx.bots` 里混着 console / sandbox 这类
1042
+ * selfId 为 null 的实例,过滤前会得到 `[null, null]`,推送任务就永远"找不到可用 bot"。
1043
+ * 这里把几种可能的来源都兜上,并且丢掉空值。
1044
+ */
1045
+ getAllBotID: (): string[] => {
1046
+ const bots: any[] = tryGetRuntime()?.ctx.bots ?? []
1047
+ const ids = bots.map((bot) => bot?.selfId ?? bot?.user?.id ?? bot?.internal?.selfId ?? '')
1048
+ return [...new Set(ids.map((id) => String(id ?? '')).filter((id) => id && id !== 'undefined'))]
1049
+ },
1050
+ getAllBotList: () => (tryGetRuntime()?.ctx.bots ?? []).map((bot) => ({ bot: new KkkBot(bot), status: 1 })),
1051
+ contactGroup,
1052
+ contactFriend,
1053
+ sendMsg,
1054
+ sendMaster,
1055
+ ctx: waitContext,
1056
+ /** karin.template 在 Koishi 侧无对应实现 */
1057
+ template: (_name: string) => undefined,
1058
+ logger,
1059
+ segment,
1060
+ root: {}
1061
+ }
1062
+
1063
+ export { logger, segment, syncUpstreamToKoishi }
1064
+ export default karin