@aligndottech/connector-core 0.5.0 → 0.6.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.
- package/dist/fetchers/confluence.d.ts +2 -1
- package/dist/fetchers/confluence.d.ts.map +1 -1
- package/dist/fetchers/confluence.js +9 -1
- package/dist/fetchers/confluence.js.map +1 -1
- package/dist/fetchers/git.d.ts +2 -1
- package/dist/fetchers/git.d.ts.map +1 -1
- package/dist/fetchers/git.js +8 -1
- package/dist/fetchers/git.js.map +1 -1
- package/dist/fetchers/github.d.ts +2 -1
- package/dist/fetchers/github.d.ts.map +1 -1
- package/dist/fetchers/github.js +10 -1
- package/dist/fetchers/github.js.map +1 -1
- package/dist/fetchers/gitlab.d.ts +4 -2
- package/dist/fetchers/gitlab.d.ts.map +1 -1
- package/dist/fetchers/gitlab.js +33 -4
- package/dist/fetchers/gitlab.js.map +1 -1
- package/dist/fetchers/jira.d.ts +2 -1
- package/dist/fetchers/jira.d.ts.map +1 -1
- package/dist/fetchers/jira.js +10 -2
- package/dist/fetchers/jira.js.map +1 -1
- package/dist/fetchers/linear.d.ts +2 -1
- package/dist/fetchers/linear.d.ts.map +1 -1
- package/dist/fetchers/linear.js +10 -2
- package/dist/fetchers/linear.js.map +1 -1
- package/dist/fetchers/notion.d.ts +4 -1
- package/dist/fetchers/notion.d.ts.map +1 -1
- package/dist/fetchers/notion.js +61 -30
- package/dist/fetchers/notion.js.map +1 -1
- package/dist/fetchers/slack.d.ts +12 -3
- package/dist/fetchers/slack.d.ts.map +1 -1
- package/dist/fetchers/slack.js +222 -28
- package/dist/fetchers/slack.js.map +1 -1
- package/dist/fetchers/teams.d.ts +2 -1
- package/dist/fetchers/teams.d.ts.map +1 -1
- package/dist/fetchers/teams.js +9 -1
- package/dist/fetchers/teams.js.map +1 -1
- package/dist/fetchers/util/time.d.ts +15 -0
- package/dist/fetchers/util/time.d.ts.map +1 -0
- package/dist/fetchers/util/time.js +24 -0
- package/dist/fetchers/util/time.js.map +1 -0
- package/dist/fetchers/zoom.d.ts +6 -1
- package/dist/fetchers/zoom.d.ts.map +1 -1
- package/dist/fetchers/zoom.js +102 -31
- package/dist/fetchers/zoom.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/fetcher.d.ts +54 -0
- package/dist/types/fetcher.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/fetchers/notion.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { fetch } from 'undici';
|
|
2
|
+
import { toIsoOrUndefined } from './util/time.js';
|
|
3
|
+
// Notion's own per-page maximum for /v1/search. Before ALI-828 the read was one
|
|
4
|
+
// page sized to the limit, with the cursor Notion returned never sent back.
|
|
5
|
+
const NOTION_PAGE_MAX = 100;
|
|
2
6
|
/** Resolve a Notion user id to a name (cached). Degrades to undefined on failure. */
|
|
3
7
|
function makeNotionUserResolver(headers) {
|
|
4
8
|
const cache = new Map();
|
|
@@ -36,50 +40,77 @@ function extractBlockText(block) {
|
|
|
36
40
|
/**
|
|
37
41
|
* Read-only personal Notion fetcher: pages the integration can see, with body
|
|
38
42
|
* text from their child blocks. Author = the page creator ("who to talk to").
|
|
43
|
+
* Pages the search with `start_cursor` while `has_more`, up to `limit`. A page
|
|
44
|
+
* whose blocks cannot be read is kept (title only) and counted into the report.
|
|
39
45
|
*/
|
|
40
46
|
export class NotionFetcher {
|
|
41
47
|
async fetch(opts) {
|
|
48
|
+
return (await this.fetchWithReport(opts)).items;
|
|
49
|
+
}
|
|
50
|
+
async fetchWithReport(opts) {
|
|
42
51
|
const headers = {
|
|
43
52
|
Authorization: `Bearer ${opts.token}`,
|
|
44
53
|
'Notion-Version': '2022-06-28',
|
|
45
54
|
'Content-Type': 'application/json',
|
|
46
55
|
};
|
|
47
56
|
const limit = opts.limit ?? 50;
|
|
48
|
-
const searchRes = await fetch('https://api.notion.com/v1/search', {
|
|
49
|
-
method: 'POST',
|
|
50
|
-
headers,
|
|
51
|
-
body: JSON.stringify({ filter: { value: 'page', property: 'object' }, page_size: limit }),
|
|
52
|
-
});
|
|
53
|
-
if (!searchRes.ok) {
|
|
54
|
-
throw new Error(`Notion API failed (${searchRes.status}). Check your integration token.`);
|
|
55
|
-
}
|
|
56
|
-
const data = (await searchRes.json());
|
|
57
57
|
const resolveUser = makeNotionUserResolver(headers);
|
|
58
58
|
const items = [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
let scanned = 0;
|
|
60
|
+
let bodiesUnreadable = 0;
|
|
61
|
+
let cursor;
|
|
62
|
+
do {
|
|
63
|
+
const searchRes = await fetch('https://api.notion.com/v1/search', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers,
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
filter: { value: 'page', property: 'object' },
|
|
68
|
+
page_size: Math.min(limit - items.length, NOTION_PAGE_MAX),
|
|
69
|
+
...(cursor ? { start_cursor: cursor } : {}),
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (!searchRes.ok) {
|
|
73
|
+
throw new Error(`Notion API failed (${searchRes.status}). Check your integration token.`);
|
|
70
74
|
}
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
const data = (await searchRes.json());
|
|
76
|
+
for (const page of data.results) {
|
|
77
|
+
if (items.length >= limit)
|
|
78
|
+
break;
|
|
79
|
+
scanned += 1;
|
|
80
|
+
const title = extractPageTitle(page);
|
|
81
|
+
const pageUrl = page.url ?? `https://notion.so/${page.id.replace(/-/g, '')}`;
|
|
82
|
+
const author = await resolveUser(page.created_by?.id);
|
|
83
|
+
const createdAt = toIsoOrUndefined(page.created_time);
|
|
84
|
+
let bodyText = '';
|
|
85
|
+
try {
|
|
86
|
+
const blocksRes = await fetch(`https://api.notion.com/v1/blocks/${page.id}/children?page_size=50`, { headers });
|
|
87
|
+
if (blocksRes.ok) {
|
|
88
|
+
const blocks = (await blocksRes.json());
|
|
89
|
+
bodyText = blocks.results.map(extractBlockText).filter(Boolean).join('\n');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
bodiesUnreadable += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
bodiesUnreadable += 1;
|
|
97
|
+
}
|
|
98
|
+
items.push({
|
|
99
|
+
source_url: pageUrl,
|
|
100
|
+
platform: 'notion',
|
|
101
|
+
raw_text: [title, bodyText].filter(Boolean).join('\n\n').slice(0, 3000),
|
|
102
|
+
title,
|
|
103
|
+
...(createdAt ? { created_at: createdAt } : {}),
|
|
104
|
+
...(author ? { author } : {}),
|
|
105
|
+
});
|
|
73
106
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
...(author ? { author } : {}),
|
|
80
|
-
});
|
|
107
|
+
cursor = data.has_more ? (data.next_cursor ?? undefined) : undefined;
|
|
108
|
+
} while (cursor && items.length < limit);
|
|
109
|
+
const skips = [];
|
|
110
|
+
if (bodiesUnreadable > 0) {
|
|
111
|
+
skips.push({ kind: 'error', count: bodiesUnreadable, detail: 'pages whose body could not be read (kept, title only)' });
|
|
81
112
|
}
|
|
82
|
-
return items;
|
|
113
|
+
return { items, report: { platform: 'notion', scanned, requested: limit, skips } };
|
|
83
114
|
}
|
|
84
115
|
}
|
|
85
116
|
//# sourceMappingURL=notion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notion.js","sourceRoot":"","sources":["../../src/fetchers/notion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"notion.js","sourceRoot":"","sources":["../../src/fetchers/notion.ts"],"names":[],"mappings":"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"}
|
package/dist/fetchers/slack.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import type { ConnectorFetcher, ConnectorFetcherOptions, FetcherItem } from '../types/fetcher.js';
|
|
1
|
+
import type { ConnectorFetcher, ConnectorFetcherOptions, FetcherItem, FetchResult } from '../types/fetcher.js';
|
|
2
2
|
/**
|
|
3
3
|
* Read-only personal Slack fetcher: threaded conversations (>=2 replies) the
|
|
4
|
-
* token can see, within `daysBack
|
|
5
|
-
*
|
|
4
|
+
* token can see, within `daysBack`, that hold at least one HUMAN message. Title
|
|
5
|
+
* and author come from the first human message, so a thread whose root was
|
|
6
|
+
* deleted or posted by a bot is still captured when a person spoke in it, and a
|
|
7
|
+
* thread made only of bot and system output is not captured at all.
|
|
8
|
+
*
|
|
9
|
+
* Channels, history and replies are all paged. Every bound (`maxChannels`,
|
|
10
|
+
* `maxHistoryPages`, `maxReplyPages`, `timeBudgetMs`) and every filter that
|
|
11
|
+
* fires is counted into the fetch report, so a thin result comes with its
|
|
12
|
+
* reason. A delay between channels keeps under Slack's Tier-2 rate limit
|
|
13
|
+
* (override via `interChannelDelayMs`).
|
|
6
14
|
*/
|
|
7
15
|
export declare class SlackFetcher implements ConnectorFetcher {
|
|
8
16
|
fetch(opts: ConnectorFetcherOptions): Promise<FetcherItem[]>;
|
|
17
|
+
fetchWithReport(opts: ConnectorFetcherOptions): Promise<FetchResult>;
|
|
9
18
|
}
|
|
10
19
|
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/fetchers/slack.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/fetchers/slack.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,WAAW,EAAE,WAAW,EAAa,MAAM,qBAAqB,CAAC;AA4K1H;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAa,YAAW,gBAAgB;IAC7C,KAAK,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAI5D,eAAe,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC;CAyJ3E"}
|
package/dist/fetchers/slack.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetch } from 'undici';
|
|
2
|
+
import { toIsoOrUndefined } from './util/time.js';
|
|
2
3
|
async function slackGet(endpoint, token, params = {}) {
|
|
3
4
|
const qs = new URLSearchParams(params);
|
|
4
5
|
const res = await fetch(`https://slack.com/api/${endpoint}?${qs}`, {
|
|
@@ -9,6 +10,112 @@ async function slackGet(endpoint, token, params = {}) {
|
|
|
9
10
|
throw new Error(`Slack API error on ${endpoint}: ${data.error}`);
|
|
10
11
|
return data;
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Bounds, every one overridable through opts so tests never sleep and a large
|
|
15
|
+
* workspace can be widened deliberately. Each one, when it fires, becomes a line
|
|
16
|
+
* the user reads in the fetch report - a cap nobody is told about is
|
|
17
|
+
* indistinguishable from a thin tool (ALI-828).
|
|
18
|
+
*
|
|
19
|
+
* The numbers are derived, not measured: 200 channels at the 3-second Tier-2
|
|
20
|
+
* delay is 10 minutes of sleeping on its own, and 8 minutes is what the 15-minute
|
|
21
|
+
* `align setup --local` budget leaves for Slack once the other sources are paid
|
|
22
|
+
* for. The reference workspace (61 channels) reaches neither.
|
|
23
|
+
*/
|
|
24
|
+
const SLACK_MAX_CHANNELS = 200;
|
|
25
|
+
const SLACK_MAX_HISTORY_PAGES = 5; // ~5,000 messages inside the daysBack window
|
|
26
|
+
const SLACK_MAX_REPLY_PAGES = 3; // ~3,000 messages in one thread
|
|
27
|
+
/**
|
|
28
|
+
* Page sizes at each endpoint's documented maximum: "under 1000" for list and
|
|
29
|
+
* history, 1000 (the default and the max) for replies. 0.5.0 sent
|
|
30
|
+
* conversations.replies with no limit, so a thread of up to 1000 messages came
|
|
31
|
+
* back in one call; a smaller page here would cut a KEPT thread at
|
|
32
|
+
* maxReplyPages and move its bytes, which the golden contract forbids. Slack's
|
|
33
|
+
* 2025 15-object cap applies only to commercially distributed non-Marketplace
|
|
34
|
+
* apps; a user's own app, which is what the CLI token belongs to, is exempt.
|
|
35
|
+
*/
|
|
36
|
+
const SLACK_LIST_PAGE_SIZE = 999;
|
|
37
|
+
const SLACK_HISTORY_PAGE_SIZE = 999;
|
|
38
|
+
const SLACK_REPLIES_PAGE_SIZE = 1000;
|
|
39
|
+
const SLACK_TIME_BUDGET_MS = 8 * 60_000;
|
|
40
|
+
/**
|
|
41
|
+
* Message subtypes that are the WORKSPACE talking, not a person: joins, leaves,
|
|
42
|
+
* topic changes, pins, and app output. A thread made only of these is machinery,
|
|
43
|
+
* and on the Align demo workspace 35 of 39 captured "threads" were exactly that -
|
|
44
|
+
* a tombstone root under the Align app's own replies (ALI-828).
|
|
45
|
+
*
|
|
46
|
+
* A closed denylist rather than "any subtype at all": `thread_broadcast` is a
|
|
47
|
+
* human reply that was also posted to the channel, and `file_share` is a human
|
|
48
|
+
* sharing a file with a comment. Both are decision content and both carry a
|
|
49
|
+
* subtype. Every member here has its own case in fetchers.slack.test.ts.
|
|
50
|
+
*/
|
|
51
|
+
const SYSTEM_SUBTYPES = new Set([
|
|
52
|
+
'bot_message',
|
|
53
|
+
'tombstone',
|
|
54
|
+
'channel_join',
|
|
55
|
+
'channel_leave',
|
|
56
|
+
'channel_topic',
|
|
57
|
+
'channel_purpose',
|
|
58
|
+
'channel_name',
|
|
59
|
+
'channel_archive',
|
|
60
|
+
'channel_unarchive',
|
|
61
|
+
'group_join',
|
|
62
|
+
'group_leave',
|
|
63
|
+
'pinned_item',
|
|
64
|
+
'unpinned_item',
|
|
65
|
+
'bot_add',
|
|
66
|
+
'bot_remove',
|
|
67
|
+
'reminder_add',
|
|
68
|
+
'group_topic',
|
|
69
|
+
'group_purpose',
|
|
70
|
+
'group_name',
|
|
71
|
+
'group_archive',
|
|
72
|
+
'group_unarchive',
|
|
73
|
+
'channel_convert_to_private',
|
|
74
|
+
'channel_convert_to_public',
|
|
75
|
+
'channel_posting_permissions',
|
|
76
|
+
'ekm_access_denied',
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* A message a person wrote. `bot_id` catches an app posting AS a user (which
|
|
80
|
+
* carries no subtype at all), and a message with no `user` has nobody to
|
|
81
|
+
* attribute it to.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately not "is this the Align bot": the CLI's Slack path uses a user
|
|
84
|
+
* token, whose `auth.test` names the HUMAN, so an identity filter would delete
|
|
85
|
+
* the user's own messages. The shape test catches Align's bot as one member of
|
|
86
|
+
* the class of all bots, and stays vendor-neutral.
|
|
87
|
+
*/
|
|
88
|
+
function isHumanMessage(m) {
|
|
89
|
+
if (m.bot_id)
|
|
90
|
+
return false;
|
|
91
|
+
// A fired /remind or a Slackbot reply carries no bot_id and no subtype, only this user.
|
|
92
|
+
if (m.user === 'USLACKBOT')
|
|
93
|
+
return false;
|
|
94
|
+
if (m.subtype && SYSTEM_SUBTYPES.has(m.subtype))
|
|
95
|
+
return false;
|
|
96
|
+
return Boolean(m.user);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Follow `response_metadata.next_cursor` for up to `maxPages` pages, or until
|
|
100
|
+
* `enough(rows)` says to stop. `truncated` is true when a cursor was still there
|
|
101
|
+
* when paging stopped, which is the fact the report needs: rows exist that this
|
|
102
|
+
* read did not see.
|
|
103
|
+
*/
|
|
104
|
+
async function slackPaged(endpoint, token, params, rowsKey, maxPages, enough = () => false) {
|
|
105
|
+
const rows = [];
|
|
106
|
+
let cursor;
|
|
107
|
+
let pages = 0;
|
|
108
|
+
for (;;) {
|
|
109
|
+
const data = await slackGet(endpoint, token, { ...params, ...(cursor ? { cursor } : {}) });
|
|
110
|
+
rows.push(...(data[rowsKey] ?? []));
|
|
111
|
+
pages += 1;
|
|
112
|
+
cursor = data.response_metadata?.next_cursor || undefined;
|
|
113
|
+
if (!cursor)
|
|
114
|
+
return { rows, truncated: false };
|
|
115
|
+
if (enough(rows) || pages >= maxPages)
|
|
116
|
+
return { rows, truncated: true };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
12
119
|
/** Resolve a Slack user id to a display name (cached - one users.info call per unique user). */
|
|
13
120
|
function makeUserResolver(token) {
|
|
14
121
|
const cache = new Map();
|
|
@@ -37,67 +144,154 @@ function makeUserResolver(token) {
|
|
|
37
144
|
}
|
|
38
145
|
/**
|
|
39
146
|
* Read-only personal Slack fetcher: threaded conversations (>=2 replies) the
|
|
40
|
-
* token can see, within `daysBack
|
|
41
|
-
*
|
|
147
|
+
* token can see, within `daysBack`, that hold at least one HUMAN message. Title
|
|
148
|
+
* and author come from the first human message, so a thread whose root was
|
|
149
|
+
* deleted or posted by a bot is still captured when a person spoke in it, and a
|
|
150
|
+
* thread made only of bot and system output is not captured at all.
|
|
151
|
+
*
|
|
152
|
+
* Channels, history and replies are all paged. Every bound (`maxChannels`,
|
|
153
|
+
* `maxHistoryPages`, `maxReplyPages`, `timeBudgetMs`) and every filter that
|
|
154
|
+
* fires is counted into the fetch report, so a thin result comes with its
|
|
155
|
+
* reason. A delay between channels keeps under Slack's Tier-2 rate limit
|
|
156
|
+
* (override via `interChannelDelayMs`).
|
|
42
157
|
*/
|
|
43
158
|
export class SlackFetcher {
|
|
44
159
|
async fetch(opts) {
|
|
160
|
+
return (await this.fetchWithReport(opts)).items;
|
|
161
|
+
}
|
|
162
|
+
async fetchWithReport(opts) {
|
|
45
163
|
const limit = opts.limit ?? 50;
|
|
46
164
|
const daysBack = opts.daysBack ?? 90;
|
|
47
165
|
const delayMs = opts.interChannelDelayMs ?? 3000;
|
|
48
|
-
const
|
|
166
|
+
const maxChannels = opts.maxChannels ?? SLACK_MAX_CHANNELS;
|
|
167
|
+
const maxHistoryPages = opts.maxHistoryPages ?? SLACK_MAX_HISTORY_PAGES;
|
|
168
|
+
const maxReplyPages = opts.maxReplyPages ?? SLACK_MAX_REPLY_PAGES;
|
|
169
|
+
const timeBudgetMs = opts.timeBudgetMs ?? SLACK_TIME_BUDGET_MS;
|
|
170
|
+
const startedAt = Date.now();
|
|
171
|
+
const oldest = String(Math.floor(startedAt / 1000) - daysBack * 86400);
|
|
49
172
|
await slackGet('auth.test', opts.token);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
173
|
+
// Read list pages until one shows MORE channels than the cap, so the report
|
|
174
|
+
// can name the surplus it saw rather than "some". At most one page past the
|
|
175
|
+
// cap is read, and a cursor left after that means the surplus is a floor.
|
|
176
|
+
const list = await slackPaged('conversations.list', opts.token, { types: 'public_channel,private_channel', exclude_archived: 'true', limit: String(SLACK_LIST_PAGE_SIZE) }, 'channels', Number.POSITIVE_INFINITY, (rows) => rows.length > maxChannels);
|
|
177
|
+
const channels = list.rows.slice(0, maxChannels);
|
|
178
|
+
const channelSurplus = list.rows.length - channels.length;
|
|
56
179
|
const resolveUser = makeUserResolver(opts.token);
|
|
57
180
|
const items = [];
|
|
181
|
+
let threadsScanned = 0;
|
|
182
|
+
let shortMessages = 0;
|
|
183
|
+
let noHumanThreads = 0;
|
|
184
|
+
let historyCut = 0;
|
|
185
|
+
let repliesCut = 0;
|
|
186
|
+
let channelsUnreadable = 0;
|
|
187
|
+
let threadsUnreadable = 0;
|
|
188
|
+
let channelsOutOfTime = 0;
|
|
58
189
|
for (let channelIndex = 0; channelIndex < channels.length; channelIndex++) {
|
|
59
190
|
const channel = channels[channelIndex];
|
|
60
191
|
if (items.length >= limit)
|
|
61
192
|
break;
|
|
62
|
-
|
|
63
|
-
|
|
193
|
+
if (channelIndex > 0) {
|
|
194
|
+
// Checked BEFORE paying the delay: the budget bounds the loop, not the sleep.
|
|
195
|
+
if (Date.now() - startedAt > timeBudgetMs) {
|
|
196
|
+
channelsOutOfTime = channels.length - channelIndex;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
if (delayMs > 0)
|
|
64
200
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const hist = await slackPaged('conversations.history', opts.token, { channel: channel.id, oldest, limit: String(SLACK_HISTORY_PAGE_SIZE) }, 'messages', maxHistoryPages);
|
|
204
|
+
if (hist.truncated)
|
|
205
|
+
historyCut += 1;
|
|
206
|
+
const threads = hist.rows.filter((m) => (m.reply_count ?? 0) >= 2);
|
|
207
|
+
shortMessages += hist.rows.length - threads.length;
|
|
72
208
|
for (const thread of threads) {
|
|
73
209
|
if (items.length >= limit)
|
|
74
210
|
break;
|
|
211
|
+
threadsScanned += 1;
|
|
75
212
|
try {
|
|
76
|
-
const replies = await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
213
|
+
const replies = await slackPaged('conversations.replies', opts.token, { channel: channel.id, ts: thread.ts, limit: String(SLACK_REPLIES_PAGE_SIZE) }, 'messages', maxReplyPages);
|
|
214
|
+
// A truncated thread is still an item; dropping it would lose the
|
|
215
|
+
// decision to protect a byte count. The truncation is reported instead.
|
|
216
|
+
if (replies.truncated)
|
|
217
|
+
repliesCut += 1;
|
|
218
|
+
// Dedupe by ts: Slack does not say whether a cursor page of replies carries
|
|
219
|
+
// the parent again, and a repeated root would double its text.
|
|
220
|
+
const seenTs = new Set();
|
|
221
|
+
const allMsgs = [];
|
|
222
|
+
for (const m of replies.rows) {
|
|
223
|
+
if (seenTs.has(m.ts))
|
|
224
|
+
continue;
|
|
225
|
+
seenTs.add(m.ts);
|
|
226
|
+
allMsgs.push(m);
|
|
227
|
+
}
|
|
228
|
+
// The thread's identity comes from the first HUMAN message: a deleted or
|
|
229
|
+
// bot root has no title and nobody to attribute, but the conversation
|
|
230
|
+
// under it may be entirely real.
|
|
231
|
+
const firstHuman = allMsgs.find(isHumanMessage);
|
|
232
|
+
if (!firstHuman) {
|
|
233
|
+
noHumanThreads += 1; // machinery, not a conversation
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Every fetched message, bot replies included: a bot reply inside a human
|
|
237
|
+
// thread is often the CI output being discussed.
|
|
81
238
|
const text = allMsgs.map((m) => m.text ?? '').join('\n');
|
|
82
|
-
const author = await resolveUser(
|
|
239
|
+
const author = await resolveUser(firstHuman.user);
|
|
240
|
+
// The root's ts: epoch seconds with microseconds, so the thread is dated by when it started.
|
|
241
|
+
const createdAt = toIsoOrUndefined(Number(thread.ts) * 1000);
|
|
83
242
|
items.push({
|
|
84
243
|
source_url: `https://slack.com/archives/${channel.id}/p${thread.ts.replace('.', '')}`,
|
|
85
244
|
platform: 'slack',
|
|
86
245
|
raw_text: `[#${channel.name}] Thread:\n${text}`,
|
|
87
|
-
title: (
|
|
246
|
+
title: (firstHuman.text ?? `Thread in #${channel.name}`).slice(0, 80),
|
|
247
|
+
...(createdAt ? { created_at: createdAt } : {}),
|
|
88
248
|
...(author ? { author } : {}),
|
|
89
249
|
});
|
|
90
250
|
}
|
|
91
251
|
catch {
|
|
92
|
-
|
|
252
|
+
threadsUnreadable += 1;
|
|
93
253
|
}
|
|
94
254
|
}
|
|
95
255
|
}
|
|
96
256
|
catch {
|
|
97
|
-
|
|
257
|
+
channelsUnreadable += 1;
|
|
98
258
|
}
|
|
99
259
|
}
|
|
100
|
-
|
|
260
|
+
// A fixed order, so two runs over the same workspace print the same report.
|
|
261
|
+
const skips = [];
|
|
262
|
+
if (shortMessages > 0) {
|
|
263
|
+
skips.push({ kind: 'shape', count: shortMessages, detail: 'messages with fewer than 2 replies (this fetcher reads threads only)' });
|
|
264
|
+
}
|
|
265
|
+
if (noHumanThreads > 0) {
|
|
266
|
+
skips.push({ kind: 'shape', count: noHumanThreads, detail: 'threads with no human message (bot or system output only)' });
|
|
267
|
+
}
|
|
268
|
+
if (channelSurplus > 0) {
|
|
269
|
+
skips.push({
|
|
270
|
+
kind: 'page_cap',
|
|
271
|
+
count: channelSurplus,
|
|
272
|
+
detail: `${list.truncated ? 'or more ' : ''}channels not scanned (the first ${maxChannels} were; raise maxChannels)`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (historyCut > 0) {
|
|
276
|
+
skips.push({ kind: 'page_cap', count: historyCut, detail: `channels whose history was cut at ${maxHistoryPages} page(s) (raise maxHistoryPages)` });
|
|
277
|
+
}
|
|
278
|
+
if (repliesCut > 0) {
|
|
279
|
+
skips.push({ kind: 'page_cap', count: repliesCut, detail: `threads whose replies were cut at ${maxReplyPages} page(s) (raise maxReplyPages)` });
|
|
280
|
+
}
|
|
281
|
+
if (channelsOutOfTime > 0) {
|
|
282
|
+
skips.push({
|
|
283
|
+
kind: 'time_budget',
|
|
284
|
+
count: channelsOutOfTime,
|
|
285
|
+
detail: `channels not scanned (the ${Math.round(timeBudgetMs / 60_000)} minute Slack time budget ran out)`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (channelsUnreadable > 0) {
|
|
289
|
+
skips.push({ kind: 'error', count: channelsUnreadable, detail: 'channels the token could not read' });
|
|
290
|
+
}
|
|
291
|
+
if (threadsUnreadable > 0) {
|
|
292
|
+
skips.push({ kind: 'error', count: threadsUnreadable, detail: 'threads whose replies could not be read' });
|
|
293
|
+
}
|
|
294
|
+
return { items, report: { platform: 'slack', scanned: threadsScanned, requested: limit, skips } };
|
|
101
295
|
}
|
|
102
296
|
}
|
|
103
297
|
//# sourceMappingURL=slack.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/fetchers/slack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/fetchers/slack.ts"],"names":[],"mappings":"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"}
|
package/dist/fetchers/teams.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConnectorFetcher, ConnectorFetcherOptions, FetcherItem } from '../types/fetcher.js';
|
|
1
|
+
import type { ConnectorFetcher, ConnectorFetcherOptions, FetcherItem, FetchResult } from '../types/fetcher.js';
|
|
2
2
|
/**
|
|
3
3
|
* Read-only personal Teams fetcher: recent channel messages (+replies) across the
|
|
4
4
|
* caller's joined teams. Author = the message author. Needs admin-consented
|
|
@@ -6,5 +6,6 @@ import type { ConnectorFetcher, ConnectorFetcherOptions, FetcherItem } from '../
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class TeamsFetcher implements ConnectorFetcher {
|
|
8
8
|
fetch(opts: ConnectorFetcherOptions): Promise<FetcherItem[]>;
|
|
9
|
+
fetchWithReport(opts: ConnectorFetcherOptions): Promise<FetchResult>;
|
|
9
10
|
}
|
|
10
11
|
//# sourceMappingURL=teams.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teams.d.ts","sourceRoot":"","sources":["../../src/fetchers/teams.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"teams.d.ts","sourceRoot":"","sources":["../../src/fetchers/teams.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAqD/G;;;;GAIG;AACH,qBAAa,YAAa,YAAW,gBAAgB;IAC7C,KAAK,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAI5D,eAAe,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC;CAiD3E"}
|
package/dist/fetchers/teams.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetch } from 'undici';
|
|
2
|
+
import { toIsoOrUndefined } from './util/time.js';
|
|
2
3
|
function stripHtml(html) {
|
|
3
4
|
return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
4
5
|
}
|
|
@@ -30,9 +31,13 @@ function extractText(body) {
|
|
|
30
31
|
*/
|
|
31
32
|
export class TeamsFetcher {
|
|
32
33
|
async fetch(opts) {
|
|
34
|
+
return (await this.fetchWithReport(opts)).items;
|
|
35
|
+
}
|
|
36
|
+
async fetchWithReport(opts) {
|
|
33
37
|
const limit = opts.limit ?? 50;
|
|
34
38
|
const teams = await graphGet('/me/joinedTeams', opts.token);
|
|
35
39
|
const items = [];
|
|
40
|
+
let scanned = 0;
|
|
36
41
|
for (const team of teams.value) {
|
|
37
42
|
if (items.length >= limit)
|
|
38
43
|
break;
|
|
@@ -45,6 +50,7 @@ export class TeamsFetcher {
|
|
|
45
50
|
for (const msg of msgs.value) {
|
|
46
51
|
if (items.length >= limit)
|
|
47
52
|
break;
|
|
53
|
+
scanned += 1;
|
|
48
54
|
const mainText = extractText(msg.body);
|
|
49
55
|
const replyTexts = (msg.replies ?? []).map((r) => extractText(r.body)).filter(Boolean);
|
|
50
56
|
const raw_text = [
|
|
@@ -56,11 +62,13 @@ export class TeamsFetcher {
|
|
|
56
62
|
.filter(Boolean)
|
|
57
63
|
.join('\n');
|
|
58
64
|
const fromName = msg.from?.user?.displayName;
|
|
65
|
+
const createdAt = toIsoOrUndefined(msg.createdDateTime);
|
|
59
66
|
items.push({
|
|
60
67
|
source_url: msg.webUrl ?? 'https://teams.microsoft.com',
|
|
61
68
|
platform: 'teams',
|
|
62
69
|
raw_text,
|
|
63
70
|
title: (msg.subject ?? mainText).slice(0, 80) || `Message in ${team.displayName}`,
|
|
71
|
+
...(createdAt ? { created_at: createdAt } : {}),
|
|
64
72
|
...(fromName ? { author: { name: fromName } } : {}),
|
|
65
73
|
});
|
|
66
74
|
}
|
|
@@ -70,7 +78,7 @@ export class TeamsFetcher {
|
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
|
-
return items;
|
|
81
|
+
return { items, report: { platform: 'teams', scanned, requested: limit, skips: [] } };
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
//# sourceMappingURL=teams.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teams.js","sourceRoot":"","sources":["../../src/fetchers/teams.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"teams.js","sourceRoot":"","sources":["../../src/fetchers/teams.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalise a source timestamp to ISO-8601 Z, or undefined.
|
|
3
|
+
*
|
|
4
|
+
* Undefined rather than a fallback: a null date says "unknown", and a plausible
|
|
5
|
+
* wrong date is indistinguishable from a measurement to everything downstream.
|
|
6
|
+
* NaN is checked explicitly because every comparison against NaN is false in
|
|
7
|
+
* both directions, so an unchecked bad date does not error, it silently vacates
|
|
8
|
+
* whatever filter reads it later.
|
|
9
|
+
*
|
|
10
|
+
* One writer for the normalisation, so ten fetchers cannot disagree about what
|
|
11
|
+
* "ISO" means. Accepts epoch milliseconds as a number for sources that hand out
|
|
12
|
+
* a numeric timestamp (Slack's `ts` is epoch seconds; callers multiply).
|
|
13
|
+
*/
|
|
14
|
+
export declare function toIsoOrUndefined(value: string | number | undefined | null): string | undefined;
|
|
15
|
+
//# sourceMappingURL=time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../../src/fetchers/util/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAO9F"}
|