@jackwener/opencli 1.5.4 → 1.5.6

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 (256) hide show
  1. package/README.md +27 -2
  2. package/README.zh-CN.md +36 -4
  3. package/dist/browser/daemon-client.d.ts +5 -1
  4. package/dist/browser/page.d.ts +6 -0
  5. package/dist/browser/page.js +15 -0
  6. package/dist/cli-manifest.json +1284 -67
  7. package/dist/cli.js +14 -14
  8. package/dist/clis/antigravity/serve.js +2 -2
  9. package/dist/clis/band/bands.d.ts +1 -0
  10. package/dist/clis/band/bands.js +72 -0
  11. package/dist/clis/band/mentions.d.ts +1 -0
  12. package/dist/clis/band/mentions.js +127 -0
  13. package/dist/clis/band/post.d.ts +1 -0
  14. package/dist/clis/band/post.js +175 -0
  15. package/dist/clis/band/posts.d.ts +1 -0
  16. package/dist/clis/band/posts.js +94 -0
  17. package/dist/clis/doubao/detail.d.ts +1 -0
  18. package/dist/clis/doubao/detail.js +33 -0
  19. package/dist/clis/doubao/detail.test.d.ts +1 -0
  20. package/dist/clis/doubao/detail.test.js +42 -0
  21. package/dist/clis/doubao/history.d.ts +1 -0
  22. package/dist/clis/doubao/history.js +28 -0
  23. package/dist/clis/doubao/history.test.d.ts +1 -0
  24. package/dist/clis/doubao/history.test.js +37 -0
  25. package/dist/clis/doubao/meeting-summary.d.ts +1 -0
  26. package/dist/clis/doubao/meeting-summary.js +39 -0
  27. package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
  28. package/dist/clis/doubao/meeting-transcript.js +36 -0
  29. package/dist/clis/doubao/utils.d.ts +27 -0
  30. package/dist/clis/doubao/utils.js +317 -0
  31. package/dist/clis/doubao/utils.test.d.ts +1 -0
  32. package/dist/clis/doubao/utils.test.js +24 -0
  33. package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
  34. package/dist/clis/douyin/_shared/public-api.js +29 -0
  35. package/dist/clis/douyin/user-videos.d.ts +5 -0
  36. package/dist/clis/douyin/user-videos.js +74 -0
  37. package/dist/clis/douyin/user-videos.test.d.ts +1 -0
  38. package/dist/clis/douyin/user-videos.test.js +108 -0
  39. package/dist/clis/ones/common.d.ts +32 -0
  40. package/dist/clis/ones/common.js +144 -0
  41. package/dist/clis/ones/enrich-tasks.d.ts +5 -0
  42. package/dist/clis/ones/enrich-tasks.js +37 -0
  43. package/dist/clis/ones/login.d.ts +1 -0
  44. package/dist/clis/ones/login.js +80 -0
  45. package/dist/clis/ones/logout.d.ts +1 -0
  46. package/dist/clis/ones/logout.js +17 -0
  47. package/dist/clis/ones/me.d.ts +1 -0
  48. package/dist/clis/ones/me.js +30 -0
  49. package/dist/clis/ones/my-tasks.d.ts +1 -0
  50. package/dist/clis/ones/my-tasks.js +120 -0
  51. package/dist/clis/ones/resolve-labels.d.ts +10 -0
  52. package/dist/clis/ones/resolve-labels.js +64 -0
  53. package/dist/clis/ones/task-helpers.d.ts +29 -0
  54. package/dist/clis/ones/task-helpers.js +212 -0
  55. package/dist/clis/ones/task-helpers.test.d.ts +1 -0
  56. package/dist/clis/ones/task-helpers.test.js +12 -0
  57. package/dist/clis/ones/task.d.ts +1 -0
  58. package/dist/clis/ones/task.js +66 -0
  59. package/dist/clis/ones/tasks.d.ts +1 -0
  60. package/dist/clis/ones/tasks.js +79 -0
  61. package/dist/clis/ones/token-info.d.ts +1 -0
  62. package/dist/clis/ones/token-info.js +42 -0
  63. package/dist/clis/ones/worklog.d.ts +11 -0
  64. package/dist/clis/ones/worklog.js +267 -0
  65. package/dist/clis/ones/worklog.test.d.ts +1 -0
  66. package/dist/clis/ones/worklog.test.js +20 -0
  67. package/dist/clis/sinafinance/rolling-news.d.ts +4 -0
  68. package/dist/clis/sinafinance/rolling-news.js +40 -0
  69. package/dist/clis/sinafinance/stock.d.ts +8 -0
  70. package/dist/clis/sinafinance/stock.js +117 -0
  71. package/dist/clis/spotify/spotify.d.ts +1 -0
  72. package/dist/clis/spotify/spotify.js +316 -0
  73. package/dist/clis/spotify/utils.d.ts +21 -0
  74. package/dist/clis/spotify/utils.js +66 -0
  75. package/dist/clis/spotify/utils.test.d.ts +1 -0
  76. package/dist/clis/spotify/utils.test.js +67 -0
  77. package/dist/clis/tieba/commands.test.d.ts +4 -0
  78. package/dist/clis/tieba/commands.test.js +79 -0
  79. package/dist/clis/tieba/hot.d.ts +1 -0
  80. package/dist/clis/tieba/hot.js +48 -0
  81. package/dist/clis/tieba/posts.d.ts +1 -0
  82. package/dist/clis/tieba/posts.js +85 -0
  83. package/dist/clis/tieba/read.d.ts +1 -0
  84. package/dist/clis/tieba/read.js +140 -0
  85. package/dist/clis/tieba/search.d.ts +1 -0
  86. package/dist/clis/tieba/search.js +108 -0
  87. package/dist/clis/tieba/utils.d.ts +101 -0
  88. package/dist/clis/tieba/utils.js +240 -0
  89. package/dist/clis/tieba/utils.test.d.ts +1 -0
  90. package/dist/clis/tieba/utils.test.js +290 -0
  91. package/dist/clis/weread/book.js +100 -13
  92. package/dist/clis/weread/commands.test.js +221 -0
  93. package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
  94. package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
  95. package/dist/clis/weread/search-regression.test.d.ts +1 -0
  96. package/dist/clis/weread/search-regression.test.js +407 -0
  97. package/dist/clis/weread/search.js +143 -7
  98. package/dist/clis/weread/shelf.js +13 -95
  99. package/dist/clis/weread/utils.d.ts +46 -0
  100. package/dist/clis/weread/utils.js +214 -7
  101. package/dist/clis/weread/utils.test.js +71 -1
  102. package/dist/clis/xiaohongshu/publish.d.ts +1 -1
  103. package/dist/clis/xiaohongshu/publish.js +78 -31
  104. package/dist/clis/xiaohongshu/publish.test.js +66 -1
  105. package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
  106. package/dist/clis/xiaohongshu/user-helpers.js +2 -0
  107. package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
  108. package/dist/clis/xueqiu/comments.d.ts +118 -0
  109. package/dist/clis/xueqiu/comments.js +354 -0
  110. package/dist/clis/xueqiu/comments.test.d.ts +1 -0
  111. package/dist/clis/xueqiu/comments.test.js +696 -0
  112. package/dist/clis/youtube/transcript.js +2 -4
  113. package/dist/clis/youtube/utils.d.ts +9 -0
  114. package/dist/clis/youtube/utils.js +67 -3
  115. package/dist/clis/youtube/utils.test.d.ts +1 -0
  116. package/dist/clis/youtube/utils.test.js +37 -0
  117. package/dist/clis/youtube/video.js +16 -15
  118. package/dist/clis/zsxq/dynamics.d.ts +1 -0
  119. package/dist/clis/zsxq/dynamics.js +47 -0
  120. package/dist/clis/zsxq/groups.d.ts +1 -0
  121. package/dist/clis/zsxq/groups.js +32 -0
  122. package/dist/clis/zsxq/search.d.ts +1 -0
  123. package/dist/clis/zsxq/search.js +43 -0
  124. package/dist/clis/zsxq/search.test.d.ts +1 -0
  125. package/dist/clis/zsxq/search.test.js +24 -0
  126. package/dist/clis/zsxq/topic.d.ts +1 -0
  127. package/dist/clis/zsxq/topic.js +47 -0
  128. package/dist/clis/zsxq/topic.test.d.ts +1 -0
  129. package/dist/clis/zsxq/topic.test.js +29 -0
  130. package/dist/clis/zsxq/topics.d.ts +1 -0
  131. package/dist/clis/zsxq/topics.js +25 -0
  132. package/dist/clis/zsxq/topics.test.d.ts +1 -0
  133. package/dist/clis/zsxq/topics.test.js +24 -0
  134. package/dist/clis/zsxq/utils.d.ts +97 -0
  135. package/dist/clis/zsxq/utils.js +230 -0
  136. package/dist/commanderAdapter.js +27 -4
  137. package/dist/commanderAdapter.test.js +39 -0
  138. package/dist/daemon.js +5 -4
  139. package/dist/errors.d.ts +29 -1
  140. package/dist/errors.js +49 -11
  141. package/dist/external-clis.yaml +17 -0
  142. package/dist/external.js +3 -3
  143. package/dist/main.js +2 -1
  144. package/dist/tui.js +2 -1
  145. package/dist/types.d.ts +5 -0
  146. package/docs/.vitepress/config.mts +3 -0
  147. package/docs/adapters/browser/band.md +63 -0
  148. package/docs/adapters/browser/ones.md +59 -0
  149. package/docs/adapters/browser/sinafinance.md +56 -6
  150. package/docs/adapters/browser/spotify.md +62 -0
  151. package/docs/adapters/browser/tieba.md +45 -0
  152. package/docs/adapters/browser/xueqiu.md +5 -0
  153. package/docs/adapters/browser/zsxq.md +49 -0
  154. package/docs/adapters/index.md +5 -2
  155. package/docs/adapters-doc/ones.md +32 -0
  156. package/extension/dist/background.js +1 -2
  157. package/extension/manifest.json +1 -1
  158. package/extension/package.json +1 -1
  159. package/extension/src/background.ts +17 -1
  160. package/extension/src/cdp.ts +42 -0
  161. package/extension/src/protocol.ts +5 -1
  162. package/package.json +1 -1
  163. package/scripts/postinstall.js +16 -0
  164. package/src/browser/daemon-client.ts +5 -1
  165. package/src/browser/page.ts +16 -0
  166. package/src/cli.ts +14 -14
  167. package/src/clis/antigravity/serve.ts +2 -2
  168. package/src/clis/band/bands.ts +76 -0
  169. package/src/clis/band/mentions.ts +134 -0
  170. package/src/clis/band/post.ts +187 -0
  171. package/src/clis/band/posts.ts +106 -0
  172. package/src/clis/doubao/detail.test.ts +53 -0
  173. package/src/clis/doubao/detail.ts +41 -0
  174. package/src/clis/doubao/history.test.ts +45 -0
  175. package/src/clis/doubao/history.ts +32 -0
  176. package/src/clis/doubao/meeting-summary.ts +53 -0
  177. package/src/clis/doubao/meeting-transcript.ts +48 -0
  178. package/src/clis/doubao/utils.test.ts +45 -0
  179. package/src/clis/doubao/utils.ts +371 -0
  180. package/src/clis/douyin/_shared/public-api.ts +84 -0
  181. package/src/clis/douyin/user-videos.test.ts +122 -0
  182. package/src/clis/douyin/user-videos.ts +101 -0
  183. package/src/clis/ones/common.ts +187 -0
  184. package/src/clis/ones/enrich-tasks.ts +47 -0
  185. package/src/clis/ones/login.ts +103 -0
  186. package/src/clis/ones/logout.ts +19 -0
  187. package/src/clis/ones/me.ts +34 -0
  188. package/src/clis/ones/my-tasks.ts +148 -0
  189. package/src/clis/ones/resolve-labels.ts +80 -0
  190. package/src/clis/ones/task-helpers.test.ts +14 -0
  191. package/src/clis/ones/task-helpers.ts +214 -0
  192. package/src/clis/ones/task.ts +79 -0
  193. package/src/clis/ones/tasks.ts +92 -0
  194. package/src/clis/ones/token-info.ts +46 -0
  195. package/src/clis/ones/worklog.test.ts +24 -0
  196. package/src/clis/ones/worklog.ts +306 -0
  197. package/src/clis/sinafinance/rolling-news.ts +42 -0
  198. package/src/clis/sinafinance/stock.ts +127 -0
  199. package/src/clis/spotify/spotify.ts +328 -0
  200. package/src/clis/spotify/utils.test.ts +87 -0
  201. package/src/clis/spotify/utils.ts +92 -0
  202. package/src/clis/tieba/commands.test.ts +86 -0
  203. package/src/clis/tieba/hot.ts +52 -0
  204. package/src/clis/tieba/posts.ts +108 -0
  205. package/src/clis/tieba/read.ts +158 -0
  206. package/src/clis/tieba/search.ts +119 -0
  207. package/src/clis/tieba/utils.test.ts +322 -0
  208. package/src/clis/tieba/utils.ts +348 -0
  209. package/src/clis/weread/book.ts +116 -13
  210. package/src/clis/weread/commands.test.ts +249 -0
  211. package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
  212. package/src/clis/weread/search-regression.test.ts +440 -0
  213. package/src/clis/weread/search.ts +189 -9
  214. package/src/clis/weread/shelf.ts +20 -122
  215. package/src/clis/weread/utils.test.ts +81 -1
  216. package/src/clis/weread/utils.ts +264 -7
  217. package/src/clis/xiaohongshu/publish.test.ts +79 -1
  218. package/src/clis/xiaohongshu/publish.ts +84 -30
  219. package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
  220. package/src/clis/xiaohongshu/user-helpers.ts +4 -0
  221. package/src/clis/xueqiu/comments.test.ts +823 -0
  222. package/src/clis/xueqiu/comments.ts +461 -0
  223. package/src/clis/youtube/transcript.ts +2 -4
  224. package/src/clis/youtube/utils.test.ts +43 -0
  225. package/src/clis/youtube/utils.ts +69 -0
  226. package/src/clis/youtube/video.ts +16 -15
  227. package/src/clis/zsxq/dynamics.ts +60 -0
  228. package/src/clis/zsxq/groups.ts +41 -0
  229. package/src/clis/zsxq/search.test.ts +29 -0
  230. package/src/clis/zsxq/search.ts +54 -0
  231. package/src/clis/zsxq/topic.test.ts +34 -0
  232. package/src/clis/zsxq/topic.ts +68 -0
  233. package/src/clis/zsxq/topics.test.ts +29 -0
  234. package/src/clis/zsxq/topics.ts +36 -0
  235. package/src/clis/zsxq/utils.ts +351 -0
  236. package/src/commanderAdapter.test.ts +47 -0
  237. package/src/commanderAdapter.ts +26 -3
  238. package/src/daemon.ts +5 -4
  239. package/src/errors.ts +71 -10
  240. package/src/external-clis.yaml +17 -0
  241. package/src/external.ts +3 -3
  242. package/src/main.ts +2 -1
  243. package/src/tui.ts +2 -1
  244. package/src/types.ts +5 -0
  245. package/tests/e2e/band-auth.test.ts +20 -0
  246. package/tests/e2e/browser-auth-helpers.ts +18 -0
  247. package/tests/e2e/browser-auth.test.ts +35 -47
  248. package/tests/e2e/browser-public.test.ts +288 -0
  249. package/tests/e2e/management.test.ts +1 -1
  250. package/tests/e2e/plugin-management.test.ts +1 -1
  251. package/vitest.config.ts +1 -0
  252. package/SKILL.md +0 -879
  253. package/dist/weread-private-api-regression.test.d.ts +0 -1
  254. package/dist/weread-search-regression.test.d.ts +0 -1
  255. package/dist/weread-search-regression.test.js +0 -39
  256. package/src/weread-search-regression.test.ts +0 -44
