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,1241 @@
1
+ import fs from 'node:fs'
2
+
3
+ import type { DouyinSearchResponse } from '@ikenxuan/amagi'
4
+ import type { DouyinUserListData } from '@template/template/douyin/userlist/components/types'
5
+ import { format } from 'date-fns'
6
+ import type { AdapterType, Elements, ImageElement, Message } from 'node-karin'
7
+ import karin, { common, logger, segment } from 'node-karin'
8
+
9
+ import {
10
+ Base,
11
+ baseHeaders,
12
+ buildGoogleMotionPhoto,
13
+ cleanOldDynamicCache,
14
+ Common,
15
+ douyinDB,
16
+ downloadFile,
17
+ downLoadFileOptions,
18
+ downloadVideo,
19
+ fileInfo,
20
+ type LiveImageMergeOptions,
21
+ loopVideoWithTransition,
22
+ Networks,
23
+ processLocalImageFile,
24
+ processImageUrl,
25
+ Render
26
+ } from '@/module'
27
+ import { Config } from '@/module/utils/Config'
28
+ import { DouyinIdData, buildDouyinPlayUrl, douyinProcessVideos, type dyVideo, getDouyinID } from '@/platform/douyin'
29
+ import type { DouyinListItem } from '@/platform/douyin/types'
30
+ import { getDouyinLiveImageSendPolicy, getWorkTypeDisplayName, getWorkTypeInfo } from '@/platform/douyin/workType'
31
+ import type { douyinPushItem } from '@/types/config/pushlist'
32
+
33
+ import { processFavoriteList } from './push/favorite'
34
+ import { processLiveStream } from './push/live'
35
+ import { processPostList } from './push/post'
36
+ import { processRecommendList } from './push/recommend'
37
+ import { renderFavoriteImage, renderLiveImage, renderRecommendImage, renderWorkImage } from './push/render'
38
+ import { type DouyinPushItem, type DouyinWorkPushItem, type WillBePushList } from './push/types'
39
+
40
+ // Re-export types for backward compatibility
41
+ export type { DouyinPushItem, DouyinWorkPushItem }
42
+
43
+ const douyinBaseHeaders: downLoadFileOptions['headers'] = {
44
+ ...baseHeaders,
45
+ Referer: 'https://www.douyin.com',
46
+ Cookie: Config.amagi.cookies.douyin
47
+ }
48
+
49
+ export class DouYinpush extends Base {
50
+ private force = false
51
+ /**
52
+ *
53
+ * @param e 事件Message
54
+ * @param force 是否强制推送
55
+ * @default false
56
+ * @returns
57
+ */
58
+ constructor(e = {} as Message, force: boolean = false) {
59
+ super(e)
60
+ this.headers!.Referer = 'https://www.douyin.com'
61
+ this.headers!.Cookie = Config.amagi.cookies.douyin
62
+ this.force = force
63
+ }
64
+
65
+ private injectBotToEventForRender(targets: Array<{ groupId: string; botId: string }>): void {
66
+ const targetBotId = targets.find((item) => item.botId)?.botId
67
+ if (!targetBotId) return
68
+
69
+ const bot = karin.getBot(targetBotId) as AdapterType | undefined
70
+ if (!bot) return
71
+
72
+ const eventWithBot = this.e as Message & { bot?: AdapterType; selfId?: string }
73
+ eventWithBot.bot = bot
74
+ eventWithBot.selfId = eventWithBot.selfId ?? targetBotId
75
+ }
76
+
77
+ /**
78
+ * 执行主要的操作流程
79
+ */
80
+ async action() {
81
+ await this.syncConfigToDatabase()
82
+
83
+ // 清理旧的作品缓存记录
84
+ const deletedCount = await cleanOldDynamicCache('douyin')
85
+ if (deletedCount > 0) {
86
+ logger.info(`已清理 ${deletedCount} 条过期的抖音作品缓存记录`)
87
+ }
88
+
89
+ // 检查备注信息
90
+ if (await this.checkremark()) return true
91
+
92
+ // 检查并补全配置文件中缺失的字段
93
+ await this.ensureConfigFields(Config.pushlist.douyin)
94
+
95
+ // 获取已注册的 bot 列表,过滤未注册的 bot
96
+ const registeredBotIds = karin.getAllBotID()
97
+ const filteredPushList = this.filterPushListByRegisteredBots(Config.pushlist.douyin, registeredBotIds)
98
+
99
+ if (filteredPushList.length === 0) {
100
+ // 推送列表本来就是空的属正常情况,别刷 warn(用户反馈日志一直被它刷屏)
101
+ if ((Config.pushlist as any).douyin?.length) {
102
+ logger.warn('推送列表里的机器人都不在线,本次跳过抖音推送')
103
+ } else {
104
+ logger.debug('抖音推送列表为空,跳过')
105
+ }
106
+ return true
107
+ }
108
+
109
+ const data = await this.getDynamicList(filteredPushList)
110
+
111
+ if (Object.keys(data).length === 0) return true
112
+
113
+ if (this.force) return await this.forcepush(data)
114
+ else return await this.getdata(data)
115
+ }
116
+
117
+ /**
118
+ * 检查并补全配置文件中缺失的字段
119
+ * @param pushList 推送配置列表
120
+ */
121
+ private async ensureConfigFields(pushList: douyinPushItem[]): Promise<void> {
122
+ if (!pushList || pushList.length === 0) return
123
+
124
+ let hasChanges = false
125
+
126
+ for (const item of pushList) {
127
+ // 检查并补全 sec_uid 字段
128
+ if (!item.sec_uid && item.short_id) {
129
+ try {
130
+ logger.info(`自动获取用户 ${item.remark || item.short_id} 的 sec_uid`)
131
+ const searchResult = await this.amagi.douyin.fetcher.searchContent({
132
+ query: item.short_id,
133
+ type: 'user'
134
+ })
135
+
136
+ // 在搜索结果中查找匹配的用户
137
+ let matchedUser = null
138
+ for (const userItem of searchResult.data.user_list) {
139
+ const currentDouyinId = userItem.user_info.unique_id || userItem.user_info.short_id
140
+ if (currentDouyinId === item.short_id) {
141
+ matchedUser = userItem.user_info
142
+ break
143
+ }
144
+ }
145
+
146
+ if (matchedUser?.sec_uid) {
147
+ item.sec_uid = matchedUser.sec_uid
148
+ hasChanges = true
149
+ logger.info(`已为 ${item.remark || item.short_id} 补全 sec_uid: ${item.sec_uid}`)
150
+ } else {
151
+ logger.warn(`无法获取用户 ${item.short_id} 的 sec_uid`)
152
+ }
153
+ } catch (error) {
154
+ logger.error(`获取 ${item.short_id} 的 sec_uid 失败: ${error}`)
155
+ }
156
+ }
157
+
158
+ // 检查并补全 pushTypes 字段
159
+ if (!item.pushTypes || item.pushTypes.length === 0) {
160
+ item.pushTypes = ['post', 'live', 'favorite', 'recommend']
161
+ hasChanges = true
162
+ logger.info(`为用户 ${item.remark ?? item.sec_uid} 自动补全推送类型:作品列表、直播、收藏、推荐`)
163
+ }
164
+
165
+ // 检查并补全 switch 字段
166
+ if (item.switch === undefined) {
167
+ item.switch = true
168
+ hasChanges = true
169
+ }
170
+ }
171
+
172
+ // 如果有修改,保存到配置文件
173
+ if (hasChanges) {
174
+ Config.Modify('pushlist', 'douyin', pushList)
175
+ logger.info('已自动补全配置文件中缺失的字段并保存')
176
+ }
177
+ }
178
+
179
+ /**
180
+ * 根据已注册的 bot 列表过滤推送配置
181
+ * @param pushList 原始推送配置列表
182
+ * @param registeredBotIds 已注册的 bot ID 列表
183
+ * @returns 过滤后的推送配置列表
184
+ */
185
+ private filterPushListByRegisteredBots(pushList: douyinPushItem[], registeredBotIds: string[]): douyinPushItem[] {
186
+ if (!pushList || pushList.length === 0) return []
187
+
188
+ const registeredSet = new Set(registeredBotIds)
189
+ const filteredList: douyinPushItem[] = []
190
+
191
+ for (const item of pushList) {
192
+ // 过滤 group_id 中未注册的 bot
193
+ const filteredGroupIds = item.group_id.filter((groupWithBot) => {
194
+ const botId = groupWithBot.split(':')[1]
195
+ const isRegistered = registeredSet.has(botId)
196
+ if (!isRegistered) {
197
+ logger.debug(`Bot ${botId} 未注册,跳过群组 ${groupWithBot.split(':')[0]} 的推送`)
198
+ }
199
+ return isRegistered
200
+ })
201
+
202
+ // 如果过滤后还有有效的群组,则保留该订阅项
203
+ if (filteredGroupIds.length > 0) {
204
+ filteredList.push({
205
+ ...item,
206
+ group_id: filteredGroupIds
207
+ })
208
+ } else {
209
+ logger.debug(`用户 ${item.remark ?? item.sec_uid} 的所有推送目标 bot 均未注册,跳过`)
210
+ }
211
+ }
212
+
213
+ return filteredList
214
+ }
215
+
216
+ /**
217
+ * 同步配置文件中的订阅信息到数据库
218
+ */
219
+ async syncConfigToDatabase() {
220
+ // 如果配置文件中没有抖音推送列表,直接返回
221
+ if (!Config.pushlist.douyin || Config.pushlist.douyin.length === 0) {
222
+ return
223
+ }
224
+
225
+ await douyinDB.syncConfigSubscriptions(Config.pushlist.douyin)
226
+ }
227
+
228
+ async getdata(data: WillBePushList) {
229
+ if (Object.keys(data).length === 0) return true
230
+
231
+ for (const awemeId in data) {
232
+ const pushItem = data[awemeId]
233
+ const actualAwemeId = awemeId.replace(/^(post|favorite|recommend|live)_/, '') // 移除推送类型前缀
234
+ const shareUrl =
235
+ pushItem.pushType === 'live'
236
+ ? `https://live.douyin.com/${pushItem.Detail_Data.room_data?.owner.web_rid}`
237
+ : (pushItem.Detail_Data.share_url ?? `https://www.douyin.com/video/${actualAwemeId}`)
238
+ let pushTypeLabel: string
239
+ switch (pushItem.pushType) {
240
+ case 'post':
241
+ pushTypeLabel = '作品列表'
242
+ break
243
+ case 'favorite':
244
+ pushTypeLabel = '喜欢列表'
245
+ break
246
+ case 'recommend':
247
+ pushTypeLabel = '推荐列表'
248
+ break
249
+ default:
250
+ pushTypeLabel = '直播'
251
+ break
252
+ }
253
+
254
+ logger.mark(`
255
+ ${logger.blue('开始处理并渲染抖音动态图片')}
256
+ ${logger.blue('博主')}: ${logger.green(pushItem.remark)}
257
+ ${logger.blue('推送类型')}: ${logger.magenta(pushTypeLabel)}
258
+ ${logger.cyan('作品id')}:${logger.yellow(actualAwemeId)}
259
+ ${logger.cyan('访问地址')}:${logger.green('https://www.douyin.com/video/' + actualAwemeId)}
260
+ ${logger.cyan('分享链接')}: ${logger.green(shareUrl)}
261
+ `)
262
+
263
+ const Detail_Data = pushItem.Detail_Data
264
+ const workTypeInfo = getWorkTypeInfo(Detail_Data)
265
+ const skip = await skipDynamic(pushItem)
266
+ if (skip) {
267
+ logger.warn(`作品 https://www.douyin.com/video/${actualAwemeId} 已被处理,跳过`)
268
+ }
269
+ let img: ImageElement[] = []
270
+ let iddata: DouyinIdData = { type: 'one_work' }
271
+ /** 按画质配置选中、即将下载发送的那一路视频源 */
272
+ let selectedVideo: dyVideo | null = null
273
+ this.injectBotToEventForRender(pushItem.targets)
274
+
275
+ if (!skip) {
276
+ iddata = await getDouyinID(this.e, shareUrl, false)
277
+ }
278
+
279
+ if (!skip) {
280
+ // 画质选档必须早于渲染和分享链接拼接:卡片上展示的清晰度要和后面实际下载的那一路视频源完全一致,
281
+ // 否则会出现「卡片写 4K、实际下载 720p」的错位。
282
+ if (pushItem.pushType !== 'live' && workTypeInfo.isVideo && pushItem.Detail_Data.video?.bit_rate?.length) {
283
+ selectedVideo = douyinProcessVideos(pushItem.Detail_Data.video.bit_rate, Config.douyin.videoQuality)[0]
284
+ }
285
+
286
+ let workShareLink: string | undefined
287
+ if (pushItem.pushType !== 'live') {
288
+ if (workTypeInfo.isArticle) {
289
+ workShareLink = `https://www.douyin.com/article/${actualAwemeId}`
290
+ } else if (workTypeInfo.isImage) {
291
+ // 图文和合辑使用无追踪参数的短链接,降低二维码密度并提高扫描识别率。
292
+ workShareLink = `https://www.douyin.com/note/${actualAwemeId}`
293
+ } else if (Config.douyin.push.shareType === 'web') {
294
+ // 视频同样用无追踪参数的规范链接:share_url 302 之后只是多出一串追踪参数,白搭一次网络请求还会拉高二维码密度
295
+ workShareLink = `https://www.douyin.com/video/${actualAwemeId}`
296
+ } else {
297
+ // 直链模式:优先用选档后的视频源拼播放地址,二维码内容与卡片展示的清晰度一致
298
+ const playAddr = selectedVideo?.play_addr ?? pushItem.Detail_Data.video?.play_addr
299
+ workShareLink = playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${actualAwemeId}`
300
+ }
301
+ }
302
+
303
+ switch (pushItem.pushType) {
304
+ case 'live': {
305
+ if (!pushItem.Detail_Data.room_data || !pushItem.Detail_Data.live_data) break
306
+ img = await renderLiveImage({
307
+ e: this.e,
308
+ Detail_Data: pushItem.Detail_Data,
309
+ dynamicTypeLabel: '直播动态推送'
310
+ })
311
+ break
312
+ }
313
+
314
+ case 'favorite': {
315
+ img = await renderFavoriteImage({
316
+ e: this.e,
317
+ Detail_Data: pushItem.Detail_Data,
318
+ create_time: pushItem.create_time,
319
+ shareLink: workShareLink!,
320
+ remark: pushItem.remark
321
+ })
322
+ break
323
+ }
324
+
325
+ case 'recommend': {
326
+ img = await renderRecommendImage({
327
+ e: this.e,
328
+ Detail_Data: pushItem.Detail_Data,
329
+ create_time: pushItem.create_time,
330
+ shareLink: workShareLink!,
331
+ remark: pushItem.remark
332
+ })
333
+ break
334
+ }
335
+
336
+ case 'post':
337
+ default: {
338
+ img = await renderWorkImage({
339
+ e: this.e,
340
+ Detail_Data: pushItem.Detail_Data,
341
+ create_time: pushItem.create_time,
342
+ shareLink: workShareLink!,
343
+ videoSource: selectedVideo
344
+ })
345
+ break
346
+ }
347
+ }
348
+ }
349
+
350
+ // 遍历目标群组,并发送消息
351
+ for (const target of pushItem.targets) {
352
+ let status = { message_id: '' }
353
+ const { groupId, botId } = target
354
+
355
+ if (!skip) {
356
+ const Contact = karin.contactGroup(groupId)
357
+
358
+ // 为当前目标注入 bot,后续解析下载沿用该 bot 身份
359
+ const bot = karin.getBot(botId) as AdapterType
360
+ const eventWithBot = this.e as Message & { bot?: AdapterType; selfId?: string }
361
+ eventWithBot.bot = bot
362
+ eventWithBot.selfId = botId
363
+ const pushImg = img ?? []
364
+
365
+ // 仅 QQ 官方机器人支持按钮:非直播作品在卡片末尾追加「解析」回调按钮,点击后下发 #解析 + 分享链接
366
+ const parseButton =
367
+ bot?.adapter?.name === 'QQ Official Bot' && pushItem.pushType !== 'live' && pushItem.Detail_Data.share_url
368
+ ? [
369
+ segment.button([
370
+ { text: '解析', callback: true, data: `#解析${pushItem.Detail_Data.share_url}` },
371
+ { text: '帮助', callback: true, data: `#kkk帮助` }
372
+ ])
373
+ ]
374
+ : []
375
+
376
+ // 发送消息
377
+ status = await karin.sendMsg(botId, Contact, [...pushImg, ...parseButton])
378
+
379
+ // 如果是直播推送,更新直播状态
380
+ if (pushItem.pushType === 'live' && 'room_data' in pushItem.Detail_Data && status.message_id) {
381
+ await douyinDB.updateLiveStatus(pushItem.sec_uid, true)
382
+ }
383
+
384
+ // 是否一同解析该新作品?(直播推送没有可解析的作品内容)
385
+ if (pushItem.pushType !== 'live' && Config.douyin.push.parsedynamic && status.message_id) {
386
+ // 收窄为作品类推送的作品详情
387
+ const Detail_Data = pushItem.Detail_Data
388
+ logger.debug(`开始解析作品,类型为:${getWorkTypeDisplayName(workTypeInfo)}`)
389
+ // 如果新作品是视频
390
+ if (workTypeInfo.isVideo && Detail_Data.video) {
391
+ /** 默认视频下载地址 */
392
+ let downloadUrl = buildDouyinPlayUrl(Detail_Data.video.play_addr)
393
+ // 根据配置文件自动选择分辨率
394
+ logger.debug(`开始排除不符合条件的视频分辨率;\n
395
+ 共拥有${logger.yellow(Detail_Data.video.bit_rate.length)}个视频源\n
396
+ 视频ID:${logger.green(Detail_Data.aweme_id)}\n
397
+ 分享链接:${logger.green(Detail_Data.share_url)}
398
+ `)
399
+ // 复用渲染前已选好的视频源,卡片展示的清晰度与实际下载的必然一致
400
+ const videoObj = selectedVideo ?? douyinProcessVideos(Detail_Data.video.bit_rate, Config.douyin.videoQuality)[0]
401
+ logger.debug('获取精确下载地址')
402
+ downloadUrl = await new Networks({
403
+ url: videoObj.play_addr.url_list[0],
404
+ headers: douyinBaseHeaders
405
+ }).getLongLink()
406
+ // 下载视频
407
+ await downloadVideo(
408
+ this.e,
409
+ {
410
+ video_url: downloadUrl,
411
+ title: { timestampTitle: `tmp_${Date.now()}.mp4`, originTitle: `${Detail_Data.desc}.mp4` }
412
+ },
413
+ { active: true, activeOption: { uin: botId, group_id: groupId } }
414
+ )
415
+ } else if (workTypeInfo.isImage && iddata.type === 'one_work') {
416
+ // 如果新作品是图集或合辑
417
+ // 判断是否为合辑(is_slides)
418
+ const isSlides = Detail_Data.is_slides === true
419
+
420
+ if (isSlides && Detail_Data.images) {
421
+ // 合辑处理逻辑
422
+ const images: Elements[] = []
423
+ const temp: fileInfo[] = []
424
+ let hasGeneratedLivePhoto = false // 标记是否生成了实况图
425
+
426
+ /** 下载 BGM(如果存在) */
427
+ let liveimgbgm: fileInfo | null = null
428
+ let bgmContext: LiveImageMergeOptions['context'] | null = null
429
+ const mergeMode = Config.douyin.liveImageMergeMode ?? 'independent'
430
+
431
+ if (Detail_Data.music) {
432
+ let mp3Path = ''
433
+ // 该声音由于版权原因在当前地区不可用
434
+ if (Detail_Data.music.play_url.uri === '') {
435
+ const extraData = JSON.parse(Detail_Data.music.extra)
436
+ mp3Path = extraData.original_song_url
437
+ } else {
438
+ mp3Path = Detail_Data.music.play_url.uri
439
+ }
440
+
441
+ liveimgbgm = await downloadFile(mp3Path, {
442
+ title: `Douyin_tmp_A_${Date.now()}.mp3`,
443
+ headers: douyinBaseHeaders
444
+ })
445
+ temp.push(liveimgbgm)
446
+ }
447
+
448
+ const images1 = Detail_Data.images ?? []
449
+ if (!images1.length) {
450
+ logger.debug('未获取到合辑的图片数据')
451
+ }
452
+
453
+ for (const [index, item] of images1.entries()) {
454
+ // 静态图片,clip_type为2或undefined
455
+ if (item.clip_type === 2 || item.clip_type === undefined) {
456
+ const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index)
457
+ images.push(segment.image(imageUrl))
458
+ continue
459
+ }
460
+
461
+ /** 动图/短片 */
462
+ const livePlayAddr = item.video?.play_addr_h264
463
+ if (!livePlayAddr?.uri) {
464
+ logger.warn(`合辑第 ${index + 1} 个动态媒体缺少视频源,跳过`)
465
+ continue
466
+ }
467
+ const liveimg = await downloadFile(buildDouyinPlayUrl(livePlayAddr), {
468
+ title: `Douyin_tmp_V_${Date.now()}.mp4`,
469
+ headers: douyinBaseHeaders
470
+ })
471
+
472
+ if (liveimg.filepath) {
473
+ const outputPath = Common.tempDri.video + `Douyin_Result_${Date.now()}.mp4`
474
+ const loopCount = item.clip_type === 4 ? 1 : 3
475
+ let staticImgPath = ''
476
+ if (item.url_list?.[0]) {
477
+ const staticImg = await downloadFile(item.url_list[0], {
478
+ title: `Douyin_static_${Date.now()}_${index}.jpg`,
479
+ headers: douyinBaseHeaders,
480
+ filepath: Common.tempDri.images + `Douyin_static_${Date.now()}_${index}.jpg`
481
+ })
482
+ if (staticImg.filepath) {
483
+ temp.push({ filepath: staticImg.filepath, totalBytes: 0 })
484
+ }
485
+ staticImgPath = staticImg.filepath ?? ''
486
+ }
487
+
488
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(
489
+ item.clip_type,
490
+ Config.app.livePhotoMode ?? 'video_and_livephoto'
491
+ )
492
+
493
+ // 生成视频
494
+ if (shouldGenerateVideo) {
495
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath)
496
+ const safeStaticPath = staticImgPath || liveimg.filepath
497
+ const result = await loopVideoWithTransition({
498
+ inputPath: liveimg.filepath,
499
+ outputPath,
500
+ loopCount,
501
+ staticImagePath: safeStaticPath,
502
+ transitionEnabled,
503
+ bgmPath: liveimgbgm?.filepath,
504
+ mergeMode,
505
+ context: bgmContext ?? undefined
506
+ })
507
+ const success = result.success
508
+ if (mergeMode === 'continuous' && result.context) {
509
+ bgmContext = result.context
510
+ }
511
+
512
+ if (success) {
513
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`
514
+ fs.renameSync(outputPath, filePath)
515
+ logger.mark(`视频文件重命名完成: ${outputPath.split('/').pop()} -> ${filePath.split('/').pop()}`)
516
+ temp.push({ filepath: filePath, totalBytes: 0 })
517
+ const videoPath =
518
+ Config.app.videoSendMode === 'base64'
519
+ ? `base64://${fs.readFileSync(filePath).toString('base64')}`
520
+ : `file://${filePath}`
521
+ images.push(segment.video(videoPath))
522
+ }
523
+ }
524
+
525
+ // 生成实况图(clip_type === 5 是 livePhoto)
526
+ if (shouldGenerateLivePhoto && item.clip_type === 5 && item.url_list?.[0]) {
527
+ let hasPushedMotionPhotoCover = false
528
+ if (staticImgPath) {
529
+ const motionPhotoCoverPath =
530
+ Common.tempDri.images + `MVIMG_${format(new Date(), 'yyyyMMdd_HHmmss_SSS')}_${index}.jpg`
531
+ const motionPhotoCreated = await buildGoogleMotionPhoto({
532
+ imagePath: staticImgPath,
533
+ videoPath: liveimg.filepath,
534
+ outputPath: motionPhotoCoverPath
535
+ })
536
+ if (motionPhotoCreated) {
537
+ temp.push({ filepath: motionPhotoCoverPath, totalBytes: 0 })
538
+ const motionPhotoCover = processLocalImageFile(motionPhotoCoverPath)
539
+ images.push(segment.image(motionPhotoCover))
540
+ hasPushedMotionPhotoCover = true
541
+ }
542
+ }
543
+ if (!hasPushedMotionPhotoCover) {
544
+ const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index)
545
+ images.push(segment.image(imageUrl))
546
+ } else {
547
+ hasGeneratedLivePhoto = true // 标记已生成实况图
548
+ }
549
+ }
550
+
551
+ logger.mark('正在尝试删除缓存文件')
552
+ await Common.removeFile(liveimg.filepath, true)
553
+ }
554
+ }
555
+
556
+ // 如果生成了实况图,添加提示文字
557
+ if (hasGeneratedLivePhoto) {
558
+ const tipImg = await Render(this.e, 'other/live-photo-tip', {
559
+ title: '实况照片已生成',
560
+ description: '保存原图到相册即可识别为实况图'
561
+ })
562
+ images.push(...tipImg)
563
+ }
564
+
565
+ const bot = karin.getBot(botId) as AdapterType
566
+ try {
567
+ if (images.length === 0) {
568
+ logger.warn(`抖音合辑推送解析未生成可发送内容,aweme_id=${Detail_Data.aweme_id}`)
569
+ } else {
570
+ const Element = common.makeForward(images, botId, bot.account.name)
571
+ await bot.sendForwardMsg(Contact, Element, {
572
+ source: '合辑内容',
573
+ summary: `查看${Element.length}张图片/视频消息`,
574
+ prompt: '抖音合辑解析结果',
575
+ news: [{ text: '点击查看解析结果' }]
576
+ })
577
+ }
578
+ } catch (error) {
579
+ logger.error(`发送合辑失败: ${error}`)
580
+ } finally {
581
+ for (const item of temp) {
582
+ await Common.removeFile(item.filepath, true)
583
+ }
584
+ }
585
+ } else if (Detail_Data.images) {
586
+ // 普通图集处理逻辑
587
+ // 检查是否包含 live 图(clip_type !== 2 且 clip_type !== undefined)
588
+ const hasLiveImage = Detail_Data.images.some((item) => item.clip_type !== 2 && item.clip_type !== undefined)
589
+
590
+ if (hasLiveImage) {
591
+ // 包含 live 图,需要特殊处理
592
+ const processedImages: Elements[] = []
593
+ const temp: fileInfo[] = []
594
+ let hasGeneratedLivePhoto = false // 标记是否生成了实况图
595
+
596
+ /** 下载 BGM(如果存在) */
597
+ let liveimgbgm: fileInfo | null = null
598
+ let bgmContext: LiveImageMergeOptions['context'] | null = null
599
+ const mergeMode = Config.douyin.liveImageMergeMode ?? 'independent'
600
+
601
+ if (Detail_Data.music) {
602
+ let mp3Path = ''
603
+ if (Detail_Data.music.play_url.uri === '') {
604
+ const extraData = JSON.parse(Detail_Data.music.extra)
605
+ mp3Path = extraData.original_song_url
606
+ } else {
607
+ mp3Path = Detail_Data.music.play_url.uri
608
+ }
609
+
610
+ liveimgbgm = await downloadFile(mp3Path, {
611
+ title: `Douyin_tmp_A_${Date.now()}.mp3`,
612
+ headers: douyinBaseHeaders
613
+ })
614
+ temp.push(liveimgbgm)
615
+ }
616
+
617
+ for (const [index, item] of Detail_Data.images.entries()) {
618
+ // 静态图片,clip_type为2或undefined
619
+ if (item.clip_type === 2 || item.clip_type === undefined) {
620
+ const image_url = item.url_list[2] ?? item.url_list[1]
621
+ if (!image_url) continue
622
+ const imageUrl = await processImageUrl(image_url, Detail_Data.desc, index)
623
+ processedImages.push(segment.image(imageUrl))
624
+ continue
625
+ }
626
+
627
+ /** live 图 */
628
+ const livePlayAddr = item.video?.play_addr_h264
629
+ if (!livePlayAddr?.uri) {
630
+ logger.warn(`图集第 ${index + 1} 个动态媒体缺少视频源,跳过`)
631
+ continue
632
+ }
633
+ const liveimg = await downloadFile(buildDouyinPlayUrl(livePlayAddr), {
634
+ title: `Douyin_tmp_V_${Date.now()}.mp4`,
635
+ headers: douyinBaseHeaders
636
+ })
637
+
638
+ if (liveimg.filepath) {
639
+ const outputPath = Common.tempDri.video + `Douyin_Result_${Date.now()}.mp4`
640
+ const loopCount = item.clip_type === 4 ? 1 : 3
641
+ let staticImgPath = ''
642
+ if (item.url_list?.[0]) {
643
+ const staticImg = await downloadFile(item.url_list[0], {
644
+ title: `Douyin_static_${Date.now()}_${index}.jpg`,
645
+ headers: douyinBaseHeaders,
646
+ filepath: Common.tempDri.images + `Douyin_static_${Date.now()}_${index}.jpg`
647
+ })
648
+ if (staticImg.filepath) {
649
+ temp.push({ filepath: staticImg.filepath, totalBytes: 0 })
650
+ }
651
+ staticImgPath = staticImg.filepath ?? ''
652
+ }
653
+
654
+ const { shouldGenerateVideo, shouldGenerateLivePhoto } = getDouyinLiveImageSendPolicy(
655
+ item.clip_type,
656
+ Config.app.livePhotoMode ?? 'video_and_livephoto'
657
+ )
658
+
659
+ // 生成视频
660
+ if (shouldGenerateVideo) {
661
+ const transitionEnabled = loopCount > 1 && Boolean(staticImgPath)
662
+ const safeStaticPath = staticImgPath || liveimg.filepath
663
+ const result = await loopVideoWithTransition({
664
+ inputPath: liveimg.filepath,
665
+ outputPath,
666
+ loopCount,
667
+ staticImagePath: safeStaticPath,
668
+ transitionEnabled,
669
+ bgmPath: liveimgbgm?.filepath,
670
+ mergeMode,
671
+ context: bgmContext ?? undefined
672
+ })
673
+ const success = result.success
674
+ if (mergeMode === 'continuous' && result.context) {
675
+ bgmContext = result.context
676
+ }
677
+
678
+ if (success) {
679
+ const filePath = Common.tempDri.video + `tmp_${Date.now()}.mp4`
680
+ fs.renameSync(outputPath, filePath)
681
+ logger.mark(`视频文件重命名完成: ${outputPath.split('/').pop()} -> ${filePath.split('/').pop()}`)
682
+ temp.push({ filepath: filePath, totalBytes: 0 })
683
+ const videoPath =
684
+ Config.app.videoSendMode === 'base64'
685
+ ? `base64://${fs.readFileSync(filePath).toString('base64')}`
686
+ : `file://${filePath}`
687
+ processedImages.push(segment.video(videoPath))
688
+ }
689
+ }
690
+
691
+ // 生成实况图(clip_type === 5 是 livePhoto)
692
+ if (shouldGenerateLivePhoto && item.clip_type === 5 && item.url_list?.[0]) {
693
+ let hasPushedMotionPhotoCover = false
694
+ if (staticImgPath) {
695
+ const motionPhotoCoverPath =
696
+ Common.tempDri.images + `MVIMG_${format(new Date(), 'yyyyMMdd_HHmmss_SSS')}_${index}.jpg`
697
+ const motionPhotoCreated = await buildGoogleMotionPhoto({
698
+ imagePath: staticImgPath,
699
+ videoPath: liveimg.filepath,
700
+ outputPath: motionPhotoCoverPath
701
+ })
702
+ if (motionPhotoCreated) {
703
+ temp.push({ filepath: motionPhotoCoverPath, totalBytes: 0 })
704
+ const motionPhotoCover = processLocalImageFile(motionPhotoCoverPath)
705
+ processedImages.push(segment.image(motionPhotoCover))
706
+ hasPushedMotionPhotoCover = true
707
+ }
708
+ }
709
+ if (!hasPushedMotionPhotoCover) {
710
+ const imageUrl = await processImageUrl(item.url_list[0], Detail_Data.desc, index)
711
+ processedImages.push(segment.image(imageUrl))
712
+ } else {
713
+ hasGeneratedLivePhoto = true // 标记已生成实况图
714
+ }
715
+ }
716
+
717
+ logger.mark('正在尝试删除缓存文件')
718
+ await Common.removeFile(liveimg.filepath, true)
719
+ }
720
+ }
721
+
722
+ // 如果生成了实况图,添加提示文字
723
+ if (hasGeneratedLivePhoto) {
724
+ const tipImg = await Render(this.e, 'other/live-photo-tip', {
725
+ title: '实况照片已生成',
726
+ description: '保存原图到相册即可识别为实况图'
727
+ })
728
+ processedImages.push(...tipImg)
729
+ }
730
+
731
+ const bot = karin.getBot(botId) as AdapterType
732
+ try {
733
+ if (processedImages.length === 0) {
734
+ logger.warn(`抖音图集推送解析未生成可发送内容,aweme_id=${Detail_Data.aweme_id}`)
735
+ } else {
736
+ const Element = common.makeForward(processedImages, botId, bot.account.name)
737
+ await bot.sendForwardMsg(Contact, Element, {
738
+ source: '图集内容',
739
+ summary: `查看${Element.length}张图片/视频消息`,
740
+ prompt: '抖音图集解析结果',
741
+ news: [{ text: '点击查看解析结果' }]
742
+ })
743
+ }
744
+ } catch (error) {
745
+ logger.error(`发送图集失败: ${error}`)
746
+ } finally {
747
+ for (const item of temp) {
748
+ await Common.removeFile(item.filepath, true)
749
+ }
750
+ }
751
+ } else {
752
+ // 纯静态图集
753
+ const imageres: ImageElement[] = []
754
+ let image_url
755
+ for (const [index, item] of Detail_Data.images.entries()) {
756
+ image_url = item.url_list[2] ?? item.url_list[1] // 图片地址
757
+ if (!image_url) continue
758
+ const imageUrl = await processImageUrl(image_url, Detail_Data.desc, index)
759
+ imageres.push(segment.image(imageUrl))
760
+ }
761
+ const bot = karin.getBot(botId) as AdapterType
762
+
763
+ if (imageres.length === 1 && image_url) {
764
+ // 单张图片直接发送
765
+ const imageUrl = await processImageUrl(image_url, Detail_Data.desc)
766
+ await bot.sendMsg(Contact, [segment.image(imageUrl)])
767
+ } else {
768
+ // 多张图片使用合并转发
769
+ const forwardMsg = common.makeForward(imageres, botId, bot.account.name)
770
+ await bot.sendForwardMsg(Contact, forwardMsg, {
771
+ source: '图片合集',
772
+ summary: `查看${forwardMsg.length}张图片消息`,
773
+ prompt: '抖音图集解析结果',
774
+ news: [{ text: '点击查看解析结果' }]
775
+ })
776
+ }
777
+ }
778
+ }
779
+ }
780
+ }
781
+ }
782
+
783
+ // 添加作品缓存(直播不需要缓存aweme_id)
784
+ if (skip || (pushItem.pushType !== 'live' && status.message_id)) {
785
+ await douyinDB.addAwemeCache(actualAwemeId, pushItem.sec_uid, groupId, pushItem.pushType)
786
+ }
787
+ }
788
+ }
789
+
790
+ return true
791
+ }
792
+
793
+ /**
794
+ * 根据配置文件获取用户当天的作品列表。
795
+ * @returns 将要推送的列表
796
+ */
797
+ async getDynamicList(userList: douyinPushItem[]): Promise<WillBePushList> {
798
+ const willbepushlist: WillBePushList = {}
799
+
800
+ try {
801
+ /** 过滤掉不启用的订阅项 */
802
+ const filteredUserList = userList.filter((item) => item.switch !== false)
803
+ for (const item of filteredUserList) {
804
+ await common.sleep(2000)
805
+
806
+ if (!item.sec_uid) {
807
+ logger.warn(`用户 ${item.remark || item.short_id} 缺少 sec_uid,跳过`)
808
+ continue
809
+ }
810
+
811
+ const sec_uid = item.sec_uid
812
+ const pushTypes = item.pushTypes || ['post'] // 默认推送作品列表
813
+
814
+ logger.debug(`开始获取用户:${item.remark}(${sec_uid})的内容,推送类型:${pushTypes.join(', ')}`)
815
+
816
+ const userinfo = await this.amagi.douyin.fetcher.fetchUserProfile({ sec_uid })
817
+
818
+ const targets = item.group_id.map((groupWithBot) => {
819
+ const [groupId, botId] = groupWithBot.split(':')
820
+ return { groupId, botId }
821
+ })
822
+
823
+ // 如果没有订阅群组,跳过该用户
824
+ if (targets.length === 0) continue
825
+
826
+ // special_state 特殊状态,用户已注销
827
+ if (userinfo.data.user?.special_state_info?.special_state === 1 && userinfo.data.user?.user_deleted === true) {
828
+ logger.warn(`${item.remark}(${sec_uid})${userinfo.data.user.special_state_info.title}`)
829
+ continue
830
+ }
831
+
832
+ // 遍历每种推送类型
833
+ for (const pushType of pushTypes) {
834
+ await common.sleep(1000)
835
+
836
+ // 处理直播推送
837
+ if (pushType === 'live') {
838
+ const liveItem = await processLiveStream(sec_uid, userinfo.data, item, targets, this.amagi)
839
+ if (liveItem) {
840
+ willbepushlist[`live_${sec_uid}`] = liveItem
841
+ }
842
+ continue
843
+ }
844
+
845
+ let contentList: DouyinListItem[] = []
846
+ let listName = ''
847
+
848
+ // 根据推送类型获取不同的列表
849
+ switch (pushType) {
850
+ case 'post':
851
+ listName = '作品列表'
852
+ const videolist = await this.amagi.douyin.fetcher.fetchUserVideoList({
853
+ sec_uid,
854
+ number: 15
855
+ })
856
+ contentList = videolist.data.aweme_list || []
857
+ break
858
+ case 'favorite':
859
+ listName = '喜欢列表'
860
+ const favoritelist = await this.amagi.douyin.fetcher.fetchUserFavoriteList({
861
+ sec_uid,
862
+ number: 15
863
+ })
864
+ if (favoritelist.data.aweme_list.length === 0)
865
+ logger.warn(`${item.remark}(${item.short_id}) 获取到的喜欢列表数量为零!此博主可能未公开他/她的喜欢列表`)
866
+ contentList = favoritelist.data.aweme_list || []
867
+ break
868
+ case 'recommend':
869
+ listName = '推荐列表'
870
+ const recommendlist = await this.amagi.douyin.fetcher.fetchUserRecommendList({
871
+ sec_uid,
872
+ number: 15
873
+ })
874
+ if (recommendlist.data.aweme_list.length === 0)
875
+ logger.warn(`${item.remark}(${item.short_id}) 获取到的推荐列表数量为零!此博主可能未公开他/她的推荐列表`)
876
+ contentList = recommendlist.data.aweme_list || []
877
+ break
878
+ }
879
+
880
+ logger.debug(`获取到 ${item.remark} 的${listName},共 ${contentList.length} 条`)
881
+
882
+ // 根据推送类型调用不同的处理函数
883
+ if (contentList.length > 0) {
884
+ let pushItems: DouyinWorkPushItem[] = []
885
+ switch (pushType) {
886
+ case 'post':
887
+ pushItems = await processPostList(contentList, sec_uid, userinfo.data, item, targets)
888
+ break
889
+ case 'favorite':
890
+ pushItems = await processFavoriteList(contentList, sec_uid, userinfo.data, item, targets, this.force)
891
+ break
892
+ case 'recommend':
893
+ pushItems = await processRecommendList(contentList, sec_uid, userinfo.data, item, targets, this.force)
894
+ break
895
+ }
896
+
897
+ // 将返回的推送项添加到willbepushlist中
898
+ for (const pushItem of pushItems) {
899
+ const key = `${pushType}_${pushItem.Detail_Data.aweme_id}`
900
+ willbepushlist[key] = pushItem
901
+ }
902
+ }
903
+ }
904
+ }
905
+ } catch (error) {
906
+ throw new Error(`获取抖音用户内容列表失败: ${error}`)
907
+ }
908
+
909
+ return willbepushlist
910
+ }
911
+
912
+ /**
913
+ * 检查作品是否已经推送过
914
+ * @param aweme_id 作品ID
915
+ * @param sec_uid 用户sec_uid
916
+ * @param groupIds 群组ID列表
917
+ * @param pushType 推送类型
918
+ * @returns 是否已经推送过
919
+ */
920
+ async checkIfAlreadyPushed(aweme_id: string, sec_uid: string, groupIds: string[], pushType: string = 'post'): Promise<boolean> {
921
+ for (const groupId of groupIds) {
922
+ const isPushed = await douyinDB.isAwemePushed(aweme_id, sec_uid, groupId, pushType)
923
+ if (!isPushed) {
924
+ return false
925
+ }
926
+ }
927
+ return true
928
+ }
929
+
930
+ /**
931
+ * 设置或更新特定 sec_uid 的群组信息。
932
+ * @param data 抖音的搜索结果数据。需要接口返回的原始数据
933
+ * @returns 操作成功或失败的消息字符串。
934
+ */
935
+ async setting(data: DouyinSearchResponse): Promise<void> {
936
+ const groupInfo = await this.e.bot.getGroupInfo('groupId' in this.e && this.e.groupId ? this.e.groupId : '')
937
+ const config = Config.pushlist // 读取配置文件
938
+ const groupId = 'groupId' in this.e && this.e.groupId ? this.e.groupId : ''
939
+ const botId = this.e.selfId
940
+
941
+ try {
942
+ // 获取用户输入的抖音号
943
+ const inputDouyinId = this.e.msg.replace(/^#设置抖音推送/, '').trim()
944
+
945
+ /**
946
+ * 搜索结果按响应形态自述(判别式 `__search_type`,由 amagi 的 normalize 按响应结构 +
947
+ * 请求类型写入)。订阅只认「用户搜索」那一支:抖音把 user 请求回成别的形态时在这里说清楚,
948
+ * 而不是拿一个 undefined 去遍历。
949
+ */
950
+ if (data.__search_type !== 'user') {
951
+ throw new Error(`抖音没有返回用户搜索结果(返回形态:${data.__search_type ?? '未知'})`)
952
+ }
953
+
954
+ // 在用户列表中查找匹配的用户
955
+ let matchedUser = null
956
+ for (const userItem of data.user_list) {
957
+ const currentDouyinId = userItem.user_info.unique_id === '' ? userItem.user_info.short_id : userItem.user_info.unique_id
958
+ if (currentDouyinId === inputDouyinId) {
959
+ matchedUser = userItem.user_info
960
+ break
961
+ }
962
+ }
963
+
964
+ // 如果没找到匹配的用户,抛出错误
965
+ if (!matchedUser) {
966
+ throw new Error(`未找到抖音号为 ${inputDouyinId} 的用户`)
967
+ }
968
+
969
+ // 使用匹配到的用户的 sec_uid 进行下一步请求
970
+ const sec_uid = matchedUser.sec_uid
971
+ const UserInfoData = await this.amagi.douyin.fetcher.fetchUserProfile({ sec_uid })
972
+
973
+ /** 处理抖音号 */
974
+ let user_shortid
975
+ user_shortid = UserInfoData.data.user.unique_id === '' ? UserInfoData.data.user.short_id : UserInfoData.data.user.unique_id
976
+
977
+ // 初始化 douyin 数组
978
+ config.douyin ??= []
979
+
980
+ // 查找是否存在相同的 sec_uid
981
+ const existingItem = config.douyin.find((item: { sec_uid: string }) => item.sec_uid === sec_uid)
982
+
983
+ // 检查数据库中是否已订阅
984
+ const isSubscribed = await douyinDB.isSubscribed(sec_uid, groupId)
985
+
986
+ if (existingItem) {
987
+ // 如果已经存在相同的 sec_uid,则检查是否存在相同的 group_id
988
+ let has = false
989
+ let groupIndexToRemove = -1 // 用于记录要删除的 group_id 对象的索引
990
+ for (let index = 0; index < existingItem.group_id.length; index++) {
991
+ // 分割每个对象的 id 属性,并获取第一部分
992
+ const item = existingItem.group_id[index]
993
+ const existingGroupId = item.split(':')[0]
994
+
995
+ // 检查分割后的第一部分是否与提供的 group_id 相同
996
+ if (existingGroupId === String(groupId)) {
997
+ has = true
998
+ groupIndexToRemove = index
999
+ break // 找到匹配项后退出循环
1000
+ }
1001
+ }
1002
+
1003
+ if (has) {
1004
+ // 如果存在相同的 group_id,则删除它
1005
+ existingItem.group_id.splice(groupIndexToRemove, 1)
1006
+
1007
+ // 同时从数据库中取消订阅
1008
+ if (isSubscribed) {
1009
+ await douyinDB.unsubscribeDouyinUser(groupId, sec_uid)
1010
+ }
1011
+
1012
+ // 如果删除后 group_id 数组为空,则删除整个属性
1013
+ if (existingItem.group_id.length === 0) {
1014
+ const index = config.douyin.indexOf(existingItem)
1015
+ config.douyin.splice(index, 1)
1016
+ }
1017
+
1018
+ // 保存配置到文件
1019
+ Config.Modify('pushlist', 'douyin', config.douyin)
1020
+ await this.e.reply(
1021
+ `群:${groupInfo.groupName}(${groupId})\n删除成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}`
1022
+ )
1023
+ logger.info(`\n删除成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}\nsec_uid${UserInfoData.data.user.sec_uid}`)
1024
+ } else {
1025
+ // 否则,将新的 group_id 添加到该 sec_uid 对应的数组中
1026
+ existingItem.group_id.push(`${groupId}:${botId}`)
1027
+
1028
+ // 确保 pushTypes 字段存在,如果不存在则添加默认值
1029
+ if (!existingItem.pushTypes || existingItem.pushTypes.length === 0) {
1030
+ existingItem.pushTypes = ['post', 'live']
1031
+ }
1032
+
1033
+ // 同时在数据库中添加订阅
1034
+ if (!isSubscribed) {
1035
+ await douyinDB.subscribeDouyinUser(groupId, botId, sec_uid, user_shortid, UserInfoData.data.user.nickname)
1036
+ }
1037
+
1038
+ // 保存配置到文件
1039
+ Config.Modify('pushlist', 'douyin', config.douyin)
1040
+ await this.e.reply(
1041
+ `群:${groupInfo.groupName}(${groupId})\n添加成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}`
1042
+ )
1043
+ if (Config.douyin.push.switch === false) await this.e.reply('请发送「#设置抖音推送开启」以进行推送')
1044
+ logger.info(`\n设置成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}\nsec_uid${UserInfoData.data.user.sec_uid}`)
1045
+ }
1046
+ } else {
1047
+ // 如果不存在相同的 sec_uid,则新增一个属性
1048
+ config.douyin.push({
1049
+ switch: true,
1050
+ sec_uid,
1051
+ group_id: [`${groupId}:${botId}`],
1052
+ remark: UserInfoData.data.user.nickname,
1053
+ short_id: user_shortid,
1054
+ pushTypes: ['post', 'live']
1055
+ })
1056
+
1057
+ // 同时在数据库中添加订阅
1058
+ if (!isSubscribed) {
1059
+ await douyinDB.subscribeDouyinUser(groupId, botId, sec_uid, user_shortid, UserInfoData.data.user.nickname)
1060
+ }
1061
+
1062
+ // 保存配置到文件
1063
+ Config.Modify('pushlist', 'douyin', config.douyin)
1064
+ await this.e.reply(`群:${groupInfo.groupName}(${groupId})\n添加成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}`)
1065
+ if (Config.douyin.push.switch === false) await this.e.reply('请发送「#设置抖音推送开启」以进行推送')
1066
+ logger.info(`\n设置成功!${UserInfoData.data.user.nickname}\n抖音号:${user_shortid}\nsec_uid${UserInfoData.data.user.sec_uid}`)
1067
+ }
1068
+
1069
+ await this.renderPushList()
1070
+ } catch (error) {
1071
+ throw new Error(`设置失败,请查看日志: ${error}`)
1072
+ }
1073
+ }
1074
+
1075
+ /** 渲染推送列表图片 */
1076
+ async renderPushList(): Promise<void> {
1077
+ await this.syncConfigToDatabase()
1078
+ const groupInfo = await this.e.bot.getGroupInfo('groupId' in this.e && this.e.groupId ? this.e.groupId : '')
1079
+
1080
+ // 获取当前群组的所有订阅
1081
+ const subscriptions = await douyinDB.getGroupSubscriptions(groupInfo.groupId)
1082
+
1083
+ if (subscriptions.length === 0) {
1084
+ await this.e.reply(
1085
+ `当前群:${groupInfo.groupName}(${groupInfo.groupId})\n没有设置任何抖音博主推送!\n可使用「#设置抖音推送 + 抖音号」进行设置`
1086
+ )
1087
+ return
1088
+ }
1089
+
1090
+ const renderOpt: DouyinUserListData['renderOpt'] = []
1091
+
1092
+ for (const subscription of subscriptions) {
1093
+ const sec_uid = subscription.sec_uid
1094
+ const userInfo = await this.amagi.douyin.fetcher.fetchUserProfile({ sec_uid })
1095
+
1096
+ // 查找配置文件中对应的全局开关状态
1097
+ const configItem = Config.pushlist.douyin?.find((item: douyinPushItem) => item.sec_uid === sec_uid)
1098
+ const switchStatus = configItem?.switch !== false // 默认为 true
1099
+ const pushTypes = configItem?.pushTypes || ['post']
1100
+
1101
+ renderOpt.push({
1102
+ avatar_img: userInfo.data.user.avatar_larger.url_list[0],
1103
+ username: userInfo.data.user.nickname,
1104
+ short_id: userInfo.data.user.unique_id === '' ? userInfo.data.user.short_id : userInfo.data.user.unique_id,
1105
+ fans: this.count(userInfo.data.user.follower_count),
1106
+ total_favorited: this.count(userInfo.data.user.total_favorited),
1107
+ following_count: this.count(userInfo.data.user.following_count),
1108
+ switch: switchStatus,
1109
+ pushTypes
1110
+ })
1111
+ }
1112
+ const img = await Render(this.e, 'douyin/userlist', {
1113
+ renderOpt,
1114
+ groupInfo: {
1115
+ groupId: groupInfo.groupId || '',
1116
+ groupName: groupInfo.groupName || '',
1117
+ groupAvatar: groupInfo.avatar || ''
1118
+ }
1119
+ })
1120
+ await this.e.reply(img)
1121
+ }
1122
+
1123
+ /**
1124
+ * 强制推送
1125
+ * @param data 处理完成的推送列表
1126
+ */
1127
+ async forcepush(data: WillBePushList) {
1128
+ const currentGroupId = 'groupId' in this.e && this.e.groupId ? this.e.groupId : ''
1129
+ const currentBotId = this.e.selfId
1130
+
1131
+ // 如果不是全部强制推送,需要过滤数据
1132
+ if (!this.e.msg.includes('全部')) {
1133
+ // 获取当前群组订阅的所有抖音用户
1134
+ const subscriptions = await douyinDB.getGroupSubscriptions(currentGroupId)
1135
+ const subscribedUids = subscriptions.map((sub) => sub.sec_uid)
1136
+
1137
+ // 创建一个新的推送列表,只包含当前群组订阅的用户的作品
1138
+ const filteredData: WillBePushList = {}
1139
+
1140
+ for (const awemeId in data) {
1141
+ // 检查该作品的用户是否被当前群组订阅
1142
+ if (subscribedUids.includes(data[awemeId].sec_uid)) {
1143
+ // 复制该作品到过滤后的列表,并将目标设置为当前群组
1144
+ filteredData[awemeId] = {
1145
+ ...data[awemeId],
1146
+ targets: [
1147
+ {
1148
+ groupId: currentGroupId,
1149
+ botId: currentBotId
1150
+ }
1151
+ ]
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ // 使用过滤后的数据进行推送
1157
+ await this.getdata(filteredData)
1158
+ } else {
1159
+ // 全部强制推送,保持原有逻辑
1160
+ await this.getdata(data)
1161
+ }
1162
+ }
1163
+
1164
+ /**
1165
+ * 检查并更新备注信息
1166
+ */
1167
+ async checkremark() {
1168
+ // 读取配置文件内容
1169
+ const config = Config.pushlist
1170
+ const updateList: { sec_uid: string }[] = []
1171
+
1172
+ if (Config.pushlist.douyin === null || Config.pushlist.douyin.length === 0) return true
1173
+
1174
+ // 遍历配置文件中的用户列表,收集需要更新备注信息的用户
1175
+ for (const i of Config.pushlist.douyin) {
1176
+ const remark = i.remark
1177
+ const sec_uid = i.sec_uid
1178
+
1179
+ if (remark === undefined || remark === '') {
1180
+ updateList.push({ sec_uid })
1181
+ }
1182
+ }
1183
+
1184
+ // 如果有需要更新备注的用户,则逐个获取备注信息并更新到配置文件中
1185
+ if (updateList.length > 0) {
1186
+ for (const i of updateList) {
1187
+ // 从外部数据源获取用户备注信息
1188
+ const userinfo = await this.amagi.douyin.fetcher.fetchUserProfile({ sec_uid: i.sec_uid })
1189
+ const remark = userinfo.data.user.nickname
1190
+
1191
+ // 在配置文件中找到对应的用户,并更新其备注信息
1192
+ const matchingItemIndex = config.douyin.findIndex((item: { sec_uid: string }) => item.sec_uid === i.sec_uid)
1193
+ if (matchingItemIndex !== -1) {
1194
+ config.douyin[matchingItemIndex].remark = remark
1195
+ }
1196
+ }
1197
+
1198
+ // 将更新后的配置文件内容写回文件
1199
+ Config.Modify('pushlist', 'douyin', config.douyin)
1200
+ }
1201
+
1202
+ return false
1203
+ }
1204
+
1205
+ /**
1206
+ * 格式化数字
1207
+ */
1208
+ count(num: number) {
1209
+ if (num > 10000) {
1210
+ return (num / 10000).toFixed(1) + '万'
1211
+ }
1212
+ return num.toString()
1213
+ }
1214
+ }
1215
+
1216
+ /**
1217
+ * 判断标题是否有屏蔽词或屏蔽标签
1218
+ * @param PushItem 推送项
1219
+ * @returns 是否应该跳过推送
1220
+ */
1221
+ const skipDynamic = async (PushItem: DouyinPushItem): Promise<boolean> => {
1222
+ // 直播动态不做内容过滤
1223
+ if (PushItem.pushType === 'live' || 'liveStatus' in PushItem.Detail_Data) {
1224
+ return false
1225
+ }
1226
+
1227
+ const tags: string[] = []
1228
+
1229
+ // 提取标签
1230
+ if (PushItem.Detail_Data.text_extra) {
1231
+ for (const item of PushItem.Detail_Data.text_extra) {
1232
+ if (item.hashtag_name) {
1233
+ tags.push(item.hashtag_name)
1234
+ }
1235
+ }
1236
+ }
1237
+
1238
+ logger.debug(`检查作品是否需要过滤:${PushItem.Detail_Data.share_url}`)
1239
+ const shouldFilter = await douyinDB.shouldFilter(PushItem, tags)
1240
+ return shouldFilter
1241
+ }