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,1244 @@
1
+ import fs from 'node:fs'
2
+ import { sendSlicedImage } from '@/module/utils/ImageSlice'
3
+ import { buildMarkdownImageMessage } from '@/module/utils/QqPanel'
4
+ import { sendParseTip } from '@/module/utils/parseTip'
5
+ import { sendParseTip } from '@/module/utils/parseTip'
6
+
7
+ import { type DouyinEmojiListResponse, DouyinVideoWorkResponse } from '@ikenxuan/amagi'
8
+ import type { RichTextEmojiDefinition } from '@kkk/richtext'
9
+ import type { DouyinUserVideoListData } from '@template/template/douyin/user_profile/components/types'
10
+ import { format } from 'date-fns'
11
+ import karin, { type Elements, Message, SendMessage } from 'node-karin'
12
+ import { common, logger, mkdirSync, segment } from 'node-karin'
13
+
14
+ import type { ParseWorkType } from '@/module/db'
15
+ import {
16
+ Base,
17
+ baseHeaders,
18
+ buildGoogleMotionPhoto,
19
+ Common,
20
+ Count,
21
+ downloadFile,
22
+ downloadVideo,
23
+ fileInfo,
24
+ type LiveImageMergeOptions,
25
+ loopVideoWithTransition,
26
+ Networks,
27
+ processLocalImageFile,
28
+ processImageUrl,
29
+ Render,
30
+ uploadFile
31
+ } from '@/module/utils'
32
+ import { Config } from '@/module/utils/Config'
33
+ import { EmojiReactionManager, getEmojiId } from '@/module/utils/EmojiReaction'
34
+ import { getParseOverride } from '@/module/utils/ParseOverride'
35
+ import { douyinComments } from '@/platform/douyin'
36
+ import { burnDouyinDanmaku, type DouyinDanmakuElem } from '@/platform/douyin/danmaku'
37
+ import { renderWorkImage } from '@/platform/douyin/push/render'
38
+ import { buildDouyinWorkDetail } from '@/platform/douyin/types'
39
+ import { douyinProcessVideos, type dyVideo, buildDouyinPlayUrl } from '@/platform/douyin/videoQuality'
40
+ import { getDouyinLiveImageSendPolicy } from '@/platform/douyin/workType'
41
+ import { DouyinDataTypes, DouyinIdData } from '@/types'
42
+
43
+ let mp4size = ''
44
+ let img
45
+ export class DouYin extends Base {
46
+ e: Message
47
+ type: DouyinDataTypes[keyof DouyinDataTypes]
48
+ is_slides: boolean
49
+ /** 强制烧录弹幕(用于 #弹幕解析 命令) */
50
+ forceBurnDanmaku: boolean
51
+ /**
52
+ * 图集/实况的图片 md 与视频**先攒着**,不在分支里立刻发 ——
53
+ * 这样顺序才是:信息卡 -> 评论区 -> 图集图片(+提示) -> 实况视频(用户指定)。
54
+ */
55
+ pendingGalleryMd: string | null
56
+ /** 实况视频(md 塞不下,最后单独发) */
57
+ pendingGalleryVideos: any[]
58
+ /** 待发送的 BGM 本地路径(推迟到图集之后发) */
59
+ pendingBgmPath: string | null
60
+
61
+ /** 标记是否已处理 live 图(用于判断是否需要发送音频) */
62
+ hasProcessedLiveImage: boolean
63
+ /**
64
+ * 本次解析的内容形态,供统计埋点读取。
65
+ * 取不到时只是不记「内容形态」这个维度,解析总次数照常累计。
66
+ */
67
+ workType?: ParseWorkType
68
+ get botadapter(): string {
69
+ return this.e.bot?.adapter?.name
70
+ }
71
+
72
+ constructor(e: Message, iddata: DouyinIdData, options?: { forceBurnDanmaku?: boolean }) {
73
+ super(e)
74
+ this.e = e
75
+ this.type = iddata?.type
76
+ this.is_slides = false
77
+ this.forceBurnDanmaku = options?.forceBurnDanmaku ?? false
78
+ this.hasProcessedLiveImage = false
79
+ this.pendingGalleryMd = null
80
+ this.pendingGalleryVideos = []
81
+ }
82
+
83
+ async DouyinHandler(data: DouyinIdData) {
84
+ await sendParseTip(this.e, '抖音')
85
+ switch (this.type) {
86
+ case 'one_work': {
87
+ const VideoData = await this.amagi.douyin.fetcher.parseWork({
88
+ aweme_id: data.aweme_id
89
+ })
90
+
91
+ if (VideoData.data.aweme_detail === null) {
92
+ throw new Error('获取作品详情失败,可能是因为该作品已被删除或设置为私密。')
93
+ }
94
+ // 根据 API 返回的数据判断作品类型,而不是依赖 URL
95
+ // aweme_type: 0=视频, 68=图集, 163=文章
96
+ const aweme_type = VideoData.data.aweme_detail.aweme_type
97
+ const isArticle = aweme_type === 163
98
+ const isVideo = aweme_type === 0 || aweme_type === 55
99
+
100
+ const CommentsData = await this.amagi.douyin.fetcher.fetchWorkComments({
101
+ aweme_id: data.aweme_id,
102
+ number: Config.douyin.numcomment
103
+ })
104
+ this.is_slides = VideoData.data.aweme_detail.is_slides === true
105
+ // 统计用的内容形态,与下面渲染 `Type` 用的是同一套判定,保证统计口径和用户看到的一致
106
+ this.workType = isArticle ? 'article' : isVideo ? 'video' : this.is_slides ? 'collection' : 'gallery'
107
+ let g_video_url = ''
108
+ let g_title
109
+
110
+ /** 图集 */
111
+ let imagenum = 0
112
+ const image_res = []
113
+ if (!isVideo && !isArticle) {
114
+ switch (true) {
115
+ // 图集
116
+ case this.is_slides === false && VideoData.data.aweme_detail.images !== null: {
117
+ const image_data = []
118
+ const imageres = []
119
+ let image_url = ''
120
+ // 使用可选链和空值合并操作符确保安全访问
121
+ const images = VideoData.data.aweme_detail.images ?? []
122
+
123
+ // 检查是否包含 live 图(clip_type !== 2)
124
+ const hasLiveImage = images.some((item) => (item.clip_type ?? 2) !== 2)
125
+
126
+ if (hasLiveImage) {
127
+ // 包含 live 图,需要特殊处理
128
+ const processedImages: Elements[] = []
129
+ const temp: fileInfo[] = []
130
+ let hasGeneratedLivePhoto = false // 标记是否生成了实况图
131
+
132
+ // 设置标题
133
+ const title = VideoData.data.aweme_detail.preview_title.substring(0, 50).replace(/[\\/:*?"<>|\r\n]/g, ' ')
134
+ g_title = title
135
+
136
+ /** 下载 BGM(如果存在) */
137
+ let liveimgbgm: fileInfo | null = null
138
+ let bgmContext: LiveImageMergeOptions['context'] | null = null
139
+ const mergeMode = Config.douyin.liveImageMergeMode ?? 'independent'
140
+
141
+ if (VideoData.data.aweme_detail.music) {
142
+ let mp3Path = ''
143
+ if (VideoData.data.aweme_detail.music.play_url.uri === '') {
144
+ const extraData = JSON.parse(VideoData.data.aweme_detail.music.extra)
145
+ mp3Path = extraData.original_song_url
146
+ } else {
147
+ mp3Path = VideoData.data.aweme_detail.music.play_url.uri
148
+ }
149
+
150
+ liveimgbgm = await downloadFile(mp3Path, {
151
+ title: `Douyin_tmp_A_${Date.now()}.mp3`,
152
+ headers: this.headers
153
+ })
154
+ temp.push(liveimgbgm)
155
+ }
156
+
157
+ for (const [index, imageItem] of images.entries()) {
158
+ imagenum++
159
+
160
+ // 静态图片,clip_type为2或undefined
161
+ if (imageItem.clip_type === 2 || imageItem.clip_type === undefined) {
162
+ image_url = imageItem.url_list[2] || imageItem.url_list[1]
163
+ const imageUrl = await processImageUrl(image_url, g_title, index)
164
+ processedImages.push(segment.image(imageUrl))
165
+
166
+ if (Config.app.removeCache === false) {
167
+ mkdirSync(`${Common.tempDri.images}${g_title}`)
168
+ const path = `${Common.tempDri.images}${g_title}/${index + 1}.png`
169
+ await new Networks({ url: image_url, type: 'arraybuffer' })
170
+ .getData()
171
+ .then((data) => fs.promises.writeFile(path, Buffer.from(data)))
172
+ }
173
+ continue
174
+ }
175
+
176
+ /**
177
+ * live 图。
178
+ * 生成类型里 `video` 是可选的(静态图不带它),静态图上面已经 `continue` 掉了;
179
+ * 这里再兜一层,遇到标着 clip_type 却没有视频源的脏数据就跳过,别拿 undefined 去拼 URL。
180
+ */
181
+ const liveVideo = imageItem.video
182
+ if (!liveVideo) continue
183
+ const liveimg = await downloadFile(buildDouyinPlayUrl(liveVideo.play_addr_h264), {
184
+ title: `Douyin_tmp_V_${Date.now()}.mp4`,
185
+ headers: this.headers
186
+ })
187
+
188
+ if (liveimg.filepath) {
189
+ const outputPath = Common.tempDri.video + `Douyin_Result_${Date.now()}.mp4`
190
+ const loopCount = imageItem.clip_type === 4 ? 1 : 3
191
+ let staticImgPath = ''
192
+ if (imageItem.url_list?.[0]) {
193
+ const staticImg = await downloadFile(imageItem.url_list[0], {
194
+ title: `Douyin_static_${Date.now()}_${index}.jpg`,
195
+ headers: this.headers,
196
+ filepath: Common.tempDri.images + `Douyin_static_${Date.now()}_${index}.jpg`
197
+ })
198
+ temp.push({ filepath: staticImg.filepath, totalBytes: 0 })
199
+ staticImgPath = staticImg.filepath ?? ''
200
+ }
201
+
202
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(
203
+ imageItem.clip_type,
204
+ Config.app.livePhotoMode ?? 'video_and_livephoto'
205
+ )
206
+
207
+ // 生成视频
208
+ if (shouldGenerateVideo) {
209
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath)
210
+ const safeStaticPath = staticImgPath || liveimg.filepath
211
+ const result = await loopVideoWithTransition({
212
+ inputPath: liveimg.filepath,
213
+ outputPath,
214
+ loopCount,
215
+ staticImagePath: safeStaticPath,
216
+ transitionEnabled,
217
+ bgmPath: liveimgbgm?.filepath,
218
+ mergeMode,
219
+ context: bgmContext ?? undefined
220
+ })
221
+ const success = result.success
222
+ if (mergeMode === 'continuous' && result.context) {
223
+ bgmContext = result.context
224
+ }
225
+
226
+ if (success) {
227
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`
228
+ fs.renameSync(outputPath, filePath)
229
+ logger.mark(`视频文件重命名完成: ${outputPath.split('/').pop()} -> ${filePath.split('/').pop()}`)
230
+ temp.push({ filepath: filePath, totalBytes: 0 })
231
+ const videoPath =
232
+ Config.app.videoSendMode === 'base64'
233
+ ? `base64://${fs.readFileSync(filePath).toString('base64')}`
234
+ : `file://${filePath}`
235
+ processedImages.push(segment.video(videoPath))
236
+ }
237
+ }
238
+
239
+ // 生成实况图(clip_type === 5 是 livePhoto)
240
+ if (shouldGenerateLivePhoto && imageItem.clip_type === 5 && imageItem.url_list?.[0]) {
241
+ let hasPushedMotionPhotoCover = false
242
+ if (staticImgPath) {
243
+ const motionPhotoCoverPath =
244
+ Common.tempDri.images + `MVIMG_${format(new Date(), 'yyyyMMdd_HHmmss_SSS')}_${index}.jpg`
245
+ const motionPhotoCreated = await buildGoogleMotionPhoto({
246
+ imagePath: staticImgPath,
247
+ videoPath: liveimg.filepath,
248
+ outputPath: motionPhotoCoverPath
249
+ })
250
+ if (motionPhotoCreated) {
251
+ temp.push({ filepath: motionPhotoCoverPath, totalBytes: 0 })
252
+ const motionPhotoCover = processLocalImageFile(motionPhotoCoverPath)
253
+ processedImages.push(segment.image(motionPhotoCover))
254
+ hasPushedMotionPhotoCover = true
255
+ }
256
+ }
257
+ if (!hasPushedMotionPhotoCover) {
258
+ const imageUrl = await processImageUrl(imageItem.url_list[0], g_title, index)
259
+ processedImages.push(segment.image(imageUrl))
260
+ } else {
261
+ hasGeneratedLivePhoto = true // 标记已生成实况图
262
+ }
263
+ }
264
+
265
+ logger.mark('正在尝试删除缓存文件')
266
+ await Common.removeFile(liveimg.filepath, true)
267
+ }
268
+ }
269
+
270
+ // 如果生成了实况图,添加提示图片
271
+ if (hasGeneratedLivePhoto) {
272
+ const tipImg = await Render(this.e, 'other/live-photo-tip', {
273
+ title: '实况照片已生成',
274
+ description: '保存原图到相册即可识别为实况图'
275
+ })
276
+ processedImages.push(...tipImg)
277
+ }
278
+
279
+ /**
280
+ * 实况图/图集:**静态图合并成一条 markdown 消息**,实况视频单独发。
281
+ *
282
+ * 原来是整包走合并转发(makeForward + sendForwardMsg)—— 官方 bot 上转发经常发不出去,
283
+ * 表现就是「图集完全没反应」。md 里连续图片是紧贴渲染的,视觉上仍是一整套图。
284
+ * 视频没法塞进 markdown,所以挑出来单独发。
285
+ */
286
+ try {
287
+ if (processedImages.length === 0) {
288
+ logger.warn(`抖音图集解析未生成可发送内容,aweme_id=${VideoData.data.aweme_detail.aweme_id}`)
289
+ } else {
290
+ const imageSegments = processedImages.filter(/** 只认图片元素:Satori 里是 img/image;**不能**用 attrs.src 兜底,视频元素也有 src! */
291
+ (item: any) => (item?.type === 'img' || item?.type === 'image') && !String(item?.attrs?.src ?? '').startsWith('base64://video'))
292
+ const otherSegments = processedImages.filter((item: any) => !((item?.type === 'img' || item?.type === 'image') && !String(item?.attrs?.src ?? '').startsWith('base64://video')))
293
+ // 静态图:一条 md 合并(图片地址从元素里取)
294
+ const mdMessage = await buildMarkdownImageMessage(
295
+ imageSegments.map((item: any) => String(item?.attrs?.src ?? '')).filter(Boolean)
296
+ )
297
+ if (mdMessage) {
298
+ // 存起来,等信息卡和评论区发完再发(用户指定顺序)
299
+ this.pendingGalleryMd = mdMessage
300
+ } else if (imageSegments.length) {
301
+ await this.e.reply(imageSegments)
302
+ }
303
+ // 实况视频:md 塞不下,排在图片之后单独发
304
+ this.pendingGalleryVideos.push(...otherSegments)
305
+ }
306
+ } finally {
307
+ for (const item of temp) {
308
+ await Common.removeFile(item.filepath, true)
309
+ }
310
+ }
311
+
312
+ // 标记已处理 live 图,不需要单独发送音频
313
+ this.hasProcessedLiveImage = true
314
+ } else {
315
+ // 纯静态图集,使用原有逻辑
316
+ for (const [index, imageItem] of images.entries()) {
317
+ // 获取图片地址,优先使用第三个URL,其次使用第二个URL
318
+ image_url = imageItem.url_list[2] || imageItem.url_list[1]
319
+
320
+ // 处理标题,去除特殊字符
321
+ const title = VideoData.data.aweme_detail.preview_title.substring(0, 50).replace(/[\\/:*?"<>|\r\n]/g, ' ')
322
+ g_title = title
323
+
324
+ const imageUrl = await processImageUrl(image_url, g_title, index)
325
+ imageres.push(segment.image(imageUrl))
326
+ imagenum++
327
+
328
+ if (Config.app.removeCache === false) {
329
+ mkdirSync(`${Common.tempDri.images}${g_title}`)
330
+ const path = `${Common.tempDri.images}${g_title}/${index + 1}.png`
331
+ await new Networks({ url: image_url, type: 'arraybuffer' })
332
+ .getData()
333
+ .then((data) => fs.promises.writeFile(path, Buffer.from(data)))
334
+ }
335
+ }
336
+ const res = common.makeForward(
337
+ imageres,
338
+ Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId,
339
+ Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name
340
+ )
341
+ image_data.push(res)
342
+ image_res.push(image_data)
343
+ /**
344
+ * 统一成**一条** markdown 消息。
345
+ *
346
+ * 原来是「单图直接发、多图走合并转发」——官方 bot 上转发经常发不出去,
347
+ * 就会退化成一张图一条消息,把群刷屏。这里改成单条 md,
348
+ * 图片按 420px 等比缩放(`![#宽px #高px](url)`),一条消息装下整套图集。
349
+ */
350
+ const mdMessage = await buildMarkdownImageMessage(
351
+ images.map((item: any) => item.url_list[2] || item.url_list[1]).filter(Boolean)
352
+ )
353
+ if (mdMessage) {
354
+ // 存起来,等信息卡和评论区发完再发(用户指定顺序)
355
+ this.pendingGalleryMd = mdMessage
356
+ } else if (imageres.length === 1) {
357
+ await this.e.reply(imageres[0])
358
+ } else {
359
+ await this.e.bot.sendForwardMsg(this.e.contact, res, {
360
+ source: '图片合集',
361
+ summary: `查看${res.length}张图片消息`,
362
+ prompt: '抖音图集解析结果',
363
+ news: [{ text: '点击查看解析结果' }]
364
+ })
365
+ }
366
+ }
367
+ break
368
+ }
369
+ // 合辑
370
+ case VideoData.data.aweme_detail.is_slides === true && VideoData.data.aweme_detail.images !== null: {
371
+ const images: Elements[] = []
372
+ const temp: fileInfo[] = []
373
+ let hasGeneratedLivePhoto = false // 标记是否生成了实况图
374
+
375
+ /** 下载 BGM(如果存在) */
376
+ let liveimgbgm: fileInfo | null = null
377
+ let bgmContext: LiveImageMergeOptions['context'] | null = null
378
+ const mergeMode = Config.douyin.liveImageMergeMode ?? 'independent'
379
+
380
+ if (VideoData.data.aweme_detail.music) {
381
+ let mp3Path = ''
382
+ // 该声音由于版权原因在当前地区不可用
383
+ if (VideoData.data.aweme_detail.music.play_url.uri === '') {
384
+ const extraData = JSON.parse(VideoData.data.aweme_detail.music.extra)
385
+ mp3Path = extraData.original_song_url
386
+ } else {
387
+ mp3Path = VideoData.data.aweme_detail.music.play_url.uri
388
+ }
389
+
390
+ liveimgbgm = await downloadFile(mp3Path, {
391
+ title: `Douyin_tmp_A_${Date.now()}.mp3`,
392
+ headers: this.headers
393
+ })
394
+ temp.push(liveimgbgm)
395
+ }
396
+
397
+ const images1 = VideoData.data.aweme_detail.images ?? []
398
+ if (!images1.length) {
399
+ logger.debug('未获取到合辑的图片数据')
400
+ }
401
+
402
+ for (const [index, item] of images1.entries()) {
403
+ imagenum++
404
+ // 静态图片,clip_type为2或undefined
405
+ if (item.clip_type === 2 || item.clip_type === undefined) {
406
+ const imageUrl = await processImageUrl(item.url_list[0], g_title, index)
407
+ images.push(segment.image(imageUrl))
408
+ continue
409
+ }
410
+ /** 动图/短片(同图集分支:`video` 在生成类型里是可选的,没有就跳过这一项) */
411
+ const liveVideo = item.video
412
+ if (!liveVideo) continue
413
+ const livePhoto = await downloadFile(buildDouyinPlayUrl(liveVideo.play_addr_h264), {
414
+ title: `Douyin_tmp_V_${Date.now()}.mp4`,
415
+ headers: this.headers
416
+ })
417
+
418
+ if (livePhoto.filepath) {
419
+ const outputPath = Common.tempDri.video + `Douyin_Result_${Date.now()}.mp4`
420
+ const loopCount = item.clip_type === 4 ? 1 : 3
421
+ let staticImgPath = ''
422
+ if (item.url_list?.[0]) {
423
+ const staticImg = await downloadFile(item.url_list[0], {
424
+ title: `Douyin_static_${Date.now()}_${index}.jpg`,
425
+ headers: this.headers,
426
+ filepath: Common.tempDri.images + `Douyin_static_${Date.now()}_${index}.jpg`
427
+ })
428
+ temp.push({ filepath: staticImg.filepath, totalBytes: 0 })
429
+ staticImgPath = staticImg.filepath ?? ''
430
+ }
431
+
432
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(
433
+ item.clip_type,
434
+ Config.app.livePhotoMode ?? 'video_and_livephoto'
435
+ )
436
+
437
+ // 生成视频
438
+ if (shouldGenerateVideo) {
439
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath)
440
+ const safeStaticPath = staticImgPath || livePhoto.filepath
441
+ const result = await loopVideoWithTransition({
442
+ inputPath: livePhoto.filepath,
443
+ outputPath,
444
+ loopCount,
445
+ staticImagePath: safeStaticPath,
446
+ transitionEnabled,
447
+ bgmPath: liveimgbgm?.filepath,
448
+ mergeMode,
449
+ context: bgmContext ?? undefined
450
+ })
451
+ const success = result.success
452
+ if (mergeMode === 'continuous' && result.context) {
453
+ bgmContext = result.context
454
+ }
455
+
456
+ if (success) {
457
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`
458
+ fs.renameSync(outputPath, filePath)
459
+ logger.mark(`视频文件重命名完成: ${outputPath.split('/').pop()} -> ${filePath.split('/').pop()}`)
460
+ temp.push({ filepath: filePath, totalBytes: 0 })
461
+ const videoPath =
462
+ Config.app.videoSendMode === 'base64'
463
+ ? `base64://${fs.readFileSync(filePath).toString('base64')}`
464
+ : `file://${filePath}`
465
+ images.push(segment.video(videoPath))
466
+ }
467
+ }
468
+
469
+ // 生成实况图(clip_type === 5 是 livePhoto,clip_type === 4 短片按视频发送)
470
+ if (shouldGenerateLivePhoto && item.clip_type === 5 && item.url_list?.[0]) {
471
+ let hasPushedMotionPhotoCover = false
472
+ if (staticImgPath) {
473
+ const motionPhotoCoverPath = Common.tempDri.images + `MVIMG_${format(new Date(), 'yyyyMMdd_HHmmss_SSS')}_${index}.jpg`
474
+ const motionPhotoCreated = await buildGoogleMotionPhoto({
475
+ imagePath: staticImgPath,
476
+ videoPath: livePhoto.filepath,
477
+ outputPath: motionPhotoCoverPath
478
+ })
479
+ if (motionPhotoCreated) {
480
+ temp.push({ filepath: motionPhotoCoverPath, totalBytes: 0 })
481
+ const motionPhotoCover = processLocalImageFile(motionPhotoCoverPath)
482
+ images.push(segment.image(motionPhotoCover))
483
+ hasPushedMotionPhotoCover = true
484
+ }
485
+ }
486
+ if (!hasPushedMotionPhotoCover) {
487
+ const imageUrl = await processImageUrl(item.url_list[0], g_title, index)
488
+ images.push(segment.image(imageUrl))
489
+ } else {
490
+ hasGeneratedLivePhoto = true // 标记已生成实况图
491
+ }
492
+ }
493
+
494
+ logger.mark('正在尝试删除缓存文件')
495
+ await Common.removeFile(livePhoto.filepath, true)
496
+ }
497
+ }
498
+
499
+ // 如果生成了实况图,添加提示图片
500
+ if (hasGeneratedLivePhoto) {
501
+ const tipImg = await Render(this.e, 'other/live-photo-tip', {
502
+ title: '实况照片已生成',
503
+ description: '保存原图到相册即可识别为实况图'
504
+ })
505
+ images.push(...tipImg)
506
+ }
507
+
508
+ try {
509
+ if (images.length === 0) {
510
+ logger.warn(`抖音合辑解析未生成可发送内容,aweme_id=${VideoData.data.aweme_detail.aweme_id}`)
511
+ } else {
512
+ /**
513
+ * **实况照片/合辑走的就是这个分支**(is_slides === true)。
514
+ * 原来是整包丢给合并转发 —— 官方 bot 上转发发不出去,就退化成
515
+ * 「一条一条发」,表现就是用户看到的「还是一条一条」。
516
+ *
517
+ * 改成和信息卡一样的处理:图片攒起来,等信息卡、评论区都发完,
518
+ * 再用**一条 markdown** 发出(提示图也一起放进去,排在最后);
519
+ * 实况视频 md 塞不下,紧随其后单独发。
520
+ */
521
+ const mergeSources: string[] = []
522
+ const mergeVideos: any[] = []
523
+ for (const item of images as any[]) {
524
+ // 同上:只认图片,视频要单独发(md 里放视频客户端不显示)
525
+ if (item?.type === 'img' || item?.type === 'image') {
526
+ const itemSrc = String(item?.attrs?.src ?? '')
527
+ // 诊断:把每张图地址的「开头」打出来,直接看出是 base64:// / file:// / http / 本地路径
528
+ logger.mark('[抖音] 图集图片地址[' + mergeSources.length + ']: ' + itemSrc.slice(0, 40) + ' … 长度 ' + itemSrc.length)
529
+ if (itemSrc) mergeSources.push(itemSrc)
530
+ } else {
531
+ mergeVideos.push(item)
532
+ }
533
+ }
534
+ if (mergeSources.length) {
535
+ const mdMessage = await buildMarkdownImageMessage(mergeSources)
536
+ if (mdMessage) {
537
+ this.pendingGalleryMd = mdMessage
538
+ } else {
539
+ // md 生成失败:图片退回逐张发(连同视频一起排队)
540
+ this.pendingGalleryVideos.push(...(images as any[]))
541
+ }
542
+ }
543
+ /**
544
+ * **视频无论如何都要排队** —— md 里放不了视频(客户端不显示),
545
+ * 之前写在 else 分支里,md 成功时视频就被整段丢掉了,
546
+ * 表现就是「图集只有 md,视频一个都没有」。
547
+ */
548
+ this.pendingGalleryVideos.push(...mergeVideos)
549
+ }
550
+ } finally {
551
+ for (const item of temp) {
552
+ await Common.removeFile(item.filepath, true)
553
+ }
554
+ }
555
+ break
556
+ }
557
+ }
558
+ }
559
+
560
+ /** 背景音乐 */
561
+ if (VideoData.data.aweme_detail.music) {
562
+ const music = VideoData.data.aweme_detail.music
563
+ let music_url = ''
564
+ // 该声音由于版权原因在当前地区不可用
565
+ if (music.play_url.uri === '') {
566
+ const extraData = JSON.parse(music.extra)
567
+ music_url = extraData.original_song_url
568
+ } else {
569
+ music_url = music.play_url.uri
570
+ }
571
+ if (!isVideo && Config.app.removeCache === false && music_url !== undefined) {
572
+ try {
573
+ const title = g_title ?? VideoData.data.aweme_detail.preview_title.substring(0, 50).replace(/[\\/:*?"<>|\r\n]/g, ' ')
574
+ const path = Common.tempDri.images + `${title}.mp3`
575
+ await downloadFile(music_url, { title, filepath: path })
576
+ } catch (error) {
577
+ console.log(error)
578
+ }
579
+ }
580
+ /**
581
+ * 图集、合辑、文章都发送BGM —— **但推迟到最后发**(用户要求语音排在图片之后)。
582
+ * 这里只下载好放进待发队列,真正的发送在下方 flush 之后。
583
+ */
584
+ const haspath = music_url && !isVideo && music_url !== undefined && !this.hasProcessedLiveImage
585
+ if (haspath) {
586
+ const audioFile = await downloadFile(music_url, {
587
+ title: `Douyin_BGM_${Date.now()}.mp3`,
588
+ headers: this.headers
589
+ })
590
+ if (audioFile.filepath) {
591
+ this.pendingBgmPath = audioFile.filepath
592
+ }
593
+ }
594
+ }
595
+
596
+ /** 视频 */
597
+ let FPS
598
+ const sendvideofile = true
599
+ type VideoType = NonNullable<DouyinVideoWorkResponse['aweme_detail']>['video']
600
+ /**
601
+ * 图文/文章作品的 video 字段不含 bit_rate,不能无条件初始化,
602
+ * 否则会在这里直接抛 TypeError;仅在视频分支内赋值,其余场景保持 null。
603
+ */
604
+ let video: VideoType | null = null
605
+ /** 按画质偏好选中、即将下载发送的那一路视频源 */
606
+ let selectedVideo: dyVideo | null = null
607
+ if (isVideo) {
608
+ // 视频地址特殊判断:play_addr_h264、play_addr、
609
+ video = VideoData.data.aweme_detail.video as VideoType
610
+ FPS = video.bit_rate[0]?.FPS ?? '获取失败' // FPS
611
+
612
+ logger.debug(`开始排除不符合条件的视频分辨率;\n
613
+ 共拥有${logger.yellow(video.bit_rate.length)}个视频源\n
614
+ 视频ID:${logger.green(VideoData.data.aweme_detail.aweme_id)}\n
615
+ 分享链接:${logger.green(VideoData.data.aweme_detail.share_url)}
616
+ `)
617
+ // 只把选中项取到局部变量,不再原地覆盖 video.bit_rate:
618
+ // video 是 aweme_detail.video 的同一个引用,覆盖它会把整个作品详情的视频源列表截断成一项,
619
+ // 污染后面所有拿 Detail_Data 的下游(渲染、推送复用)。
620
+ selectedVideo = douyinProcessVideos(video.bit_rate, Config.douyin.videoQuality, Config.douyin.maxAutoVideoSize)[0] ?? null
621
+ if (!selectedVideo) {
622
+ throw new Error(`未找到可用的视频源,aweme_id=${VideoData.data.aweme_detail.aweme_id}`)
623
+ }
624
+ // url_list[2] 是 www.douyin.com/aweme/v1/play 的包装 URL,会按 Douyin 负载均衡 302
625
+ // 到任意 CDN,部分 CDN(如 cjjd14.com、n98-v-ncdnon)返回非 MP4 乱码字节。
626
+ // 直接用 url_list[0] 的签名直链规避包装跳转。
627
+ g_video_url = selectedVideo.play_addr.url_list[0] ?? selectedVideo.play_addr.url_list[1] ?? selectedVideo.play_addr.url_list[2]
628
+ const title = VideoData.data.aweme_detail.preview_title.substring(0, 80).replace(/[\\/:*?"<>|\r\n]/g, ' ') // video title
629
+ g_title = title
630
+ mp4size = (selectedVideo.play_addr.data_size / (1024 * 1024)).toFixed(2)
631
+ }
632
+
633
+ /**
634
+ * 从面板点进来的解析:卡片在面板里已经发过了,这里不再重复发一张。
635
+ * (bilibili 那边同样处理,见 bilibili.ts 的 fromPanel 判断)
636
+ */
637
+ const fromPanelDouyin = getParseOverride()?.fromPanel === true
638
+ if (!fromPanelDouyin && Config.douyin.sendContent.includes('info')) {
639
+ if (Config.douyin.videoInfoMode === 'text') {
640
+ // 构建回复内容数组
641
+ const replyContent: SendMessage = []
642
+ const { digg_count, share_count, collect_count, comment_count, recommend_count } = VideoData.data.aweme_detail.statistics
643
+ const coverImageUrl = isArticle
644
+ ? VideoData.data.aweme_detail.video.origin_cover.url_list[0]
645
+ : isVideo
646
+ ? (VideoData.data.aweme_detail.video.animated_cover?.url_list[0] ?? VideoData.data.aweme_detail.video.cover.url_list[0])
647
+ : VideoData.data.aweme_detail.images![0].url_list[0]
648
+ const coverUrl = await processImageUrl(coverImageUrl, VideoData.data.aweme_detail.desc)
649
+ const contentMap = {
650
+ cover: segment.image(coverUrl),
651
+ title: segment.text(`\n📺 标题: ${VideoData.data.aweme_detail.desc}\n`),
652
+ author: segment.text(`\n👤 作者: ${VideoData.data.aweme_detail.author.nickname}\n`),
653
+ stats: segment.text(formatVideoStats(digg_count, share_count, collect_count, comment_count, recommend_count))
654
+ }
655
+ // 重新排序
656
+ const fixedOrder: (keyof typeof contentMap)[] = ['cover', 'title', 'author', 'stats']
657
+ fixedOrder.forEach((item) => {
658
+ if (Config.douyin.displayContent.includes(item) && contentMap[item]) {
659
+ replyContent.push(contentMap[item])
660
+ }
661
+ })
662
+ if (replyContent.length > 0) {
663
+ this.e.reply(replyContent)
664
+ }
665
+ } else {
666
+ const aweme = VideoData.data.aweme_detail
667
+ const userProfile = await this.amagi.douyin.fetcher.fetchUserProfile({
668
+ sec_uid: aweme.author.sec_uid
669
+ })
670
+ // 非视频作品使用不带追踪参数的规范短链接,避免二维码内容过长影响扫描识别。
671
+ const shareLink =
672
+ isVideo && selectedVideo
673
+ ? buildDouyinPlayUrl(selectedVideo.play_addr)
674
+ : `https://www.douyin.com/${isArticle ? 'article' : 'note'}/${aweme.aweme_id}`
675
+ const workInfoImg = await renderWorkImage({
676
+ e: this.e,
677
+ // 不再向 Detail_Data 里覆盖 video.bit_rate 塞入选档结果:
678
+ // 那会篡改原始 aweme 结构(还会给图文/文章作品注入伪造的 video 字段),
679
+ // 清晰度展示信息改由 videoSource 显式传递。
680
+ Detail_Data: buildDouyinWorkDetail(aweme, { user_info: userProfile.data }),
681
+ videoSource: selectedVideo,
682
+ create_time: aweme.create_time,
683
+ shareLink,
684
+ dynamicTypeLabel: isArticle ? '文章作品' : isVideo ? '视频作品' : this.is_slides ? '合辑作品' : '图文作品'
685
+ })
686
+ await this.e.reply(workInfoImg)
687
+ }
688
+ }
689
+
690
+ if (Config.douyin.sendContent.includes('comment')) {
691
+ const EmojiData = await this.amagi.douyin.fetcher.fetchEmojiList()
692
+ const list = Emoji(EmojiData.data)
693
+ const douyinCommentsRes = await douyinComments(CommentsData.data, list)
694
+ if (!douyinCommentsRes.CommentsData.length) {
695
+ await this.e.reply('这个作品没有评论 ~')
696
+ } else {
697
+ const suggest: string[] = []
698
+ if (VideoData.data.aweme_detail?.suggest_words?.suggest_words) {
699
+ for (const item of VideoData.data.aweme_detail.suggest_words.suggest_words) {
700
+ if (item.words && item.scene === 'comment_top_rec') {
701
+ for (const v of item.words) {
702
+ if (v.word) {
703
+ suggest.push(v.word)
704
+ }
705
+ }
706
+ }
707
+ }
708
+ }
709
+ const aweme = VideoData.data.aweme_detail
710
+ const img = await Render(this.e, 'douyin/comment', {
711
+ Type: isArticle ? '文章' : isVideo ? '视频' : this.is_slides ? '合辑' : '图集',
712
+ CommentsData: douyinCommentsRes.CommentsData,
713
+ CommentLength: douyinCommentsRes.CommentsData.length ?? 0,
714
+ share_url: isVideo && selectedVideo ? buildDouyinPlayUrl(selectedVideo.play_addr) : aweme.share_url,
715
+ VideoSize: mp4size,
716
+ VideoFPS: FPS,
717
+ ImageLength: imagenum,
718
+ Region: aweme.region,
719
+ suggestWrod: suggest,
720
+ Resolution: selectedVideo ? `${selectedVideo.play_addr.width} x ${selectedVideo.play_addr.height}` : null,
721
+ maxDepth: 6,
722
+ Author: aweme.author.nickname,
723
+ AuthorAvatar: aweme.author.avatar_thumb.url_list[0],
724
+ Statistics: {
725
+ digg_count: aweme.statistics.digg_count,
726
+ comment_count: aweme.statistics.comment_count,
727
+ share_count: aweme.statistics.share_count,
728
+ collect_count: aweme.statistics.collect_count
729
+ },
730
+ CreateTime: aweme.create_time
731
+ })
732
+ const messageElements = []
733
+ if (Config.douyin.commentImageCollection && douyinCommentsRes.image_url.length > 0) {
734
+ for (const [index, v] of douyinCommentsRes.image_url.entries()) {
735
+ const imageUrl = await processImageUrl(v, VideoData.data.aweme_detail.desc, index)
736
+ messageElements.push(segment.image(imageUrl))
737
+ }
738
+ /**
739
+ * 评论图片收集:**合并成一条 markdown** 发送。
740
+ *
741
+ * 原来是合并转发 —— 官方 bot 上转发经常发不出去,会退化成一张图一条消息。
742
+ * md 里连续图片是紧贴渲染的,一条消息就能装完整套图。
743
+ */
744
+ const mdMessage = await buildMarkdownImageMessage(
745
+ messageElements.map((item: any) => String(item?.attrs?.src ?? '')).filter(Boolean)
746
+ )
747
+ if (mdMessage) {
748
+ await this.e.reply(mdMessage)
749
+ } else {
750
+ const res = common.makeForward(
751
+ messageElements,
752
+ Config.app.fakeForward ? this.e.sender.userId : this.e.bot.account.selfId,
753
+ Config.app.fakeForward ? this.e.sender.nick : this.e.bot.account.name
754
+ )
755
+ await this.e.bot.sendForwardMsg(this.e.contact, res, {
756
+ source: '评论图片收集',
757
+ summary: `查看${messageElements.length}张图片`,
758
+ prompt: '抖音评论解析结果',
759
+ news: [{ text: '点击查看解析结果' }]
760
+ })
761
+ }
762
+ }
763
+ // 评论卡可能极长(实测 2880x40000),交给切片+md 拼接发送,避免 QQ 拒收
764
+ await sendSlicedImage(this.e, img)
765
+ }
766
+ }
767
+
768
+ /**
769
+ * 图集图片(+保存提示)→ **一条 markdown**,实况视频紧随其后。
770
+ * 放在这里是因为:信息卡、评论区都已经发完了(用户指定顺序)。
771
+ */
772
+ logger.mark('[抖音] 准备发送图集: md=' + (this.pendingGalleryMd ? '有' : '无') + ' 视频=' + this.pendingGalleryVideos.length + ' 段')
773
+ if (this.pendingGalleryMd) {
774
+ try {
775
+ await this.e.reply(this.pendingGalleryMd)
776
+ logger.mark('[抖音] 图集已用一条 markdown 发送(信息卡 -> 评论区 -> 图片)')
777
+ } catch (error: any) {
778
+ logger.warn('[抖音] 图集 md 发送失败: ' + String(error?.message ?? error))
779
+ }
780
+ this.pendingGalleryMd = null
781
+ }
782
+ for (const galleryVideo of this.pendingGalleryVideos) {
783
+ await this.e.reply(galleryVideo)
784
+ }
785
+ this.pendingGalleryVideos = []
786
+
787
+ // BGM(语音):排在图集图片之后发
788
+ if (this.pendingBgmPath) {
789
+ try {
790
+ const audioBase64 = 'base64://' + fs.readFileSync(this.pendingBgmPath).toString('base64')
791
+ await this.e.reply(segment.record(audioBase64, false))
792
+ } catch (error: any) {
793
+ logger.debug('[抖音] BGM 发送失败: ' + String(error?.message ?? error))
794
+ } finally {
795
+ await Common.removeFile(this.pendingBgmPath, true).catch(() => undefined)
796
+ this.pendingBgmPath = null
797
+ }
798
+ }
799
+
800
+ /** 发送视频 */
801
+ if (sendvideofile && isVideo && !isArticle && Config.douyin.sendContent.includes('video')) {
802
+ // 获取弹幕数据(如果开启弹幕烧录)
803
+ let danmakuList: DouyinDanmakuElem[] = []
804
+ if ((this.forceBurnDanmaku || Config.douyin.burnDanmaku) && video) {
805
+ try {
806
+ const duration = video.duration // 视频时长(毫秒)
807
+ logger.mark(`[抖音] 视频时长: ${duration}ms, 开始获取弹幕数据`)
808
+ const danmakuData = await this.amagi.douyin.fetcher.fetchDanmakuList({
809
+ aweme_id: data.aweme_id,
810
+ duration
811
+ })
812
+ /**
813
+ * 取值层级:amagi 的返回是 `{ data: { data: { danmaku_list } } }`
814
+ * (和 B站那边 `res.data?.data?.elems` 一样多包一层)。
815
+ * 原来只读到第一层 `data.danmaku_list` → 永远是 0 条弹幕,压根不会烧。
816
+ * 这里两种层级都兼容,万一还取不到就把响应结构打进日志,方便继续查。
817
+ */
818
+ const raw: any = danmakuData as any
819
+ const list: any[] | undefined =
820
+ raw?.data?.danmaku_list ?? raw?.data?.data?.danmaku_list ?? raw?.danmaku_list
821
+ if (Array.isArray(list) && list.length) {
822
+ danmakuList = list
823
+ logger.mark(`[抖音] 获取到 ${danmakuList.length} 条弹幕`)
824
+ } else {
825
+ logger.mark(
826
+ '[抖音] 没取到弹幕列表;响应结构: ' +
827
+ JSON.stringify(Object.keys(raw?.data ?? {})) + ' / 内层 ' +
828
+ JSON.stringify(Object.keys((raw?.data as any)?.data ?? {}))
829
+ )
830
+ }
831
+ } catch (err) {
832
+ logger.warn('[抖音] 获取弹幕失败,将不烧录弹幕', err)
833
+ }
834
+ }
835
+
836
+ // 如果需要烧录弹幕,先下载视频再烧录
837
+ if (!(this.forceBurnDanmaku || Config.douyin.burnDanmaku) || danmakuList.length === 0) {
838
+ logger.mark(
839
+ '[抖音] 跳过烧录:forceBurnDanmaku=' + String(this.forceBurnDanmaku) +
840
+ ' 配置burnDanmaku=' + String(Config.douyin.burnDanmaku) +
841
+ ' 弹幕数=' + danmakuList.length
842
+ )
843
+ }
844
+ if ((this.forceBurnDanmaku || Config.douyin.burnDanmaku) && danmakuList.length > 0) {
845
+ const videoFile = await downloadFile(g_video_url, {
846
+ title: `Douyin_V_tmp_${Date.now()}.mp4`,
847
+ headers: { ...baseHeaders, Referer: 'https://www.douyin.com' }
848
+ })
849
+ if (videoFile.filepath) {
850
+ const resultPath = Common.tempDri.video + `Douyin_Result_${Date.now()}.mp4`
851
+ logger.mark(`[抖音] 开始烧录 ${danmakuList.length} 条弹幕...`)
852
+ const success = await burnDouyinDanmaku(videoFile.filepath, danmakuList, resultPath, {
853
+ danmakuArea: Config.douyin.danmakuArea,
854
+ verticalMode: Config.douyin.verticalMode,
855
+ videoCodec: Config.douyin.videoCodec,
856
+ danmakuFontSize: Config.douyin.danmakuFontSize,
857
+ danmakuOpacity: Config.douyin.danmakuOpacity
858
+ })
859
+ if (success) {
860
+ const filePath = Common.tempDri.video + `${Config.app.removeCache ? 'tmp_' + Date.now() : g_title}.mp4`
861
+ fs.renameSync(resultPath, filePath)
862
+ await Common.removeFile(videoFile.filepath, true)
863
+ const stats = fs.statSync(filePath)
864
+ const fileSizeInMB = Number((stats.size / (1024 * 1024)).toFixed(2))
865
+ if (fileSizeInMB > Config.app.groupfilevalue) {
866
+ await uploadFile(this.e, { filepath: filePath, totalBytes: fileSizeInMB, originTitle: g_title || '' }, '', {
867
+ useGroupFile: true
868
+ })
869
+ } else {
870
+ await uploadFile(this.e, { filepath: filePath, totalBytes: fileSizeInMB, originTitle: g_title || '' }, '')
871
+ }
872
+ } else {
873
+ await Common.removeFile(videoFile.filepath, true)
874
+ }
875
+ }
876
+ } else {
877
+ // 不烧录弹幕,直接下载发送
878
+ await downloadVideo(
879
+ this.e,
880
+ {
881
+ video_url: g_video_url,
882
+ title: {
883
+ timestampTitle: `tmp_${Date.now()}.mp4`,
884
+ originTitle: `${g_title}.mp4`
885
+ },
886
+ headers: {
887
+ ...baseHeaders,
888
+ Referer: 'https://www.douyin.com'
889
+ }
890
+ },
891
+ {
892
+ message_id: this.e.messageId
893
+ }
894
+ )
895
+ }
896
+ }
897
+ return true
898
+ }
899
+
900
+ case 'user_dynamic': {
901
+ this.workType = 'dynamic'
902
+ const rawData = await this.amagi.douyin.fetcher.fetchUserVideoList({
903
+ sec_uid: data.sec_uid
904
+ })
905
+ const userProfileData = await this.amagi.douyin.fetcher.fetchUserProfile({
906
+ sec_uid: data.sec_uid
907
+ })
908
+
909
+ const user = userProfileData.data.user
910
+
911
+ // 转换视频列表数据
912
+ const videos: DouyinUserVideoListData['videos'] = rawData.data.aweme_list.map((aweme, index) => {
913
+ const isVideo = aweme.aweme_type === 0 || aweme.media_type === 0
914
+
915
+ return {
916
+ aweme_id: aweme.aweme_id,
917
+ is_top: aweme.is_top === 1,
918
+ title: aweme.desc || aweme.item_title || '无标题',
919
+ cover: aweme.video.cover.url_list[0],
920
+ duration: aweme.video?.duration || 0,
921
+ create_time: aweme.create_time,
922
+ statistics: {
923
+ like_count: aweme.statistics.digg_count,
924
+ comment_count: aweme.statistics.comment_count,
925
+ share_count: aweme.statistics.share_count,
926
+ collect_count: aweme.statistics.collect_count
927
+ },
928
+ is_video: isVideo,
929
+ index: index + 1,
930
+ music: aweme.music
931
+ ? {
932
+ title: aweme.music.title || '',
933
+ author: aweme.music.author || ''
934
+ }
935
+ : undefined
936
+ }
937
+ })
938
+
939
+ const displayVideos = videos.slice(0, 16)
940
+ const timeoutSeconds = 120
941
+
942
+ // 渲染视频列表页面
943
+ const img = await Render(this.e, 'douyin/user_profile', {
944
+ user: {
945
+ head_image:
946
+ user.cover_and_head_image_info.profile_cover_list.length > 0
947
+ ? user.cover_and_head_image_info.profile_cover_list[0].cover_url?.url_list[0] || null
948
+ : null,
949
+ nickname: user.nickname ?? '',
950
+ short_id: user.unique_id === '' ? user.short_id : user.unique_id,
951
+ avatar: user.avatar_larger?.url_list?.[0] || user.avatar_thumb?.url_list?.[0] || '',
952
+ signature: user.signature,
953
+ follower_count: user.follower_count,
954
+ following_count: user.following_count,
955
+ total_favorited: user.total_favorited,
956
+ verified: !!user.custom_verify || !!user.enterprise_verify_reason,
957
+ ip_location: user.ip_location ?? ''
958
+ },
959
+ videos: displayVideos,
960
+ timeoutSeconds
961
+ })
962
+
963
+ await this.e.reply(img)
964
+
965
+ logger.debug(`等待用户选择视频,开始计时,${timeoutSeconds}秒后终止等待...`)
966
+ const context = await karin.ctx(this.e, {
967
+ throwOnTimeout: false,
968
+ time: timeoutSeconds
969
+ })
970
+ if (!context) {
971
+ await this.e.reply(`${timeoutSeconds} 秒内没收到作品序号,已取消后续操作`)
972
+ return true
973
+ }
974
+ if (context) {
975
+ const num = parseInt(context.msg.trim())
976
+ if (!isNaN(num) && num >= 1 && num <= displayVideos.length) {
977
+ const emojiManager = new EmojiReactionManager(context)
978
+ let processingTimer: NodeJS.Timeout | null = null
979
+ let successTimer: NodeJS.Timeout | null = null
980
+
981
+ await emojiManager.add('EYES')
982
+ processingTimer = setTimeout(() => {
983
+ emojiManager.add('PROCESSING').catch(() => {})
984
+ }, 1500)
985
+
986
+ try {
987
+ const target = displayVideos[num - 1]
988
+ const targetData: DouyinIdData = {
989
+ type: 'one_work',
990
+ aweme_id: target.aweme_id
991
+ }
992
+ const dy = new DouYin(context, targetData)
993
+ await dy.DouyinHandler(targetData)
994
+
995
+ successTimer = setTimeout(() => {
996
+ emojiManager.replace('PROCESSING', 'SUCCESS').catch(() => {})
997
+ }, 1500)
998
+ } catch (error) {
999
+ if (processingTimer) clearTimeout(processingTimer)
1000
+ if (successTimer) clearTimeout(successTimer)
1001
+
1002
+ const processingEmojiId = getEmojiId(context, 'PROCESSING')
1003
+ if (emojiManager.has(processingEmojiId)) {
1004
+ await emojiManager.remove('PROCESSING')
1005
+ }
1006
+ await emojiManager.add('ERROR')
1007
+ throw error
1008
+ }
1009
+ }
1010
+ }
1011
+ return true
1012
+ }
1013
+ case 'music_work': {
1014
+ this.workType = 'music'
1015
+ const MusicData = await this.amagi.douyin.fetcher.fetchMusicInfo({
1016
+ music_id: data.music_id
1017
+ })
1018
+ // 生成类型里 `music_info` 可空(样本里有一条就是 null),先兜住再往下读
1019
+ const musicInfo = MusicData.data.music_info
1020
+ if (!musicInfo) {
1021
+ await this.e.reply('解析错误!未获取到音乐信息,无法下载', { reply: true })
1022
+ return true
1023
+ }
1024
+ const sec_uid = musicInfo.sec_uid
1025
+ const UserData = await this.amagi.douyin.fetcher.fetchUserProfile({ sec_uid })
1026
+ // if (UserData.data.status_code === 2) {
1027
+ // const new_UserData.data = await getDouyinData('搜索数据', Config.cookies.douyin, { query: data.music_info.author })
1028
+ // if (new_UserData.data.data[0].type === 4 && new_UserData.data.data[0].card_unique_name === 'user') {
1029
+ // UserData.data = { user: new_UserData.data.data[0].user_list[0].user_info }
1030
+ // }
1031
+ // const search_data = new_UserData.data
1032
+ // }
1033
+ if (!musicInfo.play_url) {
1034
+ await this.e.reply('解析错误!该音乐抖音未提供下载链接,无法下载', { reply: true })
1035
+ return true
1036
+ }
1037
+ img = await Render(this.e, 'douyin/musicinfo', {
1038
+ image_url: musicInfo.cover_hd.url_list[0],
1039
+ desc: musicInfo.title,
1040
+ music_id: musicInfo.id.toString(),
1041
+ create_time: Time(0),
1042
+ user_count: Count(musicInfo.user_count),
1043
+ avater_url: musicInfo.avatar_large?.url_list[0] || UserData.data.user.avatar_larger.url_list[0],
1044
+ fans: UserData.data.user.mplatform_followers_count || UserData.data.user.follower_count,
1045
+ following_count: UserData.data.user.following_count,
1046
+ total_favorited: UserData.data.user.total_favorited,
1047
+ user_shortid: UserData.data.user.unique_id === '' ? UserData.data.user.short_id : UserData.data.user.unique_id,
1048
+ share_url: musicInfo.play_url.uri,
1049
+ username:
1050
+ musicInfo?.original_musician_display_name || musicInfo.owner_nickname === '' ? musicInfo.author : musicInfo.owner_nickname
1051
+ })
1052
+ await this.e.reply([
1053
+ ...img,
1054
+ `\n正在上传 ${musicInfo.title}\n`,
1055
+ `作曲: ${musicInfo.original_musician_display_name || musicInfo.owner_nickname === '' ? musicInfo.author : musicInfo.owner_nickname}\n`,
1056
+ `music_id: ${musicInfo.id}`
1057
+ ])
1058
+ const musicFile = await downloadFile(musicInfo.play_url.uri, {
1059
+ title: `Douyin_Music_${Date.now()}.mp3`,
1060
+ headers: this.headers
1061
+ })
1062
+ if (musicFile.filepath) {
1063
+ const musicBase64 = `base64://${fs.readFileSync(musicFile.filepath).toString('base64')}`
1064
+ await this.e.reply(segment.record(musicBase64, false))
1065
+ await Common.removeFile(musicFile.filepath, true)
1066
+ }
1067
+ return true
1068
+ }
1069
+ case 'live_room_detail': {
1070
+ this.workType = 'live'
1071
+ const UserInfoData = await this.amagi.douyin.fetcher.fetchUserProfile({
1072
+ sec_uid: data.sec_uid
1073
+ })
1074
+ if (UserInfoData.data.user.live_status === 1) {
1075
+ // 直播中
1076
+ if (!UserInfoData.data.user?.live_status || UserInfoData.data.user.live_status !== 1) {
1077
+ logger.error((UserInfoData?.data?.user?.nickname ?? '用户') + '当前未在直播')
1078
+ }
1079
+ // 生成类型里 `room_data` 是可选的:拿不到直播间数据就没法继续,原来只打日志会一路
1080
+ // 走到 JSON.parse(undefined) 抛 TypeError
1081
+ const roomDataRaw = UserInfoData.data.user.room_data
1082
+ if (!roomDataRaw) {
1083
+ logger.error('未获取到直播间信息!')
1084
+ return true
1085
+ }
1086
+
1087
+ const room_data = JSON.parse(roomDataRaw)
1088
+ const live_data = await this.amagi.douyin.fetcher.fetchLiveRoomInfo({
1089
+ room_id: UserInfoData.data.user.room_id_str,
1090
+ web_rid: room_data.owner.web_rid
1091
+ })
1092
+ const liveItem = live_data.data.data.data[0]
1093
+ const user = UserInfoData.data.user
1094
+ const streamExtra = liveItem.stream_url?.extra
1095
+ const resolution = streamExtra ? `${streamExtra.width}x${streamExtra.height}` : liveItem.stream_url?.default_resolution || ''
1096
+
1097
+ const img = await Render(this.e, 'douyin/live', {
1098
+ image_url: liveItem.cover?.url_list[0],
1099
+ text: liveItem.title,
1100
+ partition_title: live_data.data.data.partition_road_map?.partition?.title || '未知分区',
1101
+ room_id: room_data.owner.web_rid,
1102
+ online_viewers: Count(Number(liveItem.room_view_stats?.display_value)),
1103
+ total_viewers: liveItem.stats?.total_user_str || '刚开播无法获取',
1104
+ username: user.nickname,
1105
+ avater_url: user.avatar_larger.url_list[0],
1106
+ fans: Count(user.follower_count),
1107
+ share_url: 'https://live.douyin.com/' + room_data.owner.web_rid,
1108
+ dynamicTYPE: '直播间信息',
1109
+ like_count: Count(Number(liveItem.like_count || 0)),
1110
+ user_count_str: liveItem.user_count_str || '',
1111
+ resolution,
1112
+ signature: user.signature || '',
1113
+ city: user.city || '',
1114
+ aweme_count: Count(Number(user.aweme_count || 0)),
1115
+ following_count: Count(Number(user.following_count || 0)),
1116
+ total_favorited: Count(Number(user.total_favorited || 0)),
1117
+ has_commerce_goods: liveItem.has_commerce_goods || false
1118
+ })
1119
+ await this.e.reply(img)
1120
+ } else {
1121
+ this.e.reply(`「${UserInfoData.data.user.nickname}」\n未开播,正在休息中~`)
1122
+ }
1123
+ return true
1124
+ }
1125
+ default:
1126
+ break
1127
+ }
1128
+ }
1129
+ }
1130
+
1131
+ /**
1132
+ * 传递整数,返回x小时后的时间
1133
+ * @param {number} delay
1134
+ * @returns
1135
+ */
1136
+ export const Time = (delay: number): string => {
1137
+ const currentDate = new Date()
1138
+ currentDate.setHours(currentDate.getHours() + delay)
1139
+
1140
+ const year = currentDate.getFullYear().toString()
1141
+ const month = (currentDate.getMonth() + 1).toString()
1142
+ const day = String(currentDate.getDate()).padStart(2, '0')
1143
+ const hours = String(currentDate.getHours()).padStart(2, '0')
1144
+ const minutes = String(currentDate.getMinutes()).padStart(2, '0')
1145
+ const seconds = String(currentDate.getSeconds()).padStart(2, '0')
1146
+
1147
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
1148
+ }
1149
+
1150
+ /** 表情列表 → 富文本渲染器的表情定义(`name` 是 `[表情名]`,`url` 已在模板侧过协议白名单) */
1151
+ export const Emoji = (data: DouyinEmojiListResponse): RichTextEmojiDefinition[] => {
1152
+ const list: RichTextEmojiDefinition[] = []
1153
+
1154
+ for (const i of data.emoji_list) {
1155
+ list.push({
1156
+ name: i.display_name,
1157
+ url: i.emoji_url.url_list[0] ?? ''
1158
+ })
1159
+ }
1160
+
1161
+ return list
1162
+ }
1163
+
1164
+ /**
1165
+ * 格式化视频统计信息为三行,每行两个数据项,并保持对齐
1166
+ */
1167
+ const formatVideoStats = (
1168
+ digg_count: number,
1169
+ share_count: number,
1170
+ collect_count: number,
1171
+ comment_count: number,
1172
+ recommend_count: number
1173
+ ): string => {
1174
+ // 计算每个数据项的文本
1175
+ const diggText = `❤ 点赞: ${Count(digg_count)}`
1176
+ const shareText = `🔄 转发: ${Count(share_count)}`
1177
+ const collectText = `⭐ 收藏: ${Count(collect_count)}`
1178
+ const commentText = `💬 评论: ${Count(comment_count)}`
1179
+ const recommendText = `👍 推荐: ${Count(recommend_count)}`
1180
+
1181
+ // 找出第一列中最长的项的长度
1182
+ const firstColItems = [diggText, shareText]
1183
+ const maxFirstColLength = Math.max(...firstColItems.map((item) => getStringDisplayWidth(item)))
1184
+
1185
+ // 构建三行文本,确保第二列对齐
1186
+ const line1 = alignTwoColumns(diggText, shareText, maxFirstColLength)
1187
+ const line2 = alignTwoColumns(collectText, commentText, maxFirstColLength)
1188
+ const line3 = alignTwoColumns(recommendText, '', maxFirstColLength)
1189
+
1190
+ return `${line1}\n${line2}\n${line3}`
1191
+ }
1192
+
1193
+ /**
1194
+ * 对齐两列文本
1195
+ */
1196
+ const alignTwoColumns = (col1: string, col2: string, targetLength: number): string => {
1197
+ // 计算需要添加的空格数量
1198
+ const col1Width = getStringDisplayWidth(col1)
1199
+ const spacesNeeded = targetLength - col1Width + 5 // 5是两列之间的固定间距
1200
+
1201
+ // 添加空格使两列对齐
1202
+ return col1 + ' '.repeat(spacesNeeded) + col2
1203
+ }
1204
+
1205
+ /**
1206
+ * 获取字符串在显示时的实际宽度
1207
+ * 考虑到不同字符的显示宽度不同(如中文、emoji等)
1208
+ */
1209
+ const getStringDisplayWidth = (str: string): number => {
1210
+ let width = 0
1211
+ for (let i = 0; i < str.length; i++) {
1212
+ const code = str.codePointAt(i)
1213
+ if (!code) continue
1214
+
1215
+ // 处理emoji和特殊Unicode字符
1216
+ if (code > 0xffff) {
1217
+ width += 2 // emoji通常占用2个字符宽度
1218
+ i++ // 跳过代理对的后半部分
1219
+ } else if (
1220
+ // 处理中文字符和其他全角字符
1221
+ (code >= 0x3000 && code <= 0x9fff) || // 中文字符范围
1222
+ (code >= 0xff00 && code <= 0xffef) || // 全角ASCII、全角标点
1223
+ code === 0x2026 || // 省略号
1224
+ code === 0x2014 || // 破折号
1225
+ (code >= 0x2e80 && code <= 0x2eff) || // CJK部首补充
1226
+ (code >= 0x3000 && code <= 0x303f) || // CJK符号和标点
1227
+ (code >= 0x31c0 && code <= 0x31ef) || // CJK笔画
1228
+ (code >= 0x3200 && code <= 0x32ff) || // 封闭式CJK字母和月份
1229
+ (code >= 0x3300 && code <= 0x33ff) || // CJK兼容
1230
+ (code >= 0xac00 && code <= 0xd7af) || // 朝鲜文音节
1231
+ (code >= 0xf900 && code <= 0xfaff) || // CJK兼容表意文字
1232
+ (code >= 0xfe30 && code <= 0xfe4f) // CJK兼容形式
1233
+ ) {
1234
+ width += 2
1235
+ } else if (code === 0x200d || (code >= 0xfe00 && code <= 0xfe0f) || (code >= 0x1f3fb && code <= 0x1f3ff)) {
1236
+ // emoji修饰符和连接符
1237
+ width += 0 // 这些字符不增加宽度,它们是修饰符
1238
+ } else {
1239
+ // 普通ASCII字符
1240
+ width += 1
1241
+ }
1242
+ }
1243
+ return width
1244
+ }