@lemoncat7/dsh-partner 2.23.55 → 2.25.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 (301) hide show
  1. package/README.md +7 -2
  2. package/docs/compatibility-0.2.2-rc.1.md +7 -0
  3. package/docs/releases/2.24.0-rc.10.md +8 -0
  4. package/docs/releases/2.24.0-rc.11.md +10 -0
  5. package/docs/releases/2.24.0-rc.2.md +9 -0
  6. package/docs/releases/2.24.0-rc.3.md +6 -0
  7. package/docs/releases/2.24.0-rc.4.md +11 -0
  8. package/docs/releases/2.24.0-rc.5.md +11 -0
  9. package/docs/releases/2.24.0-rc.6.md +12 -0
  10. package/docs/releases/2.24.0-rc.9.md +5 -0
  11. package/docs/releases/2.25.0-rc.1.md +12 -0
  12. package/docs/releases/2.25.0-rc.11.md +5 -0
  13. package/docs/releases/2.25.0-rc.2.md +10 -0
  14. package/docs/releases/2.25.0-rc.3.md +11 -0
  15. package/docs/releases/2.25.0-rc.4.md +3 -0
  16. package/docs/releases/2.25.0-rc.5.md +3 -0
  17. package/docs/releases/2.25.0.md +32 -0
  18. package/lib/agent-runtime.d.ts +3 -1
  19. package/lib/agent-runtime.d.ts.map +1 -1
  20. package/lib/agent-runtime.js +91 -18
  21. package/lib/agent-runtime.js.map +1 -1
  22. package/lib/api/features/tts-api.d.ts +4 -0
  23. package/lib/api/features/tts-api.d.ts.map +1 -0
  24. package/lib/api/features/tts-api.js +49 -0
  25. package/lib/api/features/tts-api.js.map +1 -0
  26. package/lib/api.d.ts +3 -0
  27. package/lib/api.d.ts.map +1 -1
  28. package/lib/api.js +79 -6
  29. package/lib/api.js.map +1 -1
  30. package/lib/channels/activity-defaults.d.ts.map +1 -1
  31. package/lib/channels/activity-defaults.js +7 -0
  32. package/lib/channels/activity-defaults.js.map +1 -1
  33. package/lib/channels/bilibili/activity-provider.d.ts +3 -0
  34. package/lib/channels/bilibili/activity-provider.d.ts.map +1 -0
  35. package/lib/channels/bilibili/activity-provider.js +8 -0
  36. package/lib/channels/bilibili/activity-provider.js.map +1 -0
  37. package/lib/channels/bilibili/catalog.d.ts +23 -0
  38. package/lib/channels/bilibili/catalog.d.ts.map +1 -0
  39. package/lib/channels/bilibili/catalog.js +45 -0
  40. package/lib/channels/bilibili/catalog.js.map +1 -0
  41. package/lib/channels/bilibili/media.d.ts +9 -0
  42. package/lib/channels/bilibili/media.d.ts.map +1 -0
  43. package/lib/channels/bilibili/media.js +67 -0
  44. package/lib/channels/bilibili/media.js.map +1 -0
  45. package/lib/channels/bilibili/service.d.ts +37 -0
  46. package/lib/channels/bilibili/service.d.ts.map +1 -0
  47. package/lib/channels/bilibili/service.js +138 -0
  48. package/lib/channels/bilibili/service.js.map +1 -0
  49. package/lib/channels/bilibili/tools.d.ts +6 -0
  50. package/lib/channels/bilibili/tools.d.ts.map +1 -0
  51. package/lib/channels/bilibili/tools.js +48 -0
  52. package/lib/channels/bilibili/tools.js.map +1 -0
  53. package/lib/channels/direct/message-format.js +298 -206
  54. package/lib/channels/direct/message-format.js.map +4 -4
  55. package/lib/channels/direct/transport.d.ts +1 -1
  56. package/lib/channels/direct/transport.d.ts.map +1 -1
  57. package/lib/channels/manager.d.ts +2 -0
  58. package/lib/channels/manager.d.ts.map +1 -1
  59. package/lib/channels/manager.js +24 -4
  60. package/lib/channels/manager.js.map +1 -1
  61. package/lib/channels/outbound-media.d.ts +1 -1
  62. package/lib/channels/outbound-media.d.ts.map +1 -1
  63. package/lib/channels/outbound-media.js +3 -2
  64. package/lib/channels/outbound-media.js.map +1 -1
  65. package/lib/channels/qq/authority.d.ts +1 -1
  66. package/lib/channels/qq/authority.d.ts.map +1 -1
  67. package/lib/channels/qq/authority.js +2 -2
  68. package/lib/channels/qq/authority.js.map +1 -1
  69. package/lib/channels/qq/reply-tool.d.ts +1 -1
  70. package/lib/channels/qq/reply-tool.d.ts.map +1 -1
  71. package/lib/channels/qq/reply-tool.js +7 -2
  72. package/lib/channels/qq/reply-tool.js.map +1 -1
  73. package/lib/channels/weixin/api.d.ts +1 -1
  74. package/lib/channels/weixin/api.d.ts.map +1 -1
  75. package/lib/channels/weixin/api.js +7 -1
  76. package/lib/channels/weixin/api.js.map +1 -1
  77. package/lib/channels/weixin/connect.d.ts +5 -1
  78. package/lib/channels/weixin/connect.d.ts.map +1 -1
  79. package/lib/character/activity-journal.d.ts +3 -1
  80. package/lib/character/activity-journal.d.ts.map +1 -1
  81. package/lib/character/activity-journal.js +43 -7
  82. package/lib/character/activity-journal.js.map +1 -1
  83. package/lib/character/activity-summary.d.ts +23 -0
  84. package/lib/character/activity-summary.d.ts.map +1 -0
  85. package/lib/character/activity-summary.js +23 -0
  86. package/lib/character/activity-summary.js.map +1 -0
  87. package/lib/character/affection-tool.js +2 -2
  88. package/lib/character/affection-tool.js.map +1 -1
  89. package/lib/character/affection.d.ts +6 -42
  90. package/lib/character/affection.d.ts.map +1 -1
  91. package/lib/character/affection.js +4 -13
  92. package/lib/character/affection.js.map +1 -1
  93. package/lib/character/conversation-behavior.d.ts +1 -0
  94. package/lib/character/conversation-behavior.d.ts.map +1 -1
  95. package/lib/character/conversation-behavior.js +8 -5
  96. package/lib/character/conversation-behavior.js.map +1 -1
  97. package/lib/character/habit-tool.d.ts +4 -0
  98. package/lib/character/habit-tool.d.ts.map +1 -0
  99. package/lib/character/habit-tool.js +31 -0
  100. package/lib/character/habit-tool.js.map +1 -0
  101. package/lib/character/habits.d.ts +38 -0
  102. package/lib/character/habits.d.ts.map +1 -0
  103. package/lib/character/habits.js +92 -0
  104. package/lib/character/habits.js.map +1 -0
  105. package/lib/character/idle-contact.d.ts +9 -0
  106. package/lib/character/idle-contact.d.ts.map +1 -1
  107. package/lib/character/idle-contact.js +62 -9
  108. package/lib/character/idle-contact.js.map +1 -1
  109. package/lib/character/interaction-review.d.ts +10 -0
  110. package/lib/character/interaction-review.d.ts.map +1 -1
  111. package/lib/character/interaction-review.js +51 -31
  112. package/lib/character/interaction-review.js.map +1 -1
  113. package/lib/chat/live-speech.d.ts +5 -2
  114. package/lib/chat/live-speech.d.ts.map +1 -1
  115. package/lib/chat/live-speech.js +37 -9
  116. package/lib/chat/live-speech.js.map +1 -1
  117. package/lib/chat/persona-delivery.d.ts +31 -0
  118. package/lib/chat/persona-delivery.d.ts.map +1 -0
  119. package/lib/chat/persona-delivery.js +131 -0
  120. package/lib/chat/persona-delivery.js.map +1 -0
  121. package/lib/chat/protocol.d.ts +1 -1
  122. package/lib/chat/protocol.js +1 -1
  123. package/lib/chat/reply-continuity.d.ts.map +1 -1
  124. package/lib/chat/reply-continuity.js +12 -0
  125. package/lib/chat/reply-continuity.js.map +1 -1
  126. package/lib/chat/tools.d.ts.map +1 -1
  127. package/lib/chat/tools.js +26 -11
  128. package/lib/chat/tools.js.map +1 -1
  129. package/lib/chat/work-expression.d.ts +36 -0
  130. package/lib/chat/work-expression.d.ts.map +1 -1
  131. package/lib/chat/work-expression.js +80 -17
  132. package/lib/chat/work-expression.js.map +1 -1
  133. package/lib/client-api.d.ts +17 -1
  134. package/lib/client-api.d.ts.map +1 -1
  135. package/lib/client-api.js.map +1 -1
  136. package/lib/client.activities.js +3 -0
  137. package/lib/client.d.ts.map +1 -1
  138. package/lib/client.js +4119 -3763
  139. package/lib/client.js.map +4 -4
  140. package/lib/client.tts.js +7 -0
  141. package/lib/collaboration/composition.d.ts.map +1 -1
  142. package/lib/collaboration/composition.js +2 -0
  143. package/lib/collaboration/composition.js.map +1 -1
  144. package/lib/collaboration/tool-discovery.d.ts +1 -1
  145. package/lib/collaboration/tool-discovery.d.ts.map +1 -1
  146. package/lib/collaboration/tool-discovery.js +15 -9
  147. package/lib/collaboration/tool-discovery.js.map +1 -1
  148. package/lib/companions/identity-tool.d.ts +1 -1
  149. package/lib/companions/identity-tool.d.ts.map +1 -1
  150. package/lib/companions/identity-tool.js +3 -4
  151. package/lib/companions/identity-tool.js.map +1 -1
  152. package/lib/companions/identity.js +4 -4
  153. package/lib/companions/identity.js.map +1 -1
  154. package/lib/companions/management.d.ts +4 -4
  155. package/lib/companions/persona-refinement.d.ts.map +1 -1
  156. package/lib/companions/persona-refinement.js +1 -45
  157. package/lib/companions/persona-refinement.js.map +1 -1
  158. package/lib/compat/native-presentation.d.ts +4 -0
  159. package/lib/compat/native-presentation.d.ts.map +1 -0
  160. package/lib/compat/native-presentation.js +40 -0
  161. package/lib/compat/native-presentation.js.map +1 -0
  162. package/lib/compat/ptc-events.d.ts +2 -0
  163. package/lib/compat/ptc-events.d.ts.map +1 -0
  164. package/lib/compat/ptc-events.js +2 -0
  165. package/lib/compat/ptc-events.js.map +1 -0
  166. package/lib/compat/tool-dispatch.d.ts +4 -0
  167. package/lib/compat/tool-dispatch.d.ts.map +1 -0
  168. package/lib/compat/tool-dispatch.js +14 -0
  169. package/lib/compat/tool-dispatch.js.map +1 -0
  170. package/lib/concern-execution-receipt.d.ts +2 -1
  171. package/lib/concern-execution-receipt.d.ts.map +1 -1
  172. package/lib/concern-execution-receipt.js +15 -13
  173. package/lib/concern-execution-receipt.js.map +1 -1
  174. package/lib/concern-execution.d.ts +3 -1
  175. package/lib/concern-execution.d.ts.map +1 -1
  176. package/lib/concern-execution.js +20 -1
  177. package/lib/concern-execution.js.map +1 -1
  178. package/lib/concern-recording.d.ts +1 -1
  179. package/lib/concern-recording.d.ts.map +1 -1
  180. package/lib/concern-recording.js +14 -5
  181. package/lib/concern-recording.js.map +1 -1
  182. package/lib/concern-store.d.ts.map +1 -1
  183. package/lib/concern-store.js +3 -1
  184. package/lib/concern-store.js.map +1 -1
  185. package/lib/concern-tool.d.ts.map +1 -1
  186. package/lib/concern-tool.js +5 -3
  187. package/lib/concern-tool.js.map +1 -1
  188. package/lib/domain.d.ts +13 -1
  189. package/lib/domain.d.ts.map +1 -1
  190. package/lib/domain.js.map +1 -1
  191. package/lib/execution/operation-tool-access.d.ts.map +1 -1
  192. package/lib/execution/operation-tool-access.js +2 -6
  193. package/lib/execution/operation-tool-access.js.map +1 -1
  194. package/lib/execution/service.d.ts +2 -0
  195. package/lib/execution/service.d.ts.map +1 -1
  196. package/lib/execution/service.js +7 -1
  197. package/lib/execution/service.js.map +1 -1
  198. package/lib/execution/task-preamble.js +2 -2
  199. package/lib/execution/task-preamble.js.map +1 -1
  200. package/lib/execution/work-progress-card.d.ts +10 -0
  201. package/lib/execution/work-progress-card.d.ts.map +1 -0
  202. package/lib/execution/work-progress-card.js +45 -0
  203. package/lib/execution/work-progress-card.js.map +1 -0
  204. package/lib/execution/work-progress-state.d.ts +32 -0
  205. package/lib/execution/work-progress-state.d.ts.map +1 -0
  206. package/lib/execution/work-progress-state.js +19 -0
  207. package/lib/execution/work-progress-state.js.map +1 -0
  208. package/lib/execution/work-progress.d.ts +16 -0
  209. package/lib/execution/work-progress.d.ts.map +1 -0
  210. package/lib/execution/work-progress.js +76 -0
  211. package/lib/execution/work-progress.js.map +1 -0
  212. package/lib/execution/work-tool.d.ts.map +1 -1
  213. package/lib/execution/work-tool.js +63 -52
  214. package/lib/execution/work-tool.js.map +1 -1
  215. package/lib/heartbeat.js +1 -1
  216. package/lib/heartbeat.js.map +1 -1
  217. package/lib/index.d.ts.map +1 -1
  218. package/lib/index.js +11 -3
  219. package/lib/index.js.map +1 -1
  220. package/lib/memory-store.d.ts +1 -1
  221. package/lib/observation-loop.d.ts.map +1 -1
  222. package/lib/observation-loop.js +24 -6
  223. package/lib/observation-loop.js.map +1 -1
  224. package/lib/store.d.ts.map +1 -1
  225. package/lib/store.js +12 -1
  226. package/lib/store.js.map +1 -1
  227. package/lib/ui/affection-panel.js +1 -1
  228. package/lib/ui/affection-panel.js.map +1 -1
  229. package/lib/ui/bilibili-settings.d.ts +12 -0
  230. package/lib/ui/bilibili-settings.d.ts.map +1 -0
  231. package/lib/ui/bilibili-settings.js +77 -0
  232. package/lib/ui/bilibili-settings.js.map +1 -0
  233. package/lib/ui/channels-panel.d.ts.map +1 -1
  234. package/lib/ui/channels-panel.js +11 -4
  235. package/lib/ui/channels-panel.js.map +1 -1
  236. package/lib/ui/companion-vitals.js +1 -1
  237. package/lib/ui/companion-vitals.js.map +1 -1
  238. package/lib/ui/habits-panel.d.ts +4 -0
  239. package/lib/ui/habits-panel.d.ts.map +1 -0
  240. package/lib/ui/habits-panel.js +30 -0
  241. package/lib/ui/habits-panel.js.map +1 -0
  242. package/lib/ui/identity-editor.js +1 -8
  243. package/lib/ui/identity-editor.js.map +1 -1
  244. package/lib/ui/idle-contact-editor.d.ts.map +1 -1
  245. package/lib/ui/idle-contact-editor.js +3 -2
  246. package/lib/ui/idle-contact-editor.js.map +1 -1
  247. package/lib/ui/memory/concern-board.js +1 -1
  248. package/lib/ui/memory/concern-board.js.map +1 -1
  249. package/lib/ui/memory/concern-execution-status.d.ts.map +1 -1
  250. package/lib/ui/memory/concern-execution-status.js +2 -2
  251. package/lib/ui/memory/concern-execution-status.js.map +1 -1
  252. package/lib/ui/push-draft.d.ts +2 -2
  253. package/lib/ui/qq-channel-settings.js +3 -3
  254. package/lib/ui/qq-channel-settings.js.map +1 -1
  255. package/lib/ui/thoughts-panel-loader.d.ts +4 -0
  256. package/lib/ui/thoughts-panel-loader.d.ts.map +1 -0
  257. package/lib/ui/thoughts-panel-loader.js +23 -0
  258. package/lib/ui/thoughts-panel-loader.js.map +1 -0
  259. package/lib/ui/thoughts-panel.d.ts.map +1 -1
  260. package/lib/ui/thoughts-panel.js +7 -5
  261. package/lib/ui/thoughts-panel.js.map +1 -1
  262. package/lib/ui/tts-settings-loader.d.ts +4 -0
  263. package/lib/ui/tts-settings-loader.d.ts.map +1 -0
  264. package/lib/ui/tts-settings-loader.js +23 -0
  265. package/lib/ui/tts-settings-loader.js.map +1 -0
  266. package/lib/ui/tts-settings.d.ts +11 -0
  267. package/lib/ui/tts-settings.d.ts.map +1 -0
  268. package/lib/ui/tts-settings.js +63 -0
  269. package/lib/ui/tts-settings.js.map +1 -0
  270. package/lib/ui/workspace-navigation.d.ts +16 -16
  271. package/lib/voice/credentials.d.ts +9 -0
  272. package/lib/voice/credentials.d.ts.map +1 -0
  273. package/lib/voice/credentials.js +12 -0
  274. package/lib/voice/credentials.js.map +1 -0
  275. package/lib/voice/domain.d.ts +42 -0
  276. package/lib/voice/domain.d.ts.map +1 -0
  277. package/lib/voice/domain.js +53 -0
  278. package/lib/voice/domain.js.map +1 -0
  279. package/lib/voice/service.d.ts +63 -0
  280. package/lib/voice/service.d.ts.map +1 -0
  281. package/lib/voice/service.js +211 -0
  282. package/lib/voice/service.js.map +1 -0
  283. package/lib/voice/synthesize.d.ts +12 -0
  284. package/lib/voice/synthesize.d.ts.map +1 -0
  285. package/lib/voice/synthesize.js +97 -0
  286. package/lib/voice/synthesize.js.map +1 -0
  287. package/lib/voice/tool.d.ts +7 -0
  288. package/lib/voice/tool.d.ts.map +1 -0
  289. package/lib/voice/tool.js +73 -0
  290. package/lib/voice/tool.js.map +1 -0
  291. package/lib/voice/validation.d.ts +3 -0
  292. package/lib/voice/validation.d.ts.map +1 -0
  293. package/lib/voice/validation.js +31 -0
  294. package/lib/voice/validation.js.map +1 -0
  295. package/package.json +71 -48
  296. package/resources/bilibili/Dockerfile +7 -0
  297. package/resources/bilibili/media.py +245 -0
  298. package/resources/bilibili/requirements.txt +3 -0
  299. package/resources/bilibili/server.py +300 -0
  300. package/resources/bilibili/test_bridge.py +115 -0
  301. package/resources/bilibili/test_media.py +110 -0