@@ -0,0 +1,42 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const { mockGetConversationDetail } = vi.hoisted(() => ({
3
+ mockGetConversationDetail: vi.fn(),
4
+ }));
5
+ vi.mock('./utils.js', async () => {
6
+ const actual = await vi.importActual('./utils.js');
7
+ return {
8
+ ...actual,
9
+ getConversationDetail: mockGetConversationDetail,
10
+ };
11
+ });
12
+ import { getRegistry } from '../../registry.js';
13
+ import './detail.js';
14
+ describe('doubao detail', () => {
15
+ const detail = getRegistry().get('doubao/detail');
16
+ beforeEach(() => {
17
+ mockGetConversationDetail.mockReset();
18
+ });
19
+ it('returns meeting metadata even when the conversation has no chat messages', async () => {
20
+ mockGetConversationDetail.mockResolvedValue({
21
+ messages: [],
22
+ meeting: {
23
+ title: 'Weekly Sync',
24
+ time: '2026-03-28 10:00',
25
+ },
26
+ });
27
+ const result = await detail.func({}, { id: '1234567890' });
28
+ expect(result).toEqual([
29
+ { Role: 'Meeting', Text: 'Weekly Sync (2026-03-28 10:00)' },
30
+ ]);
31
+ });
32
+ it('still returns an error row for a truly empty conversation', async () => {
33
+ mockGetConversationDetail.mockResolvedValue({
34
+ messages: [],
35
+ meeting: null,
36
+ });
37
+ const result = await detail.func({}, { id: '1234567890' });
38
+ expect(result).toEqual([
39
+ { Role: 'System', Text: 'No messages found. Verify the conversation ID.' },
40
+ ]);
41
+ });
42
+ });
@@ -0,0 +1 @@
1
+ export declare const historyCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,28 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { DOUBAO_DOMAIN, getDoubaoConversationList } from './utils.js';
3
+ export const historyCommand = cli({
4
+ site: 'doubao',
5
+ name: 'history',
6
+ description: 'List conversation history from Doubao sidebar',
7
+ domain: DOUBAO_DOMAIN,
8
+ strategy: Strategy.COOKIE,
9
+ browser: true,
10
+ navigateBefore: false,
11
+ args: [
12
+ { name: 'limit', required: false, help: 'Max number of conversations to show', default: '50' },
13
+ ],
14
+ columns: ['Index', 'Id', 'Title', 'Url'],
15
+ func: async (page, kwargs) => {
16
+ const limit = parseInt(kwargs.limit, 10) || 50;
17
+ const conversations = await getDoubaoConversationList(page);
18
+ if (conversations.length === 0) {
19
+ return [{ Index: 0, Id: '', Title: 'No conversation history found. Make sure you are logged in.', Url: '' }];
20
+ }
21
+ return conversations.slice(0, limit).map((conv, i) => ({
22
+ Index: i + 1,
23
+ Id: conv.Id,
24
+ Title: conv.Title,
25
+ Url: conv.Url,
26
+ }));
27
+ },
28
+ });
@@ -0,0 +1 @@
1
+ import './history.js';
@@ -0,0 +1,37 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const { mockGetDoubaoConversationList } = vi.hoisted(() => ({
3
+ mockGetDoubaoConversationList: vi.fn(),
4
+ }));
5
+ vi.mock('./utils.js', async () => {
6
+ const actual = await vi.importActual('./utils.js');
7
+ return {
8
+ ...actual,
9
+ getDoubaoConversationList: mockGetDoubaoConversationList,
10
+ };
11
+ });
12
+ import { getRegistry } from '../../registry.js';
13
+ import './history.js';
14
+ describe('doubao history', () => {
15
+ const history = getRegistry().get('doubao/history');
16
+ beforeEach(() => {
17
+ mockGetDoubaoConversationList.mockReset();
18
+ });
19
+ it('includes the conversation id in the tabular output', async () => {
20
+ mockGetDoubaoConversationList.mockResolvedValue([
21
+ {
22
+ Id: '1234567890123',
23
+ Title: 'Weekly Sync',
24
+ Url: 'https://www.doubao.com/chat/1234567890123',
25
+ },
26
+ ]);
27
+ const result = await history.func({}, {});
28
+ expect(result).toEqual([
29
+ {
30
+ Index: 1,
31
+ Id: '1234567890123',
32
+ Title: 'Weekly Sync',
33
+ Url: 'https://www.doubao.com/chat/1234567890123',
34
+ },
35
+ ]);
36
+ });
37
+ });
@@ -0,0 +1 @@
1
+ export declare const meetingSummaryCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,39 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { DOUBAO_DOMAIN, openMeetingPanel, getMeetingSummary, getMeetingChapters, parseDoubaoConversationId, } from './utils.js';
3
+ export const meetingSummaryCommand = cli({
4
+ site: 'doubao',
5
+ name: 'meeting-summary',
6
+ description: 'Get meeting summary and chapters from a Doubao conversation',
7
+ domain: DOUBAO_DOMAIN,
8
+ strategy: Strategy.COOKIE,
9
+ browser: true,
10
+ navigateBefore: false,
11
+ args: [
12
+ { name: 'id', required: true, positional: true, help: 'Conversation ID (numeric or full URL)' },
13
+ { name: 'chapters', required: false, help: 'Also include AI chapters', default: 'false' },
14
+ ],
15
+ columns: ['Section', 'Content'],
16
+ func: async (page, kwargs) => {
17
+ const conversationId = parseDoubaoConversationId(kwargs.id);
18
+ const includeChapters = kwargs.chapters === 'true' || kwargs.chapters === true;
19
+ const opened = await openMeetingPanel(page, conversationId);
20
+ if (!opened) {
21
+ return [{ Section: 'Error', Content: 'No meeting card found in this conversation.' }];
22
+ }
23
+ const summary = await getMeetingSummary(page);
24
+ const result = [];
25
+ if (summary) {
26
+ result.push({ Section: 'Summary', Content: summary });
27
+ }
28
+ if (includeChapters) {
29
+ const chapters = await getMeetingChapters(page);
30
+ if (chapters) {
31
+ result.push({ Section: 'Chapters', Content: chapters });
32
+ }
33
+ }
34
+ if (result.length === 0) {
35
+ return [{ Section: 'Info', Content: 'Meeting panel opened but no content found yet. Try again.' }];
36
+ }
37
+ return result;
38
+ },
39
+ });
@@ -0,0 +1 @@
1
+ export declare const meetingTranscriptCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,36 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { DOUBAO_DOMAIN, openMeetingPanel, getMeetingTranscript, parseDoubaoConversationId, triggerTranscriptDownload, } from './utils.js';
3
+ export const meetingTranscriptCommand = cli({
4
+ site: 'doubao',
5
+ name: 'meeting-transcript',
6
+ description: 'Get or download the meeting transcript from a Doubao conversation',
7
+ domain: DOUBAO_DOMAIN,
8
+ strategy: Strategy.COOKIE,
9
+ browser: true,
10
+ navigateBefore: false,
11
+ args: [
12
+ { name: 'id', required: true, positional: true, help: 'Conversation ID (numeric or full URL)' },
13
+ { name: 'download', required: false, help: 'Trigger browser file download instead of reading text', default: 'false' },
14
+ ],
15
+ columns: ['Section', 'Content'],
16
+ func: async (page, kwargs) => {
17
+ const conversationId = parseDoubaoConversationId(kwargs.id);
18
+ const shouldDownload = kwargs.download === 'true' || kwargs.download === true;
19
+ const opened = await openMeetingPanel(page, conversationId);
20
+ if (!opened) {
21
+ return [{ Section: 'Error', Content: 'No meeting card found in this conversation.' }];
22
+ }
23
+ if (shouldDownload) {
24
+ const ok = await triggerTranscriptDownload(page);
25
+ if (!ok) {
26
+ return [{ Section: 'Error', Content: 'Failed to trigger transcript download.' }];
27
+ }
28
+ return [{ Section: 'Download', Content: 'Transcript download triggered in browser. Check your Downloads folder.' }];
29
+ }
30
+ const transcript = await getMeetingTranscript(page);
31
+ if (!transcript) {
32
+ return [{ Section: 'Info', Content: 'No transcript content found. The meeting may not have a text record.' }];
33
+ }
34
+ return [{ Section: 'Transcript', Content: transcript }];
35
+ },
36
+ });
@@ -2,6 +2,11 @@ import type { IPage } from '../../types.js';
2
2
  export declare const DOUBAO_DOMAIN = "www.doubao.com";