@@ -0,0 +1,73 @@
1
+ import { supportsChannelTts } from './domain.js';
2
+ import { directReplyTarget } from '../channels/reply-access.js';
3
+ import { qqGroupPeer, qqSettings } from '../channels/qq/settings.js';
4
+ import { channelReplyAllowed } from '../channels/reply-access.js';
5
+ import { questionOrigin } from '../channels/question-origin.js';
6
+ import { isIdleContactNotice } from '../character/idle-contact.js';
7
+ import { OneBotApiError } from '../channels/qq/connection.js';
8
+ export function voiceReplyTool(owner, store, service, call, sendAudio) {
9
+ return { name: 'partner_voice_reply', description: '使用伙伴「渠道 → TTS」中配置并分配给当前渠道的通用服务向当前聊天对象说话,属于公开聊天回复能力。action=status 查看当前语音服务和可用音色;action=send_file 用 path 发送脚本已生成的当前会话目录内 WAV/MP3(最多10MB),不合成、不改音色、不要求开启或选择 TTS 服务;可 continueTurn:true 连续发送试听,比较和记录音色由你自行完成。action=speak 将按当前人物写好的 text 合成并发送音频(QQ 为语音条,微信、Matrix、Mattermost 为音频附件),不重写语气。不能填写收件人、接口或密钥。默认送达后结束本轮;continueTurn:true 可接着执行或接话。成功后不重复发同样的文字;结果未知不能重试发送。未启用外部 TTS 时 QQ 原生语音仍可用 partner_qq_reply。', parameters: { type: 'object', additionalProperties: false, required: ['action'], properties: { action: { type: 'string', enum: ['status', 'speak', 'send_file'] }, path: { type: 'string', minLength: 1, maxLength: 4096, description: 'send_file 专用,本会话工作目录内已生成的音频路径' }, text: { type: 'string', minLength: 1, maxLength: 2000 }, voice: { type: 'string', maxLength: 200 }, continueTurn: { type: 'boolean' } } }, presentCall: () => ({ card: 'generic', title: '语音 TTS' }),
10
+ output: { schema: { type: 'object' }, render: (_a, v) => [{ type: 'text', text: JSON.stringify(v) }], presentationMeta: (_a, v) => { const r = v; return r.status === 'confirmed' ? { kind: 'partner-chat-v1', parts: [], delivered: true, deliveryKind: 'text', originRouteId: r.originRouteId, inputMessageId: r.inputMessageId, spokenText: r.spokenText, ...(r.continueTurn ? { intermediate: true } : {}) } : null; } },
11
+ async execute(raw, exec) {
12
+ const a = raw;
13
+ if (!a || Object.keys(a).some(k => !['action', 'path', 'text', 'voice', 'continueTurn'].includes(k)) || !['status', 'speak', 'send_file'].includes(a.action) || a.continueTurn !== undefined && typeof a.continueTurn !== 'boolean')
14
+ throw Error('语音参数无效');
15
+ const agent = exec.agent;
16
+ if (!agent)
17
+ throw Error('需要当前渠道会话');
18
+ const origin = () => { const events = agent.session.snapshotEvents(), state = store.snapshot(); const latest = [...events].reverse().find(e => e.type === 'user/message' && (e.data.source.kind === 'user' || isIdleContactNotice(e))); const route = latest && isIdleContactNotice(latest) ? state.sessions.find(r => r.sessionId === agent.session.id && latest.type === 'user/message' && r.idleContactState?.messageId === latest.data.id) : questionOrigin(agent.session.id, events, state.sessions); return { route, input: latest?.type === 'user/message' ? latest.data.id : undefined }; };
19
+ const initial = origin(), route = initial.route, c = store.snapshot().channels.find(c => c.id === route?.channelId && c.companionId === owner);
20
+ if (!route || route.companionId !== owner || !initial.input || !c || !supportsChannelTts(c.platform))
21
+ throw Error('当前会话不是支持语音 TTS 的消息渠道');
22
+ const group = c.platform === 'qq' ? qqGroupPeer(route.userId) : undefined;
23
+ const target = directReplyTarget(store.snapshot(), c.id, route.userId);
24
+ const guard = () => { exec.signal.throwIfAborted(); const now = origin(), state = store.snapshot(), live = state.channels.find(x => x.id === c.id), settings = qqSettings(live?.qq); if (store.isCompanionRemoving(owner) || !live?.enabled || live.updatedAt !== c.updatedAt || live.companionId !== owner || directReplyTarget(state, c.id, route.userId) !== target || now.input !== initial.input || now.route?.id !== route.id)
25
+ throw Error('语音对象或渠道配置已变化'); if (!channelReplyAllowed(state, c.id, route.userId, target || undefined))
26
+ throw Error('当前会话许可已撤回'); if (c.platform === 'qq' && (group ? !settings.groupReplies || !settings.groupIds.includes(group.groupId) : !settings.privateReplies))
27
+ throw Error('当前会话回复已关闭'); };
28
+ guard();
29
+ if (a.action === 'status') {
30
+ if (!c.tts?.enabled)
31
+ return { enabled: false, sendFile: true, nativeQqVoice: c.platform === 'qq' && qqSettings(c.qq).replyVoiceEnabled === true };
32
+ const p = service.profile(owner, c.tts.profileId);
33
+ return { enabled: true, sendFile: true, delivery: c.platform === 'qq' ? 'voice' : 'audio-attachment', provider: p.provider, voice: c.tts.voice || p.voice, name: p.name };
34
+ }
35
+ if (a.action === 'send_file' && (typeof a.path !== 'string' || !a.path.trim() || a.path.length > 4096 || a.voice !== undefined || a.text !== undefined))
36
+ throw Error('send_file 只需 path,可选 continueTurn;音色与内容由原音频决定');
37
+ if (a.action !== 'send_file' && a.path !== undefined)
38
+ throw Error('只有 send_file 接受音频文件路径');
39
+ if (a.action === 'speak' && !c.tts?.enabled)
40
+ throw Error('渠道尚未开启语音 TTS');
41
+ if (c.platform !== 'qq' && !sendAudio)
42
+ throw Error('当前渠道音频发送未初始化');
43
+ if (a.action === 'speak' && (typeof a.text !== 'string' || !a.text.trim() || a.text.length > 2000 || a.voice !== undefined && (typeof a.voice !== 'string' || a.voice.length > 200)))
44
+ throw Error('请提供有效的语音正文和音色');
45
+ if (!a.continueTurn && typeof exec.concludeTurn !== 'function')
46
+ throw Error('当前宿主不支持语音终结');
47
+ const input = { owner, channelId: c.id, routeId: route.id, inputId: initial.input, text: a.text?.trim() ?? '', voice: a.voice, signal: exec.signal, guard, send: async (data, signal, check, file) => {
48
+ if (c.platform !== 'qq') {
49
+ await sendAudio(agent.session.id, file, signal, route.id, check);
50
+ return { confirmed: true };
51
+ }
52
+ try {
53
+ const result = await call(c.id, group ? 'send_group_msg' : 'send_private_msg', { ...(group ? { group_id: Number(group.groupId) } : { user_id: Number(route.userId) }), message: [{ type: 'record', data: { file: 'base64://' + data.toString('base64') } }] }, signal, check);
54
+ return result && ['number', 'string'].includes(typeof result.message_id) ? String(result.message_id) : '';
55
+ }
56
+ catch (e) {
57
+ if (e instanceof OneBotApiError)
58
+ throw Object.assign(Error('渠道明确拒绝发送,可复用已生成音频'), { sendRejected: true });
59
+ throw e;
60
+ }
61
+ } };
62
+ const cwd = route.cwd ?? agent.session.header?.cwd;
63
+ if (a.action === 'send_file' && !cwd)
64
+ throw Error('当前会话缺少工作目录');
65
+ const r = a.action === 'send_file' ? await service.deliverFile({ ...input, cwd: cwd, path: a.path }) : await service.deliver(input);
66
+ if (r.status !== 'confirmed')
67
+ throw Error('本次语音' + (r.status === 'unknown' ? '结果未知' : '尚未确认送达') + ';不要自动重新合成或发送,请核对服务和渠道记录');
68
+ if (!a.continueTurn)
69
+ exec.concludeTurn();
70
+ return { status: r.status, messageId: r.messageId, originRouteId: route.id, inputMessageId: initial.input, spokenText: r.spokenText, continueTurn: a.continueTurn === true };
71
+ } };
72
+ }
73
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/voice/tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PartnerState } from '../domain.js';
2
+ export declare function validateTtsState(state: Partial<PartnerState>): void;
3
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/voice/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,cAAc,CAAA;AAE9C,wBAAgB,gBAAgB,CAAC,KAAK,EAAC,OAAO,CAAC,YAAY,CAAC,GAAE,IAAI,CAcjE"}
@@ -0,0 +1,31 @@
1
+ import { channelTts, ttsProfile } from './domain.js';
2
+ export function validateTtsState(state) {
3
+ for (const c of state.companions ?? []) {
4
+ if (c.ttsProfiles === undefined)
5
+ continue;
6
+ if (!Array.isArray(c.ttsProfiles) || c.ttsProfiles.length > 20)
7
+ throw Error('invalid TTS profiles');
8
+ const ids = new Set();
9
+ for (const p of c.ttsProfiles) {
10
+ if (!p || typeof p.id !== 'string' || !/^[a-zA-Z0-9_-]{1,100}$/.test(p.id) || ids.has(p.id) || !Number.isFinite(p.updatedAt) || p.credentialId !== undefined && !/^[a-zA-Z0-9_-]{1,100}$/.test(p.credentialId))
11
+ throw Error('invalid TTS profile identity');
12
+ ids.add(p.id);
13
+ ttsProfile(p, p.id, p.updatedAt, p.credentialId);
14
+ }
15
+ }
16
+ for (const c of state.channels ?? []) {
17
+ if (c.tts)
18
+ channelTts(c.tts);
19
+ if (c.ttsReceipts === undefined)
20
+ continue;
21
+ if (!Array.isArray(c.ttsReceipts) || c.ttsReceipts.length > 500)
22
+ throw Error('invalid TTS receipts');
23
+ const ids = new Set();
24
+ for (const r of c.ttsReceipts) {
25
+ if (!r || !/^[a-f0-9]{64}$/.test(r.id) || ids.has(r.id) || !['generating', 'generated', 'sending', 'confirmed', 'failed', 'unknown'].includes(r.status) || !Number.isFinite(r.at) || typeof r.profileId !== 'string' || typeof r.inputId !== 'string' || typeof r.routeId !== 'string' || typeof r.spokenText !== 'string' || r.spokenText.length > 2000 || r.audioId !== undefined && !/^[a-f0-9]{64}\.(wav|mp3)$/.test(r.audioId))
26
+ throw Error('invalid TTS receipt');
27
+ ids.add(r.id);
28
+ }
29
+ }
30
+ }
31
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/voice/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,UAAU,EAAC,UAAU,EAAC,MAAM,aAAa,CAAA;AACjD,MAAM,UAAU,gBAAgB,CAAC,KAA2B;IAC3D,KAAI,MAAM,CAAC,IAAI,KAAK,CAAC,UAAU,IAAE,EAAE,EAAC,CAAC;QACpC,IAAG,CAAC,CAAC,WAAW,KAAG,SAAS;YAAC,SAAQ;QACrC,IAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,IAAE,CAAC,CAAC,WAAW,CAAC,MAAM,GAAC,EAAE;YAAC,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;QAC7F,MAAM,GAAG,GAAC,IAAI,GAAG,EAAU,CAAA;QAC3B,KAAI,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAC,CAAC;YAAA,IAAG,CAAC,CAAC,IAAE,OAAO,CAAC,CAAC,EAAE,KAAG,QAAQ,IAAE,CAAE,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAE,CAAC,CAAC,YAAY,KAAG,SAAS,IAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC;gBAAC,MAAM,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAAA,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAAA,UAAU,CAAC,CAAsC,EAAC,CAAC,CAAC,EAAE,EAAC,CAAC,CAAC,SAAS,EAAC,CAAC,CAAC,YAAY,CAAC,CAAA;QAAA,CAAC;IAC3W,CAAC;IACD,KAAI,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,IAAE,EAAE,EAAC,CAAC;QAClC,IAAG,CAAC,CAAC,GAAG;YAAC,UAAU,CAAC,CAAC,CAAC,GAAwC,CAAC,CAAA;QAC/D,IAAG,CAAC,CAAC,WAAW,KAAG,SAAS;YAAC,SAAQ;QACrC,IAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,IAAE,CAAC,CAAC,WAAW,CAAC,MAAM,GAAC,GAAG;YAAC,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;QAC9F,MAAM,GAAG,GAAC,IAAI,GAAG,EAAU,CAAA;QAC3B,KAAI,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAC,CAAC;YAAA,IAAG,CAAC,CAAC,IAAE,CAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAE,CAAC,CAAC,YAAY,EAAC,WAAW,EAAC,SAAS,EAAC,WAAW,EAAC,QAAQ,EAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,IAAE,OAAO,CAAC,CAAC,SAAS,KAAG,QAAQ,IAAE,OAAO,CAAC,CAAC,OAAO,KAAG,QAAQ,IAAE,OAAO,CAAC,CAAC,OAAO,KAAG,QAAQ,IAAE,OAAO,CAAC,CAAC,UAAU,KAAG,QAAQ,IAAE,CAAC,CAAC,UAAU,CAAC,MAAM,GAAC,IAAI,IAAE,CAAC,CAAC,OAAO,KAAG,SAAS,IAAE,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;gBAAC,MAAM,KAAK,CAAC,qBAAqB,CAAC,CAAC;YAAA,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAAA,CAAC;IAC7c,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemoncat7/dsh-partner",
3
- "version": "2.23.55",
3
+ "version": "2.25.0",
4
4
  "description": "Long-lived AI companions with QQ, WeChat, Matrix and Mattermost channels for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -31,6 +31,16 @@
31
31
  }
32
32
  },
33
33
  "files": [
34
+ "docs/releases/2.25.0.md",
35
+ "docs/releases/2.25.0-rc.11.md",
36
+ "docs/releases/2.25.0-rc.5.md",
37
+ "docs/releases/2.25.0-rc.4.md",
38
+ "docs/releases/2.25.0-rc.3.md",
39
+ "docs/releases/2.25.0-rc.2.md",
40
+ "docs/releases/2.25.0-rc.1.md",
41
+ "docs/releases/2.24.0-rc.11.md",
42
+ "docs/releases/2.24.0-rc.10.md",
43
+ "docs/releases/2.24.0-rc.9.md",
34
44
  "docs/releases/2.23.54.md",
35
45
  "docs/releases/2.23.53.md",
36
46
  "docs/releases/2.23.52.md",
@@ -199,7 +209,16 @@
199
209
  "docs/releases/2.23.21.md",
200
210
  "docs/releases/2.23.22.md",
201
211
  "docs/releases/2.23.23.md",
202
- "docs/releases/2.23.24.md"
212
+ "docs/releases/2.23.24.md",
213
+ "resources/bilibili/*.py",
214
+ "resources/bilibili/requirements.txt",
215
+ "resources/bilibili/Dockerfile",
216
+ "docs/releases/2.24.0-rc.2.md",
217
+ "docs/releases/2.24.0-rc.3.md",
218
+ "docs/releases/2.24.0-rc.4.md",
219
+ "docs/releases/2.24.0-rc.5.md",
220
+ "docs/releases/2.24.0-rc.6.md",
221
+ "docs/compatibility-0.2.2-rc.1.md"
203
222
  ],