3
3
  export declare const DOUBAO_CHAT_URL = "https://www.doubao.com/chat";
4
4
  export declare const DOUBAO_NEW_CHAT_URL = "https://www.doubao.com/chat/new-thread/create-by-msg";
5
+ export interface DoubaoConversation {
6
+ Id: string;
7
+ Title: string;
8
+ Url: string;
9
+ }
5
10
  export interface DoubaoTurn {
6
11
  Role: 'User' | 'Assistant' | 'System';
7
12
  Text: string;
@@ -20,4 +25,26 @@ export declare function getDoubaoVisibleTurns(page: IPage): Promise<DoubaoTurn[]
20
25
  export declare function getDoubaoTranscriptLines(page: IPage): Promise<string[]>;
21
26
  export declare function sendDoubaoMessage(page: IPage, text: string): Promise<'button' | 'enter'>;
22
27
  export declare function waitForDoubaoResponse(page: IPage, beforeLines: string[], beforeTurns: DoubaoTurn[], promptText: string, timeoutSeconds: number): Promise<string>;
28
+ export declare function getDoubaoConversationList(page: IPage): Promise<DoubaoConversation[]>;
29
+ export interface DoubaoMessage {
30
+ Role: 'User' | 'Assistant' | 'System';
31
+ Text: string;
32
+ HasMeetingCard: boolean;
33
+ }
34
+ export interface DoubaoMeetingInfo {
35
+ title: string;
36
+ time: string;
37
+ }
38
+ export declare function parseDoubaoConversationId(input: string): string;
39
+ export declare function navigateToConversation(page: IPage, conversationId: string): Promise<void>;
40
+ export declare function getConversationDetail(page: IPage, conversationId: string): Promise<{
41
+ messages: DoubaoMessage[];
42
+ meeting: DoubaoMeetingInfo | null;
43
+ }>;
44
+ export declare function mergeTranscriptSnapshots(existing: string, incoming: string): string;
45
+ export declare function openMeetingPanel(page: IPage, conversationId: string): Promise<boolean>;
46
+ export declare function getMeetingSummary(page: IPage): Promise<string>;
47
+ export declare function getMeetingChapters(page: IPage): Promise<string>;
48
+ export declare function getMeetingTranscript(page: IPage): Promise<string>;
49
+ export declare function triggerTranscriptDownload(page: IPage): Promise<boolean>;
23
50
  export declare function startNewDoubaoChat(page: IPage): Promise<string>;
@@ -551,6 +551,323 @@ export async function waitForDoubaoResponse(page, beforeLines, beforeTurns, prom
551
551
  }
552
552
  return lastCandidate;
553
553
  }