204
223
  "dsh": {
205
224
  "bundle": {
@@ -219,8 +238,8 @@
219
238
  }
220
239
  },
221
240
  "engines": {
222
- "node": "^22.19.0 || >=24.0.0",
223
- "dsh": "^0.2.1-rc.2"
241
+ "node": ">=24.9.0",
242
+ "dsh": "=0.2.2-rc.1"
224
243
  },
225
244
  "scripts": {
226
245
  "clean": "node -e \"require('node:fs').rmSync('lib', { recursive: true, force: true })\"",
@@ -231,7 +250,7 @@
231
250
  "pack:verify": "node scripts/check-package.mjs"
232
251
  },
233
252
  "dependencies": {
234
- "@deepseek-ai/schemastery": "~3.18.5",
253
+ "@deepseek-ai/schemastery": "~3.19.0",
235
254
  "@modelcontextprotocol/sdk": "^1.30.0",
236
255
  "mdast-util-from-markdown": "2.0.3",
237
256
  "mdast-util-gfm": "3.1.0",
@@ -239,50 +258,52 @@
239
258
  "micromark-extension-gfm": "3.0.0"
240
259
  },
241
260
  "peerDependencies": {
242
- "@deepseek-ai/cordis": "^4.0.5",
243
- "@deepseek-ai/dsh-agent": "^0.2.1-rc.2",
244
- "@deepseek-ai/dsh-agent-default-model": "^0.2.1-rc.2",
245
- "@deepseek-ai/dsh-agent-preset-registry": "^0.2.1-rc.2",
246
- "@deepseek-ai/dsh-attachment": "^0.2.1-rc.2",
247
- "@deepseek-ai/dsh-credentials": "^0.2.1-rc.2",
248
- "@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
249
- "@deepseek-ai/dsh-scope": "^0.2.1-rc.2",
250
- "@deepseek-ai/dsh-session": "^0.2.1-rc.2",
251
- "@deepseek-ai/dsh-settings": "^0.2.1-rc.2",
252
- "@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.2",
253
- "@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
254
- "@deepseek-ai/dsh-user-questions": "^0.2.1-rc.2",
255
- "@deepseek-ai/dsh-workspace": "^0.2.1-rc.2"
261
+ "@deepseek-ai/cordis": "^4.1.0",
262
+ "@deepseek-ai/dsh-agent": "^0.2.2-rc.1",
263
+ "@deepseek-ai/dsh-agent-default-model": "^0.2.2-rc.1",
264
+ "@deepseek-ai/dsh-agent-preset-registry": "^0.2.2-rc.1",
265
+ "@deepseek-ai/dsh-attachment": "^0.2.2-rc.1",
266
+ "@deepseek-ai/dsh-credentials": "^0.2.2-rc.1",
267
+ "@deepseek-ai/dsh-llm": "^0.2.2-rc.1",
268
+ "@deepseek-ai/dsh-scope": "^0.2.2-rc.1",
269
+ "@deepseek-ai/dsh-session": "^0.2.2-rc.1",
270
+ "@deepseek-ai/dsh-settings": "^0.2.2-rc.1",
271
+ "@deepseek-ai/dsh-system-prompt": "^0.2.2-rc.1",
272
+ "@deepseek-ai/dsh-tools": "^0.2.2-rc.1",
273
+ "@deepseek-ai/dsh-user-questions": "^0.2.2-rc.1",
274
+ "@deepseek-ai/dsh-workspace": "^0.2.2-rc.1",
275
+ "@deepseek-ai/dsh-tool-executor-native": "^0.2.2-rc.1",
276
+ "@deepseek-ai/dsh-tool-executor-ptc": "^0.2.2-rc.1"
256
277
  },
257
278
  "devDependencies": {
258
- "@deepseek-ai/cordis": "~4.0.5",
259
- "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
260
- "@deepseek-ai/dsh-agent-default-model": "0.2.1-rc.2",
261
- "@deepseek-ai/dsh-agent-preset-registry": "0.2.1-rc.2",
262
- "@deepseek-ai/dsh-api-remotes": "0.2.1-rc.2",
263
- "@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.2",
264
- "@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
265
- "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
266
- "@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.2",
267
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
268
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
269
- "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.2",
270
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
271
- "@deepseek-ai/dsh-client-ui-theme": "0.2.1-rc.2",
272
- "@deepseek-ai/dsh-client-ui-workspace": "0.2.1-rc.2",
273
- "@deepseek-ai/dsh-credentials": "0.2.1-rc.2",
274
- "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
275
- "@deepseek-ai/dsh-ptc-runtime": "0.2.1-rc.2",
276
- "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
277
- "@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.2",
278
- "@deepseek-ai/dsh-scope": "0.2.1-rc.2",
279
- "@deepseek-ai/dsh-session": "0.2.1-rc.2",
280
- "@deepseek-ai/dsh-settings": "0.2.1-rc.2",
281
- "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
282
- "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
283
- "@deepseek-ai/dsh-user-approval": "0.2.1-rc.2",
284
- "@deepseek-ai/dsh-user-questions": "0.2.1-rc.2",
285
- "@deepseek-ai/dsh-workspace": "0.2.1-rc.2",
279
+ "@deepseek-ai/cordis": "~4.1.0",
280
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
281
+ "@deepseek-ai/dsh-agent-default-model": "0.2.2-rc.1",
282
+ "@deepseek-ai/dsh-agent-preset-registry": "0.2.2-rc.1",
283
+ "@deepseek-ai/dsh-api-remotes": "0.2.2-rc.1",
284
+ "@deepseek-ai/dsh-api-session-controller": "0.2.2-rc.1",
285
+ "@deepseek-ai/dsh-attachment": "0.2.2-rc.1",
286
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.2-rc.1",
287
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.2-rc.1",
288
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.2-rc.1",
289
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.2-rc.1",
290
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.2-rc.1",
291
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.2-rc.1",
292
+ "@deepseek-ai/dsh-client-ui-theme": "0.2.2-rc.1",
293
+ "@deepseek-ai/dsh-client-ui-workspace": "0.2.2-rc.1",
294
+ "@deepseek-ai/dsh-credentials": "0.2.2-rc.1",
295
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
296
+ "@deepseek-ai/dsh-ptc-runtime": "0.2.2-rc.1",
297
+ "@deepseek-ai/dsh-sandbox": "0.2.2-rc.1",
298
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.2-rc.1",
299
+ "@deepseek-ai/dsh-scope": "0.2.2-rc.1",
300
+ "@deepseek-ai/dsh-session": "0.2.2-rc.1",
301
+ "@deepseek-ai/dsh-settings": "0.2.2-rc.1",
302
+ "@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
303
+ "@deepseek-ai/dsh-tools": "0.2.2-rc.1",
304
+ "@deepseek-ai/dsh-user-approval": "0.2.2-rc.1",
305
+ "@deepseek-ai/dsh-user-questions": "0.2.2-rc.1",
306
+ "@deepseek-ai/dsh-workspace": "0.2.2-rc.1",
286
307
  "@dimforge/rapier3d-compat": "0.20.0",
287
308
  "@hugeicons/core-free-icons": "^4.3.5",
288
309
  "@hugeicons/react": "^1.1.10",
@@ -295,7 +316,9 @@
295
316
  "react-dom": "18.3.1",
296
317
  "three": "0.185.1",
297
318
  "typescript": "^5.9.3",
298
- "ws": "^8.22.0"
319
+ "ws": "^8.22.0",
320
+ "@deepseek-ai/dsh-tool-executor-native": "0.2.2-rc.1",
321
+ "@deepseek-ai/dsh-tool-executor-ptc": "0.2.2-rc.1"
299
322
  },