554
+ function getConversationListScript() {
555
+ return `
556
+ (() => {
557
+ const sidebar = document.querySelector('[data-testid="flow_chat_sidebar"]');
558
+ if (!sidebar) return [];
559
+
560
+ const items = Array.from(
561
+ sidebar.querySelectorAll('a[data-testid="chat_list_thread_item"]')
562
+ );
563
+
564
+ return items
565
+ .map(a => {
566
+ const href = a.getAttribute('href') || '';
567
+ const match = href.match(/\\/chat\\/(\\d{10,})/);
568
+ if (!match) return null;
569
+ const id = match[1];
570
+ const textContent = (a.textContent || a.innerText || '').trim();
571
+ const title = textContent
572
+ .replace(/\\s+/g, ' ')
573
+ .substring(0, 200);
574
+ return { id, title, href };
575
+ })
576
+ .filter(Boolean);
577
+ })()
578
+ `;
579
+ }
580
+ export async function getDoubaoConversationList(page) {
581
+ await ensureDoubaoChatPage(page);
582
+ const raw = await page.evaluate(getConversationListScript());
583
+ if (!Array.isArray(raw))
584
+ return [];
585
+ return raw.map((item) => ({
586
+ Id: item.id,
587
+ Title: item.title,
588
+ Url: `${DOUBAO_CHAT_URL}/${item.id}`,
589
+ }));
590
+ }
591
+ export function parseDoubaoConversationId(input) {
592
+ const match = input.match(/(\d{10,})/);
593
+ return match ? match[1] : input;
594
+ }
595
+ function getConversationDetailScript() {
596
+ return `
597
+ (() => {
598
+ const clean = (v) => (v || '').replace(/\\u00a0/g, ' ').replace(/\\n{3,}/g, '\\n\\n').trim();
599
+
600
+ const messageList = document.querySelector('[data-testid="message-list"]');
601
+ if (!messageList) return { messages: [], meeting: null };
602
+
603
+ const meetingCard = messageList.querySelector('[data-testid="meeting-minutes-card"]');
604
+ let meeting = null;
605
+ if (meetingCard) {
606
+ const raw = clean(meetingCard.textContent || '');
607
+ const match = raw.match(/^(.+?)(?:会议时间:|\\s*$)(.*)/);
608
+ meeting = {
609
+ title: match ? match[1].trim() : raw,
610
+ time: match && match[2] ? match[2].trim() : '',
611
+ };
612
+ }
613
+
614
+ const unions = Array.from(messageList.querySelectorAll('[data-testid="union_message"]'));
615
+ const messages = unions.map(u => {
616
+ const isSend = !!u.querySelector('[data-testid="send_message"]');
617
+ const isReceive = !!u.querySelector('[data-testid="receive_message"]');
618
+ const textEl = u.querySelector('[data-testid="message_text_content"]');
619
+ const text = textEl ? clean(textEl.innerText || textEl.textContent || '') : '';
620
+ return {
621
+ role: isSend ? 'User' : isReceive ? 'Assistant' : 'System',
622
+ text,
623
+ hasMeetingCard: !!u.querySelector('[data-testid="meeting-minutes-card"]'),
624
+ };
625
+ }).filter(m => m.text);
626
+
627
+ return { messages, meeting };
628
+ })()
629
+ `;
630
+ }
631
+ export async function navigateToConversation(page, conversationId) {
632
+ const url = `${DOUBAO_CHAT_URL}/${conversationId}`;
633
+ const currentUrl = await page.evaluate('window.location.href').catch(() => '');
634
+ if (typeof currentUrl === 'string' && currentUrl.includes(`/chat/${conversationId}`)) {
635
+ await page.wait(1);
636
+ return;
637
+ }
638
+ await page.goto(url, { waitUntil: 'load', settleMs: 3000 });
639
+ await page.wait(2);
640
+ }
641
+ export async function getConversationDetail(page, conversationId) {
642
+ await navigateToConversation(page, conversationId);
643
+ const raw = await page.evaluate(getConversationDetailScript());
644
+ const messages = (raw.messages || []).map((m) => ({
645
+ Role: m.role,
646
+ Text: m.text,
647
+ HasMeetingCard: m.hasMeetingCard,
648
+ }));
649
+ return { messages, meeting: raw.meeting };
650
+ }
651
+ // ---------------------------------------------------------------------------
652
+ // Meeting minutes panel helpers
653
+ // ---------------------------------------------------------------------------
654
+ function clickMeetingCardScript() {
655
+ return `
656
+ (() => {
657
+ const card = document.querySelector('[data-testid="meeting-minutes-card"]');
658
+ if (!card) return false;
659
+ card.click();
660
+ return true;
661
+ })()
662
+ `;
663
+ }
664
+ function readMeetingSummaryScript() {
665
+ return `
666
+ (() => {
667
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
668
+ if (!panel) return { error: 'no panel' };
669
+
670
+ const summary = panel.querySelector('[data-testid="meeting-summary-todos"]');
671
+ const summaryText = summary
672
+ ? (summary.innerText || summary.textContent || '').trim()
673
+ : '';
674
+
675
+ return { summary: summaryText };
676
+ })()
677
+ `;
678
+ }
679
+ function clickTextNotesTabScript() {
680
+ return `
681
+ (() => {
682
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
683
+ if (!panel) return false;
684
+ const tabs = panel.querySelectorAll('[role="tab"], .semi-tabs-tab');
685
+ for (const tab of tabs) {
686
+ if ((tab.textContent || '').trim().includes('文字')) {
687
+ tab.click();
688
+ return true;
689
+ }
690
+ }
691
+ return false;
692
+ })()
693
+ `;
694
+ }
695
+ function readTextNotesScript() {
696
+ return `
697
+ (() => {
698
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
699
+ if (!panel) return '';
700
+ const textNotes = panel.querySelector('[data-testid="meeting-text-notes"]');
701
+ if (!textNotes) return '';
702
+ return (textNotes.innerText || textNotes.textContent || '').trim();
703
+ })()
704
+ `;
705
+ }
706
+ function normalizeTranscriptLines(text) {
707
+ return text
708
+ .split('\n')
709
+ .map(line => line.trim())
710
+ .filter(Boolean);
711
+ }
712
+ function containsLineSequence(haystack, needle) {
713
+ if (needle.length === 0)
714
+ return true;
715
+ if (needle.length > haystack.length)
716
+ return false;
717
+ for (let start = 0; start <= haystack.length - needle.length; start += 1) {
718
+ let matched = true;
719
+ for (let offset = 0; offset < needle.length; offset += 1) {
720
+ if (haystack[start + offset] !== needle[offset]) {
721
+ matched = false;
722
+ break;
723
+ }
724
+ }
725
+ if (matched)
726
+ return true;
727
+ }
728
+ return false;
729
+ }
730
+ export function mergeTranscriptSnapshots(existing, incoming) {
731
+ const currentLines = normalizeTranscriptLines(existing);
732
+ const nextLines = normalizeTranscriptLines(incoming);
733
+ if (nextLines.length === 0)
734
+ return currentLines.join('\n');
735
+ if (currentLines.length === 0)
736
+ return nextLines.join('\n');
737
+ if (containsLineSequence(currentLines, nextLines))
738
+ return currentLines.join('\n');
739
+ const maxOverlap = Math.min(currentLines.length, nextLines.length);
740
+ for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
741
+ let matched = true;
742
+ for (let index = 0; index < overlap; index += 1) {
743
+ if (currentLines[currentLines.length - overlap + index] !== nextLines[index]) {
744
+ matched = false;
745
+ break;
746
+ }
747
+ }
748
+ if (matched) {
749
+ return [...currentLines, ...nextLines.slice(overlap)].join('\n');
750
+ }
751
+ }
752
+ return [...currentLines, ...nextLines].join('\n');
753
+ }
754
+ function clickChapterTabScript() {
755
+ return `
756
+ (() => {
757
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
758
+ if (!panel) return false;
759
+ const tabs = panel.querySelectorAll('[role="tab"], .semi-tabs-tab');
760
+ for (const tab of tabs) {
761
+ if ((tab.textContent || '').trim().includes('章节')) {
762
+ tab.click();
763
+ return true;
764
+ }
765
+ }
766
+ return false;
767
+ })()
768
+ `;
769
+ }
770
+ function readChapterScript() {
771
+ return `
772
+ (() => {
773
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
774
+ if (!panel) return '';
775
+ const chapter = panel.querySelector('[data-testid="meeting-ai-chapter"]');
776
+ if (!chapter) return '';
777
+ return (chapter.innerText || chapter.textContent || '').trim();
778
+ })()
779
+ `;
780
+ }
781
+ function triggerTranscriptDownloadScript() {
782
+ return `
783
+ (() => {
784
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
785
+ if (!panel) return { error: 'no panel' };
786
+
787
+ const downloadIcon = panel.querySelector('[class*="DownloadMeetingAudio"] span[role="img"]');
788
+ if (!downloadIcon) return { error: 'no download icon' };
789
+
790
+ downloadIcon.click();
791
+ return { clicked: 'icon' };
792
+ })()
793
+ `;
794
+ }
795
+ function clickTranscriptDownloadBtnScript() {
796
+ return `
797
+ (() => {
798
+ const btn = document.querySelector('[data-testid="minutes-download-text-btn"]');
799
+ if (!btn) return { error: 'no download text btn' };
800
+ btn.click();
801
+ return { clicked: 'transcript' };
802
+ })()
803
+ `;
804
+ }
805
+ export async function openMeetingPanel(page, conversationId) {
806
+ await navigateToConversation(page, conversationId);
807
+ const clicked = await page.evaluate(clickMeetingCardScript());
808
+ if (!clicked)
809
+ return false;
810
+ await page.wait(2);
811
+ return true;
812
+ }
813
+ export async function getMeetingSummary(page) {
814
+ const result = await page.evaluate(readMeetingSummaryScript());
815
+ return result.summary || '';
816
+ }
817
+ export async function getMeetingChapters(page) {
818
+ await page.evaluate(clickChapterTabScript());
819
+ await page.wait(1.5);
820
+ return await page.evaluate(readChapterScript());
821
+ }
822
+ function scrollTextNotesPanelScript() {
823
+ return `
824
+ (() => {
825
+ const panel = document.querySelector('[data-testid="canvas_panel_container"]');
826
+ if (!panel) return 0;
827
+ const textNotes = panel.querySelector('[data-testid="meeting-text-notes"]');
828
+ if (!textNotes) return 0;
829
+
830
+ const scrollable = textNotes.closest('[class*="overflow"]')
831
+ || textNotes.parentElement
832
+ || textNotes;
833
+ const maxScroll = scrollable.scrollHeight - scrollable.clientHeight;
834
+ if (maxScroll > 0) {
835
+ scrollable.scrollTop = scrollable.scrollHeight;
836
+ }
837
+ return maxScroll;
838
+ })()
839
+ `;
840
+ }
841
+ export async function getMeetingTranscript(page) {
842
+ await page.evaluate(clickTextNotesTabScript());
843
+ await page.wait(2);
844
+ let merged = '';
845
+ let stableRounds = 0;
846
+ for (let i = 0; i < 10; i++) {
847
+ await page.evaluate(scrollTextNotesPanelScript());
848
+ await page.wait(1);
849
+ const snapshot = await page.evaluate(readTextNotesScript());
850
+ const nextMerged = mergeTranscriptSnapshots(merged, snapshot);
851
+ if (nextMerged === merged && snapshot.length > 0) {
852
+ stableRounds += 1;
853
+ if (stableRounds >= 2)
854
+ break;
855
+ }
856
+ else {
857
+ stableRounds = 0;
858
+ merged = nextMerged;
859
+ }
860
+ }
861
+ return merged;
862
+ }
863
+ export async function triggerTranscriptDownload(page) {
864
+ const iconResult = await page.evaluate(triggerTranscriptDownloadScript());
865
+ if (iconResult.error)
866
+ return false;
867
+ await page.wait(1);
868
+ const btnResult = await page.evaluate(clickTranscriptDownloadBtnScript());
869
+ return !btnResult.error;
870
+ }
554
871
  export async function startNewDoubaoChat(page) {
555
872
  await ensureDoubaoChatPage(page);
556
873
  const clickedLabel = await page.evaluate(clickNewChatScript());
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { mergeTranscriptSnapshots, parseDoubaoConversationId } from './utils.js';
3
+ describe('parseDoubaoConversationId', () => {
4
+ it('extracts the numeric id from a full conversation URL', () => {
5
+ expect(parseDoubaoConversationId('https://www.doubao.com/chat/1234567890123')).toBe('1234567890123');
6
+ });
7
+ it('keeps a raw id unchanged', () => {
8
+ expect(parseDoubaoConversationId('1234567890123')).toBe('1234567890123');
9
+ });
10
+ });
11
+ describe('mergeTranscriptSnapshots', () => {
12
+ it('extends the transcript when the next snapshot overlaps with the tail', () => {
13
+ const merged = mergeTranscriptSnapshots('Alice 00:00\nHello team\nBob 00:05\nHi', 'Bob 00:05\nHi\nAlice 00:10\nNext topic');
14
+ expect(merged).toBe('Alice 00:00\nHello team\nBob 00:05\nHi\nAlice 00:10\nNext topic');
15
+ });
16
+ it('does not duplicate a snapshot that is already contained in the transcript', () => {
17
+ const merged = mergeTranscriptSnapshots('Alice 00:00\nHello team\nBob 00:05\nHi', 'Bob 00:05\nHi');
18
+ expect(merged).toBe('Alice 00:00\nHello team\nBob 00:05\nHi');
19
+ });
20
+ it('keeps both windows when a virtualized panel returns adjacent chunks without full history', () => {
21
+ const merged = mergeTranscriptSnapshots('Alice 00:00\nHello team\nBob 00:05\nHi', 'Alice 00:10\nNext topic\nBob 00:15\nAction items');
22
+ expect(merged).toBe('Alice 00:00\nHello team\nBob 00:05\nHi\nAlice 00:10\nNext topic\nBob 00:15\nAction items');
23
+ });
24
+ });
@@ -0,0 +1,33 @@
1
+ import type { IPage } from '../../../types.js';
2
+ export interface DouyinComment {
3
+ text?: string;
4
+ digg_count?: number;
5
+ user?: {
6
+ nickname?: string;
7
+ };
8
+ }
9
+ export interface DouyinVideo {
10
+ aweme_id: string;
11
+ desc?: string;
12
+ video?: {
13
+ duration?: number;
14
+ play_addr?: {
15
+ url_list?: string[];
16
+ };
17
+ };
18
+ statistics?: {
19
+ digg_count?: number;
20
+ };
21
+ }
22
+ export interface DouyinVideoListResponse {
23
+ aweme_list?: DouyinVideo[];
24
+ }
25
+ export interface DouyinCommentListResponse {
26
+ comments?: DouyinComment[];
27
+ }
28
+ export declare function fetchDouyinUserVideos(page: IPage, secUid: string, count: number): Promise<DouyinVideo[]>;
29
+ export declare function fetchDouyinComments(page: IPage, awemeId: string, count: number): Promise<Array<{
30
+ text: string;
31
+ digg_count: number;
32
+ nickname: string;
33
+ }>>;
@@ -0,0 +1,29 @@
1
+ import { browserFetch } from './browser-fetch.js';
2
+ export async function fetchDouyinUserVideos(page, secUid, count) {
3
+ const params = new URLSearchParams({
4
+ sec_user_id: secUid,
5
+ max_cursor: '0',
6
+ count: String(count),
7
+ aid: '6383',
8
+ });
9
+ const data = await browserFetch(page, 'GET', `https://www.douyin.com/aweme/v1/web/aweme/post/?${params.toString()}`, {
10
+ headers: { referer: 'https://www.douyin.com/' },
11
+ });
12
+ return data.aweme_list || [];
13
+ }
14
+ export async function fetchDouyinComments(page, awemeId, count) {
15
+ const params = new URLSearchParams({
16
+ aweme_id: awemeId,
17
+ count: String(count),
18
+ cursor: '0',
19
+ aid: '6383',
20
+ });
21
+ const data = await browserFetch(page, 'GET', `https://www.douyin.com/aweme/v1/web/comment/list/?${params.toString()}`, {
22
+ headers: { referer: 'https://www.douyin.com/' },
23
+ });
24
+ return (data.comments || []).slice(0, count).map((comment) => ({
25
+ text: comment.text || '',
26
+ digg_count: comment.digg_count ?? 0,
27
+ nickname: comment.user?.nickname || '',
28
+ }));
29
+ }
@@ -0,0 +1,5 @@
1
+ export declare const MAX_USER_VIDEOS_LIMIT = 20;
2
+ export declare const USER_VIDEO_COMMENT_CONCURRENCY = 4;
3
+ export declare const DEFAULT_COMMENT_LIMIT = 10;
4
+ export declare function normalizeUserVideosLimit(limit: unknown): number;
5
+ export declare function normalizeCommentLimit(limit: unknown): number;