300
323
  "publishConfig": {
301
324
  "access": "public"
@@ -0,0 +1,7 @@
1
+ FROM python:3.11-slim
2
+ WORKDIR /app
3
+ COPY requirements.txt .
4
+ RUN pip install --no-cache-dir -r requirements.txt
5
+ COPY --chmod=644 server.py media.py .
6
+ USER 1000:1000
7
+ CMD ["python", "server.py"]
@@ -0,0 +1,245 @@
1
+ """Staged video submission. Playback APIs only return links; no downloads."""
2
+ import asyncio, json, os, re, shutil, time, uuid
3
+ from pathlib import Path
4
+ from urllib.parse import urlparse
5
+ from aiohttp import ClientSession, ClientTimeout, ClientError, web
6
+ from bilibili_api import video, video_uploader, dynamic
7
+ from bilibili_api.utils.picture import Picture
8
+ from bilibili_api.utils.network import Api, get_client, BiliAPIFile
9
+
10
+ HEADERS={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36','Referer':'https://www.bilibili.com/'}
11
+ MAX_FILE=512*1024*1024
12
+ try:
13
+ from imageio_ffmpeg import get_ffmpeg_exe
14
+ FFMPEG=get_ffmpeg_exe()
15
+ except ImportError:
16
+ FFMPEG='ffmpeg'
17
+
18
+ def media_url(url):
19
+ p=urlparse(url)
20
+ if p.scheme!='https' or p.username or p.password or p.port not in (None,443) or not any((p.hostname or '').endswith('.'+h) for h in ('bilivideo.com','bilivideo.cn','hdslb.com')):
21
+ raise ValueError('视频来源不受支持')
22
+ return url
23
+
24
+ class LocalPicture(Picture):
25
+ def __init__(self,path):
26
+ from PIL import Image
27
+ self.path=str(path)
28
+ with Image.open(path) as im:
29
+ if im.format not in ('PNG','JPEG','WEBP') or im.width*im.height>25000000:raise ValueError('图片格式或尺寸无效')
30
+ self.width,self.height=im.size;self.imageType={'PNG':'png','JPEG':'jpg','WEBP':'webp'}[im.format];self.mime=im.get_format_mimetype();im.verify()
31
+ self.content=Path(path).read_bytes();self.size=len(self.content)//1024;self.url='file://'+str(path)
32
+ def _to_biliapifile(self):
33
+ return BiliAPIFile(path=self.path,mime_type=self.mime)
34
+
35
+ class UploadRejected(ValueError):
36
+ def __init__(self, phase, status, payload=None):
37
+ payload=payload if isinstance(payload,dict) else {}
38
+ code=payload.get('code');code=code if isinstance(code,int) else None
39
+ limited=code==601 or status==429
40
+ self.details={'phase':phase,'httpStatus':status,'code':code,'rateLimited':limited}
41
+ # Fixed descriptions prevent secrets or attacker-controlled upstream text leaking.
42
+ message='平台提示上传过快,请稍作休息后再继续' if limited else '平台拒绝上传请求,请核实登录状态和创作中心提示'
43
+ super().__init__(f'{phase}失败:HTTP {status}'+(f',业务码 {code}' if code is not None else '')+';'+message)
44
+
45
+ def checked_response(response,phase):
46
+ try:data=response.json()
47
+ except Exception:data={}
48
+ if response.code>=400 or data.get('OK')==0 or isinstance(data.get('code'),int) and data['code']!=0:
49
+ raise UploadRejected(phase,response.code,data)
50
+ return data
51
+
52
+ class UploadOnly(video_uploader.VideoUploader):
53
+ async def _preupload(self,page):
54
+ # SDK 17.4.2 discards the body on HTTP errors. Keep bounded diagnostic fields.
55
+ client=get_client()
56
+ resp=await client.request(method='GET',url='https://member.bilibili.com/preupload',
57
+ params={'profile':'ugcfx/bup','name':os.path.basename(page.path),'size':page.get_size(),'r':self.line['os'],'ssl':'0','version':'2.14.0','build':'2100400','upcdn':self.line['upcdn'],'probe_version':self.line['probe_version']},
58
+ cookies=await self.credential.get_buvid_cookies(),headers=HEADERS)
59
+ data=checked_response(resp,'预上传')
60
+ if data.get('OK')!=1 or not all(k in data for k in ('auth','endpoint','upos_uri','chunk_size','biz_id')):raise ValueError('预上传返回缺少上传配置,未上传素材')
61
+ data=self._switch_upload_endpoint(data,self.line)
62
+ url=media_url(self._get_upload_url(data))
63
+ resp=await client.request(method='POST',url=url,headers={**HEADERS,'x-upos-auth':data['auth']},params={'uploads':'','output':'json','profile':'ugcfx/bup','filesize':page.get_size(),'partsize':data['chunk_size'],'biz_id':data['biz_id']})
64
+ result=checked_response(resp,'初始化分片')
65
+ if result.get('OK')!=1 or not result.get('upload_id'):raise ValueError('分片初始化未返回任务编号')
66
+ data['upload_id']=result['upload_id'];return data
67
+ async def _submit(self, videos, cover_url=''):
68
+ return {'videos':videos,'cover':cover_url}
69
+
70
+ class Media:
71
+ def __init__(self, root, credential):
72
+ self.root=root/'media';self.root.mkdir(exist_ok=True);self.credential=credential
73
+ self.tasks={};self.limit=asyncio.Semaphore(2)
74
+ for path in self.root.glob('*/*/job.json'):
75
+ try:
76
+ j=json.loads(path.read_text())
77
+ if j['status'] in ('queued','running','submitting'):
78
+ j.update(status='unknown' if j['status']=='submitting' else 'interrupted',message='服务重启,任务未自动重试;若已提交投稿请在创作中心核实');self.save(path.parent,j)
79
+ except (ValueError,KeyError,OSError):pass
80
+ def directory(self,channel,jid):
81
+ if not re.fullmatch(r'bilibili-[A-Za-z0-9-]{1,80}',channel) or not re.fullmatch(r'[a-f0-9]{32}',jid):raise ValueError('媒体任务 ID 无效')
82
+ return self.root/channel/jid
83
+ def save(self,d,j):
84
+ d.mkdir(parents=True,exist_ok=True);p=d/'job.tmp';p.write_text(json.dumps(j));os.chmod(p,0o600);os.replace(p,d/'job.json')
85
+ def load(self,channel,jid):
86
+ d=self.directory(channel,jid)
87
+ if not (d/'job.json').exists():raise ValueError('媒体任务不存在或不属于本渠道')
88
+ return d,json.loads((d/'job.json').read_text())
89
+ def cleanup(self):
90
+ for p in self.root.glob('*/*/job.json'):
91
+ if time.time()-p.stat().st_mtime>7*86400 and p.parent.name not in self.tasks:shutil.rmtree(p.parent,ignore_errors=True)
92
+ size=sum(p.stat().st_size for p in self.root.glob('*/*/*') if p.is_file())
93
+ if size>2*1024**3:raise ValueError('视频暂存空间已满,请清理旧任务后重试')
94
+ def public(self,j):
95
+ return {k:j[k] for k in ('id','operation','status','createdAt','title','message','bvid','page','size','mimeType','published','result','uid','phase','error','retryAt','dynamic_id') if k in j}
96
+ def cooldown(self,channel):
97
+ # Channel validation also prevents arbitrary filesystem reads.
98
+ self.directory(channel,'0'*32)
99
+ p=self.root/channel/'cooldown.json'
100
+ if p.exists():
101
+ data=json.loads(p.read_text())
102
+ if data.get('retryAt',0)>time.time():return data
103
+ return {}
104
+ def record_failure(self,channel,j,e):
105
+ details=getattr(e,'details',None)
106
+ j.update(status='failed',published=False,message=str(e)[:250] if isinstance(e,ValueError) else '媒体操作失败('+type(e).__name__+')',phase=j.get('phase','上传素材'))
107
+ if details:
108
+ j['error']=details;j['phase']=details['phase']
109
+ if details.get('rateLimited'):
110
+ j['retryAt']=time.time()+900
111
+ p=self.root/channel/'cooldown.json';tmp=p.with_suffix('.tmp');tmp.write_text(json.dumps({'retryAt':j['retryAt'],'reason':'平台上传限流;十五分钟为本地保护间隔,不保证届时平台已解除限制'}));os.chmod(tmp,0o600);os.replace(tmp,p)
112
+ async def stage(self,request):
113
+ channel=request.match_info['channel'];jid=uuid.uuid4().hex;d=self.directory(channel,jid);self.cleanup()
114
+ d.mkdir(parents=True,exist_ok=True)
115
+ total=0;names=set();images=request.path.startswith('/stage-images/')
116
+ try:
117
+ reader=await request.multipart()
118
+ while (field:=await reader.next()) is not None:
119
+ if (not re.fullmatch(r'image[0-8]',field.name or '') if images else field.name not in ('video','cover')) or field.name in names:raise ValueError('只接受按顺序编号的1–9张图片' if images else '只接受一个视频和一个封面')
120
+ names.add(field.name);size=0
121
+ with (d/field.name).open('wb') as out:
122
+ while chunk:=await field.read_chunk(65536):
123
+ size+=len(chunk);total+=len(chunk)
124
+ if size>(MAX_FILE if field.name=='video' else 8*1024**2):raise ValueError('每张图片上限 8 MB' if images else '视频上限 512 MB,封面上限 8 MB')
125
+ out.write(chunk)
126
+ if not size:raise ValueError('媒体文件为空')
127
+ if images:
128
+ if not names or names!={f'image{i}' for i in range(len(names))}:raise ValueError('漫画图片须为连续的 1–9 张')
129
+ from PIL import Image
130
+ for name in names:
131
+ with Image.open(d/name) as im:
132
+ if im.format not in ('PNG','JPEG','WEBP') or im.width*im.height>25000000:raise ValueError('漫画图片须为 PNG/JPEG/WebP,最多2500万像素')
133
+ im.verify()
134
+ elif names!={'video','cover'}:raise ValueError('投稿需要视频与封面文件')
135
+ j={'id':jid,'operation':'stage','status':'staged','createdAt':time.time(),'size':total,'imageCount':len(names) if images else 0};self.save(d,j)
136
+ return web.json_response({'stageId':jid})
137
+ except BaseException:
138
+ shutil.rmtree(d,ignore_errors=True);raise
139
+ async def probe(self,path):
140
+ proc=await asyncio.create_subprocess_exec(FFMPEG,'-nostdin','-v','error','-protocol_whitelist','file,pipe','-format_whitelist','mov,matroska,webm,avi,flv','-i',str(path),'-map','0:v:0','-frames:v','1','-f','null','-',stdout=asyncio.subprocess.DEVNULL,stderr=asyncio.subprocess.DEVNULL)
141
+ try:await asyncio.wait_for(proc.wait(),20)
142
+ except BaseException:proc.kill();await proc.wait();raise
143
+ if proc.returncode:raise ValueError('文件不是可读取的视频')
144
+ async def start(self,channel,op,p):
145
+ if op not in ('upload_draft','image_dynamic'):raise ValueError('不支持的上传操作')
146
+ cooling=self.cooldown(channel) if op=='upload_draft' else {}
147
+ if cooling:raise ValueError('B站上传限流冷却中,请勿立即重试;可用 upload_health 查询建议重试时间')
148
+ self.cleanup()
149
+ if any(x[0]==channel and not x[1].done() for x in self.tasks.values()):raise ValueError('本渠道已有媒体任务,请等待完成')
150
+ jid=p.get('stageId')
151
+ d=self.directory(channel,jid)
152
+ cred=self.credential(channel)
153
+ _,old=self.load(channel,jid)
154
+ if old['status']!='staged':raise ValueError('素材已使用,请查询原任务')
155
+ if op=='image_dynamic' and (not 1<=old.get('imageCount',0)<=9 or len(p.get('text',''))>2000):raise ValueError('漫画图片暂存无效或文字过长')
156
+ if op=='upload_draft':
157
+ _,old=self.load(channel,jid)
158
+ if old['status']!='staged':raise ValueError('素材已使用,请查询原任务')
159
+ await self.probe(d/'video')
160
+ from PIL import Image
161
+ with Image.open(d/'cover') as im:
162
+ if im.format not in ('PNG','JPEG','WEBP') or im.width*im.height>25000000:raise ValueError('封面需为 PNG/JPEG/WebP 图片,最多 2500 万像素')
163
+ if not str(p.get('title','')).strip() or not p.get('tags') or not isinstance(p.get('tid'),int):raise ValueError('缺少标题、标签或分区')
164
+ if p.get('copyright')==2 and not p.get('source'):raise ValueError('转载投稿需要来源')
165
+ j={'id':jid,'operation':op,'status':'queued','createdAt':time.time(),'published':False}
166
+ j.update(uid=str(cred.dedeuserid))
167
+ if op=='upload_draft':j.update(title=p['title'])
168
+ else:j.update(imageCount=old['imageCount'])
169
+ self.save(d,j)
170
+ task=asyncio.create_task(self.run(channel,d,j,p,cred) if op=='upload_draft' else self.run_images(channel,d,j,p,cred));self.tasks[jid]=(channel,task)
171
+ task.add_done_callback(lambda _:self.tasks.pop(jid,None))
172
+ return self.public(j)
173
+ async def run(self,channel,d,j,p,cred):
174
+ try:
175
+ async with self.limit:
176
+ j['status']='running';j['phase']='上传素材';self.save(d,j)
177
+ if not await cred.check_valid():raise ValueError('上传前登录失效,请重新登录')
178
+ meta={'title':p['title'],'desc':p.get('description',''),'tid':p['tid'],'tag':','.join(p['tags']),'copyright':p.get('copyright',1),'source':p.get('source',''),'no_reprint':1,'desc_format_id':0}
179
+ uploader=UploadOnly([video_uploader.VideoUploaderPage(str(d/'video'),p['title'])],meta,cred,cover=LocalPicture(d/'cover'))
180
+ uploaded=await asyncio.wait_for(uploader.start(),1800)
181
+ if not uploaded or not uploaded.get('videos'):raise ValueError('素材上传未完成')
182
+ j.update(status='draft',meta=meta,uploaded=uploaded,message='素材已上传,待投稿草稿保存在伙伴服务中;尚未提交 B 站审核,不是创作中心云草稿。')
183
+ except asyncio.CancelledError:
184
+ j.update(status='interrupted',message='任务已中断,未自动重试或投稿');raise
185
+ except Exception as e:
186
+ self.record_failure(channel,j,e)
187
+ finally:
188
+ for name in ('video','cover'):(d/name).unlink(missing_ok=True)
189
+ self.save(d,j)
190
+ async def run_images(self,channel,d,j,p,cred):
191
+ submitting=False
192
+ try:
193
+ async with self.limit:
194
+ j.update(status='running',phase='上传漫画图片');self.save(d,j)
195
+ if not await cred.check_valid():raise ValueError('登录失效,请重新扫码')
196
+ pics=[]
197
+ for index in range(j['imageCount']):
198
+ picture=LocalPicture(d/f'image{index}')
199
+ uploaded=await asyncio.wait_for(dynamic.upload_image(picture,cred),90)
200
+ image_url=uploaded.get('image_url','')
201
+ if image_url.startswith('http://'):image_url='https://'+image_url[7:]
202
+ media_url(image_url)
203
+ pics.append({'img_src':image_url,'img_width':picture.width,'img_height':picture.height})
204
+ # Same request shape as bilibili-api send_dynamic; upload first so a
205
+ # failure before this boundary is known not to have published anything.
206
+ info=dynamic.BuildDynamic.empty().add_plain_text(p.get('text',''))
207
+ data={'dyn_req':{'content':{'contents':await info.get_contents(credential=cred)},'scene':2,'meta':{'app_meta':{'from':'create.dynamic.web','mobi_app':'web'}},'pics':pics,'attach_card':None}}
208
+ if str(self.credential(channel).dedeuserid)!=str(cred.dedeuserid):raise ValueError('登录账号已变化,图片动态未发布')
209
+ j.update(status='submitting',phase='发布图片动态');self.save(d,j);submitting=True
210
+ result=await asyncio.wait_for(Api(**dynamic.API['send']['instant'],credential=cred,json_body=True).update_data(**data).update_params(csrf=cred.bili_jct).result,35)
211
+ did=str(result.get('dyn_id_str') or result.get('dynamic_id_str') or result.get('dynamic_id') or '')
212
+ if not did.isdigit() or int(did)<=0:raise ValueError('动态返回缺少编号,请核实账号动态,不要重复发布')
213
+ j.update(status='submitted',published=True,dynamic_id=did,result={'dynamic_id':did,'url':'https://t.bilibili.com/'+did},message='图片动态已提交,实际可见状态以B站为准')
214
+ except BaseException as e:
215
+ if submitting:j.update(status='unknown',message='图片动态提交结果不明,请查看账号动态,禁止自动重发')
216
+ elif isinstance(e,asyncio.CancelledError):j.update(status='interrupted',message='图片上传已中断,尚未发布')
217
+ else:self.record_failure(channel,j,e)
218
+ if isinstance(e,asyncio.CancelledError):raise
219
+ finally:
220
+ for file in d.glob('image[0-8]'):file.unlink(missing_ok=True)
221
+ self.save(d,j)
222
+
223
+ async def publish(self,channel,p):
224
+ d,j=self.load(channel,p['draft_id']);cred=self.credential(channel)
225
+ if j.get('uid')!=str(cred.dedeuserid):raise ValueError('草稿不属于当前登录账号')
226
+ if j['status']!='draft':raise ValueError('草稿未就绪或已提交,请查询原任务,不重复投稿')
227
+ if time.time()-j['createdAt']>7*86400:raise ValueError('草稿已过期,请重新上传')
228
+ j['status']='submitting';self.save(d,j)
229
+ try:
230
+ meta={**j['meta'],**j['uploaded'],'csrf':cred.bili_jct}
231
+ r=await Api(url='https://member.bilibili.com/x/vu/web/add/v3',method='POST',verify=True,credential=cred,no_csrf=True,json_body=True).update_params(csrf=cred.bili_jct,t=time.time()*1000).update_data(**meta).result
232
+ if not r or not r.get('bvid'):raise ValueError('投稿返回缺少 BV 号,请在创作中心核实')
233
+ j.update(status='submitted',published=True,result={'bvid':r['bvid'],'aid':str(r.get('aid',''))},message='投稿已提交,实际公开状态以 B 站审核为准')
234
+ except BaseException:
235
+ j.update(status='unknown',message='投稿结果不明,请在创作中心核实,不自动重发');raise
236
+ finally:self.save(d,j)
237
+ return self.public(j)
238
+ async def cancel(self,channel):
239
+ tasks=[t for owner,t in self.tasks.values() if owner==channel]
240
+ for t in tasks:t.cancel()
241
+ await asyncio.gather(*tasks,return_exceptions=True)
242
+ async def close(self,app):
243
+ tasks=[t for _,t in self.tasks.values()]
244
+ for t in tasks:t.cancel()
245
+ await asyncio.gather(*tasks,return_exceptions=True)
@@ -0,0 +1,3 @@
1
+ bilibili-api-python==17.4.2
2
+ aiohttp==3.14.5
3
+ imageio-ffmpeg==0.6.0