@robono/server 0.25.0 → 0.27.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/CHANGELOG.md +8 -0
- package/HISTORY-IMPORT.md +117 -5
- package/INTEGRATION-ASSISTANT.md +103 -0
- package/README.md +16 -0
- package/SBOM.spdx.json +9 -9
- package/dist/calling.d.ts +30 -0
- package/dist/calling.d.ts.map +1 -1
- package/dist/calling.js +13 -0
- package/dist/calling.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/client.js.map +1 -1
- package/dist/generated/custom-messages.d.ts +31 -0
- package/dist/generated/custom-messages.d.ts.map +1 -0
- package/dist/generated/custom-messages.js +84 -0
- package/dist/generated/custom-messages.js.map +1 -0
- package/dist/generated/history-import-interactions.d.ts +17 -0
- package/dist/generated/history-import-interactions.d.ts.map +1 -0
- package/dist/generated/history-import-interactions.js +40 -0
- package/dist/generated/history-import-interactions.js.map +1 -0
- package/dist/generated/interactions.d.ts +199 -0
- package/dist/generated/interactions.d.ts.map +1 -0
- package/dist/generated/interactions.js +89 -0
- package/dist/generated/interactions.js.map +1 -0
- package/dist/generated/sdk-versions.d.ts +13 -0
- package/dist/generated/sdk-versions.d.ts.map +1 -0
- package/dist/generated/sdk-versions.js +4 -0
- package/dist/generated/sdk-versions.js.map +1 -0
- package/dist/history-convert.d.ts +52 -0
- package/dist/history-convert.d.ts.map +1 -0
- package/dist/history-convert.js +276 -0
- package/dist/history-convert.js.map +1 -0
- package/dist/history-export-cli.d.ts +4 -0
- package/dist/history-export-cli.d.ts.map +1 -0
- package/dist/history-export-cli.js +166 -0
- package/dist/history-export-cli.js.map +1 -0
- package/dist/history-export-files.d.ts +39 -0
- package/dist/history-export-files.d.ts.map +1 -0
- package/dist/history-export-files.js +119 -0
- package/dist/history-export-files.js.map +1 -0
- package/dist/history-export.d.ts +92 -0
- package/dist/history-export.d.ts.map +1 -0
- package/dist/history-export.js +78 -0
- package/dist/history-export.js.map +1 -0
- package/dist/history-import-cli.d.ts +1 -7
- package/dist/history-import-cli.d.ts.map +1 -1
- package/dist/history-import-cli.js +4 -33
- package/dist/history-import-cli.js.map +1 -1
- package/dist/history-import-validation.d.ts +8 -0
- package/dist/history-import-validation.d.ts.map +1 -0
- package/dist/history-import-validation.js +85 -0
- package/dist/history-import-validation.js.map +1 -0
- package/dist/history-import.d.ts +10 -1
- package/dist/history-import.d.ts.map +1 -1
- package/dist/history-import.js +27 -6
- package/dist/history-import.js.map +1 -1
- package/dist/history-migrate-cli.d.ts +3 -0
- package/dist/history-migrate-cli.d.ts.map +1 -0
- package/dist/history-migrate-cli.js +260 -0
- package/dist/history-migrate-cli.js.map +1 -0
- package/dist/history-polls.d.ts +4 -0
- package/dist/history-polls.d.ts.map +1 -0
- package/dist/history-polls.js +66 -0
- package/dist/history-polls.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-assistant.txt +103 -0
- package/dist/messaging-notification-providers.d.ts +2 -3
- package/dist/messaging-notification-providers.d.ts.map +1 -1
- package/dist/messaging-notification-providers.js.map +1 -1
- package/dist/messaging.d.ts +5 -0
- package/dist/messaging.d.ts.map +1 -1
- package/dist/messaging.js +11 -2
- package/dist/messaging.js.map +1 -1
- package/dist/messenger-example-cli.d.ts +3 -0
- package/dist/messenger-example-cli.d.ts.map +1 -0
- package/dist/messenger-example-cli.js +47 -0
- package/dist/messenger-example-cli.js.map +1 -0
- package/dist/messenger-example.d.ts +9 -0
- package/dist/messenger-example.d.ts.map +1 -0
- package/dist/messenger-example.js +42 -0
- package/dist/messenger-example.js.map +1 -0
- package/dist/messenger-example.json +1 -0
- package/dist/messenger-hosted-install.d.ts.map +1 -1
- package/dist/messenger-hosted-install.js +4 -2
- package/dist/messenger-hosted-install.js.map +1 -1
- package/dist/messenger-install.d.ts +1 -1
- package/dist/messenger-install.d.ts.map +1 -1
- package/dist/messenger-install.js +5 -4
- package/dist/messenger-install.js.map +1 -1
- package/dist/messenger-preview-template.d.ts.map +1 -1
- package/dist/messenger-preview-template.js +1 -0
- package/dist/messenger-preview-template.js.map +1 -1
- package/package.json +8 -4
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exportPageFiles } from './history-export-files.js';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { open, lstat, mkdir, readFile, rename, unlink } from 'node:fs/promises';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { RobonoHistoryExport, validExportCursor } from './history-export.js';
|
|
9
|
+
import { RobonoError } from './errors.js';
|
|
10
|
+
async function checkpoint(folder, value) {
|
|
11
|
+
const temporary = join(folder, '.checkpoint-' + randomUUID());
|
|
12
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
13
|
+
try {
|
|
14
|
+
await file.writeFile(JSON.stringify(value, null, 2) + '\n');
|
|
15
|
+
await file.sync();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
await file.close();
|
|
19
|
+
}
|
|
20
|
+
await rename(temporary, join(folder, 'checkpoint.json'));
|
|
21
|
+
// Persist the rename as well as the data before advancing to the next page.
|
|
22
|
+
if (process.platform !== 'win32') {
|
|
23
|
+
const dir = await open(folder, 'r');
|
|
24
|
+
try {
|
|
25
|
+
await dir.sync();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await dir.close();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function retry(work, log) {
|
|
33
|
+
for (let attempt = 0;; attempt++)
|
|
34
|
+
try {
|
|
35
|
+
return await work();
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
if (attempt >= 4 || !(e instanceof TypeError || e instanceof RobonoError && e.retryable))
|
|
39
|
+
throw e;
|
|
40
|
+
log('Connection interrupted. Retrying the same export page.');
|
|
41
|
+
await new Promise(r => setTimeout(r, Math.min(15000, 1000 * 2 ** attempt)));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Writes private JSONL and a checkpoint; no message contents or keys are logged. */
|
|
45
|
+
export async function runHistoryExport(args, env = process.env, log = console.log, fetcher) {
|
|
46
|
+
if (args.length === 1 && args[0] === '--help') {
|
|
47
|
+
log('robono-history-export --out new-folder --conversations id,id [--from ISO-date] [--until ISO-date] [--attachments] [--max-attachment-bytes bytes]\nResume: robono-history-export --resume folder\nSet ROBONO_API_KEY privately on your server. Saves JSONL records and a checkpoint; add --attachments to download retained managed files (default 1 GiB limit).');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const options = new Map();
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const key = args[i];
|
|
53
|
+
if (!key || options.has(key))
|
|
54
|
+
throw Error('Each export option can be supplied only once.');
|
|
55
|
+
if (key === '--attachments') {
|
|
56
|
+
options.set(key, 'true');
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!['--out', '--resume', '--conversations', '--from', '--until', '--max-attachment-bytes'].includes(key))
|
|
60
|
+
throw Error('Unknown export option. Use --help.');
|
|
61
|
+
const value = args[++i];
|
|
62
|
+
if (!value || value.startsWith('--'))
|
|
63
|
+
throw Error('Supply a value for ' + key);
|
|
64
|
+
options.set(key, value);
|
|
65
|
+
}
|
|
66
|
+
const resume = options.has('--resume');
|
|
67
|
+
if (resume ? [...options.keys()].some(k => !['--resume', '--max-attachment-bytes'].includes(k)) : !options.has('--out') || !options.has('--conversations'))
|
|
68
|
+
throw Error('Use --out new-folder --conversations id,id, or --resume folder.');
|
|
69
|
+
const byteLimit = options.has('--max-attachment-bytes') ? Number(options.get('--max-attachment-bytes')) : 1073741824;
|
|
70
|
+
if (!Number.isSafeInteger(byteLimit) || byteLimit < 1 || byteLimit > 1099511627776)
|
|
71
|
+
throw Error('Set an attachment byte limit between 1 and 1099511627776.');
|
|
72
|
+
if (!resume && options.has('--max-attachment-bytes') && !options.has('--attachments'))
|
|
73
|
+
throw Error('--max-attachment-bytes requires --attachments.');
|
|
74
|
+
const apiKey = env.ROBONO_API_KEY;
|
|
75
|
+
if (!apiKey)
|
|
76
|
+
throw Error('Set ROBONO_API_KEY privately in your server environment.');
|
|
77
|
+
const exporter = new RobonoHistoryExport({ apiKey, ...(fetcher ? { fetch: fetcher } : {}) });
|
|
78
|
+
const folder = resolve(options.get(resume ? '--resume' : '--out'));
|
|
79
|
+
if (!resume)
|
|
80
|
+
await mkdir(folder, { mode: 0o700 });
|
|
81
|
+
const directory = await lstat(folder);
|
|
82
|
+
if (!directory.isDirectory() || directory.isSymbolicLink() || process.platform !== 'win32' && (directory.mode & 0o077) !== 0)
|
|
83
|
+
throw Error('Use a private export directory that is not a symbolic link.');
|
|
84
|
+
const lockPath = join(folder, '.export.lock');
|
|
85
|
+
let lock;
|
|
86
|
+
try {
|
|
87
|
+
lock = await open(lockPath, 'wx', 0o600);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw Error('This export is locked. If an earlier process crashed, confirm it has stopped before removing .export.lock and resuming.');
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
await lock.writeFile(JSON.stringify({ pid: process.pid }));
|
|
94
|
+
let state;
|
|
95
|
+
if (resume) {
|
|
96
|
+
const info = await lstat(join(folder, 'checkpoint.json'));
|
|
97
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 16384 || info.nlink !== 1 || process.platform !== 'win32' && (info.mode & 0o077) !== 0)
|
|
98
|
+
throw Error('Invalid export checkpoint file.');
|
|
99
|
+
state = JSON.parse(await readFile(join(folder, 'checkpoint.json'), 'utf8'));
|
|
100
|
+
if (state.version !== 1 || !state.selection || !Number.isSafeInteger(state.offset) || state.offset < 0 || !Number.isSafeInteger(state.records) || state.records < 0 || typeof state.done !== 'boolean' || state.cursor !== null && !validExportCursor(state.cursor) || state.done && state.cursor !== null)
|
|
101
|
+
throw Error('Invalid export checkpoint.');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const selection = { jobId: randomUUID(), conversationIds: options.get('--conversations').split(','), ...(options.has('--from') ? { from: options.get('--from') } : {}), ...(options.has('--until') ? { until: options.get('--until') } : {}) };
|
|
105
|
+
state = { version: 1, selection, offset: 0, records: 0, cursor: null, done: false, ...(options.has('--attachments') ? { attachments: { maxBytes: byteLimit, bytes: 0 } } : {}) };
|
|
106
|
+
const file = await open(join(folder, 'history.jsonl'), 'wx', 0o600);
|
|
107
|
+
await file.close();
|
|
108
|
+
await checkpoint(folder, state);
|
|
109
|
+
}
|
|
110
|
+
if (state.attachments) {
|
|
111
|
+
const a = state.attachments;
|
|
112
|
+
if (!Number.isSafeInteger(a.maxBytes) || a.maxBytes < 1 || a.maxBytes > 1099511627776 || !Number.isSafeInteger(a.bytes) || a.bytes < 0 || a.bytes > a.maxBytes)
|
|
113
|
+
throw Error('Invalid attachment checkpoint.');
|
|
114
|
+
if (options.has('--max-attachment-bytes')) {
|
|
115
|
+
if (byteLimit < a.bytes)
|
|
116
|
+
throw Error('The new limit is smaller than the saved files.');
|
|
117
|
+
a.maxBytes = byteLimit;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (options.has('--max-attachment-bytes'))
|
|
121
|
+
throw Error('This export did not include attachment files. Start a new export with --attachments.');
|
|
122
|
+
const file = await open(join(folder, 'history.jsonl'), constants.O_RDWR | (constants.O_NOFOLLOW ?? 0));
|
|
123
|
+
try {
|
|
124
|
+
const info = await file.stat();
|
|
125
|
+
if (!info.isFile() || info.nlink !== 1 || info.size < state.offset || process.platform !== 'win32' && (info.mode & 0o077) !== 0)
|
|
126
|
+
throw Error('The private export file does not match its checkpoint.');
|
|
127
|
+
if (state.done) {
|
|
128
|
+
log(`Export already complete: ${state.records} records.`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const started = await retry(() => exporter.start(state.selection), log);
|
|
132
|
+
if (started.state === 'cancelled')
|
|
133
|
+
throw Error('This export was cancelled. Start a new export.');
|
|
134
|
+
// Only remove bytes written after the last durable checkpoint, never saved pages.
|
|
135
|
+
await file.truncate(state.offset);
|
|
136
|
+
while (!state.done) {
|
|
137
|
+
const page = await retry(() => exporter.page(state.selection.jobId, state.cursor ?? undefined), log);
|
|
138
|
+
const attachments = state.attachments ? await exportPageFiles(exporter, state.selection.jobId, folder, page.records, state.attachments, fetcher) : null;
|
|
139
|
+
const data = Buffer.from((attachments?.records ?? page.records).map(record => JSON.stringify(record) + '\n').join(''));
|
|
140
|
+
let written = 0;
|
|
141
|
+
while (written < data.length) {
|
|
142
|
+
const result = await file.write(data, written, data.length - written, state.offset + written);
|
|
143
|
+
if (!result.bytesWritten)
|
|
144
|
+
throw Error('Could not write the export file.');
|
|
145
|
+
written += result.bytesWritten;
|
|
146
|
+
}
|
|
147
|
+
await file.sync();
|
|
148
|
+
const next = { ...state, offset: state.offset + data.length, records: state.records + page.records.length, cursor: page.nextCursor, done: page.done, ...(state.attachments && attachments ? { attachments: { ...state.attachments, bytes: attachments.bytes } } : {}) };
|
|
149
|
+
await checkpoint(folder, next);
|
|
150
|
+
state = next;
|
|
151
|
+
log(`Saved ${state.records} records.`);
|
|
152
|
+
}
|
|
153
|
+
log(state.attachments ? 'Export complete. Keep the entire folder private. Check attachment statuses for expired or customer-managed files.' : 'Export complete. Keep history.jsonl and checkpoint.json private. Attachment files are not included.');
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
await file.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
await lock.close();
|
|
161
|
+
await unlink(lockPath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
165
|
+
runHistoryExport(process.argv.slice(2)).catch(error => { console.error(error instanceof Error ? error.message : 'History export failed.'); process.exitCode = 1; });
|
|
166
|
+
//# sourceMappingURL=history-export-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-export-cli.js","sourceRoot":"","sources":["../src/history-export-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RobonoHistoryExport, HistoryExportRecord } from './history-export.js';
|
|
2
|
+
export interface ExportFilePolicy {
|
|
3
|
+
maxBytes: number;
|
|
4
|
+
bytes: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function exportPageFiles(exporter: RobonoHistoryExport, jobId: string, folder: string, records: HistoryExportRecord[], policy: ExportFilePolicy, fetcher?: typeof fetch): Promise<{
|
|
7
|
+
records: ({
|
|
8
|
+
type: "conversation";
|
|
9
|
+
id: string;
|
|
10
|
+
group: boolean;
|
|
11
|
+
supergroup: boolean;
|
|
12
|
+
name: string | null;
|
|
13
|
+
externalGroupId: string | null;
|
|
14
|
+
allowed: boolean;
|
|
15
|
+
revision: string;
|
|
16
|
+
ownerId: string | null;
|
|
17
|
+
} | {
|
|
18
|
+
type: "member";
|
|
19
|
+
conversationId: string;
|
|
20
|
+
userId: string;
|
|
21
|
+
canSend: boolean;
|
|
22
|
+
features?: Record<string, boolean>;
|
|
23
|
+
joinedAfterId?: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "message";
|
|
26
|
+
message: Record<string, unknown> & {
|
|
27
|
+
id: string;
|
|
28
|
+
conversationId: string;
|
|
29
|
+
senderId: string;
|
|
30
|
+
kind: string;
|
|
31
|
+
};
|
|
32
|
+
pin: {
|
|
33
|
+
pinnedBy: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
} | null;
|
|
36
|
+
})[];
|
|
37
|
+
bytes: number;
|
|
38
|
+
}>;
|
|
39
|
+
//# sourceMappingURL=history-export-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-export-files.d.ts","sourceRoot":"","sources":["../src/history-export-files.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,mBAAmB,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAAE,QAAQ,EAAC,MAAM,CAAC;IAAA,KAAK,EAAC,MAAM,CAAA;CAAC;AAChE,wBAAsB,eAAe,CAAC,QAAQ,EAAC,mBAAmB,EAAC,KAAK,EAAC,MAAM,EAAC,MAAM,EAAC,MAAM,EAAC,OAAO,EAAC,mBAAmB,EAAE,EAAC,MAAM,EAAC,gBAAgB,EAAC,OAAO,GAAC,OAAO,KAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6C7K"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { open, lstat, mkdir, link, unlink } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
export async function exportPageFiles(exporter, jobId, folder, records, policy, fetcher = fetch) {
|
|
6
|
+
const files = join(folder, 'attachments');
|
|
7
|
+
await mkdir(files, { mode: 0o700 }).catch(e => { if (e.code !== 'EEXIST')
|
|
8
|
+
throw e; });
|
|
9
|
+
const dir = await lstat(files);
|
|
10
|
+
if (!dir.isDirectory() || dir.isSymbolicLink() || process.platform !== 'win32' && (dir.mode & 0o077) !== 0)
|
|
11
|
+
throw Error('Use a private attachments directory without symbolic links.');
|
|
12
|
+
const output = [];
|
|
13
|
+
let bytes = policy.bytes;
|
|
14
|
+
for (const record of records) {
|
|
15
|
+
if (record.type !== 'message' || !record.message.media) {
|
|
16
|
+
output.push(record);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const media = record.message.media;
|
|
20
|
+
if (typeof media.assetId !== 'string' || !/^robono-r2:[0-9a-f-]{36}$/i.test(media.assetId)) {
|
|
21
|
+
output.push({ ...record, attachment: { status: 'customer_managed' } });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const grant = await exporter.attachment({ jobId, conversationId: record.message.conversationId, messageId: record.message.id, assetId: media.assetId });
|
|
25
|
+
if (!grant.available) {
|
|
26
|
+
output.push({ ...record, attachment: { status: grant.reason } });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (bytes + grant.media.byteSize > policy.maxBytes)
|
|
30
|
+
throw Error('The attachment export reached its byte limit. Resume with a larger --max-attachment-bytes value, or start a data-only export.');
|
|
31
|
+
const name = media.assetId.slice(10).toLowerCase() + '.bin', destination = join(files, name), temporary = join(files, '.part-' + randomUUID());
|
|
32
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
33
|
+
let size = 0;
|
|
34
|
+
const digest = createHash('sha256');
|
|
35
|
+
try {
|
|
36
|
+
// The URL comes only from Robono's authorized grant, never from message text.
|
|
37
|
+
// No API key or customer's authentication header is sent to storage.
|
|
38
|
+
const response = await fetcher(grant.url, { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(120000) });
|
|
39
|
+
if (!response.ok || !response.body) {
|
|
40
|
+
await response.body?.cancel();
|
|
41
|
+
throw Error('An attachment download was interrupted. Resume to request a fresh download grant.');
|
|
42
|
+
}
|
|
43
|
+
const reader = response.body.getReader();
|
|
44
|
+
try {
|
|
45
|
+
while (true) {
|
|
46
|
+
const part = await reader.read();
|
|
47
|
+
if (part.done)
|
|
48
|
+
break;
|
|
49
|
+
size += part.value.byteLength;
|
|
50
|
+
if (size > grant.media.byteSize)
|
|
51
|
+
throw Error('An attachment exceeded its verified size.');
|
|
52
|
+
digest.update(part.value);
|
|
53
|
+
let offset = 0;
|
|
54
|
+
while (offset < part.value.length) {
|
|
55
|
+
const written = await file.write(part.value, offset, part.value.length - offset);
|
|
56
|
+
if (!written.bytesWritten)
|
|
57
|
+
throw Error('Could not save an attachment.');
|
|
58
|
+
offset += written.bytesWritten;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await reader.cancel().catch(() => { });
|
|
64
|
+
reader.releaseLock();
|
|
65
|
+
}
|
|
66
|
+
if (size !== grant.media.byteSize)
|
|
67
|
+
throw Error('An attachment was incomplete. Resume to download it again.');
|
|
68
|
+
const sha256 = digest.digest('hex');
|
|
69
|
+
await file.sync();
|
|
70
|
+
try {
|
|
71
|
+
await link(temporary, destination);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
if (e.code !== 'EEXIST')
|
|
75
|
+
throw e;
|
|
76
|
+
// A crash may leave a complete file before its page checkpoint. Reuse only
|
|
77
|
+
// an identical regular file; never overwrite somebody else's file or link.
|
|
78
|
+
if ((await lstat(destination)).isSymbolicLink())
|
|
79
|
+
throw Error('An attachment destination cannot be a symbolic link.');
|
|
80
|
+
const old = await open(destination, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
81
|
+
try {
|
|
82
|
+
const info = await old.stat();
|
|
83
|
+
if (!info.isFile() || info.nlink !== 1 || info.size !== size || process.platform !== 'win32' && (info.mode & 0o077) !== 0)
|
|
84
|
+
throw Error('An existing attachment file does not match this export.');
|
|
85
|
+
const hash = createHash('sha256'), buffer = Buffer.alloc(65536);
|
|
86
|
+
while (true) {
|
|
87
|
+
const r = await old.read(buffer, 0, buffer.length, null);
|
|
88
|
+
if (!r.bytesRead)
|
|
89
|
+
break;
|
|
90
|
+
hash.update(buffer.subarray(0, r.bytesRead));
|
|
91
|
+
}
|
|
92
|
+
if (hash.digest('hex') !== sha256)
|
|
93
|
+
throw Error('An existing attachment file was changed. Move it aside before resuming.');
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await old.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
bytes += size;
|
|
100
|
+
output.push({ ...record, attachment: { status: 'saved', path: 'attachments/' + name, byteSize: size, sha256, fileName: grant.media.fileName, mimeType: grant.media.mimeType } });
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await file.close();
|
|
104
|
+
await unlink(temporary).catch(e => { if (e.code !== 'ENOENT')
|
|
105
|
+
throw e; });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (process.platform !== 'win32') {
|
|
109
|
+
const dir = await open(files, 'r');
|
|
110
|
+
try {
|
|
111
|
+
await dir.sync();
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await dir.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { records: output, bytes };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=history-export-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-export-files.js","sourceRoot":"","sources":["../src/history-export-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface HistoryExportCursor {
|
|
2
|
+
conversationIndex: number;
|
|
3
|
+
phase: 'conversation' | 'members' | 'messages';
|
|
4
|
+
after: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HistoryExportSelection {
|
|
7
|
+
jobId: string;
|
|
8
|
+
conversationIds: string[];
|
|
9
|
+
from?: string;
|
|
10
|
+
until?: string;
|
|
11
|
+
}
|
|
12
|
+
export type HistoryExportRecord = {
|
|
13
|
+
type: 'conversation';
|
|
14
|
+
id: string;
|
|
15
|
+
group: boolean;
|
|
16
|
+
supergroup: boolean;
|
|
17
|
+
name: string | null;
|
|
18
|
+
externalGroupId: string | null;
|
|
19
|
+
allowed: boolean;
|
|
20
|
+
revision: string;
|
|
21
|
+
ownerId: string | null;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'member';
|
|
24
|
+
conversationId: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
canSend: boolean;
|
|
27
|
+
features?: Record<string, boolean>;
|
|
28
|
+
joinedAfterId?: string;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'message';
|
|
31
|
+
message: Record<string, unknown> & {
|
|
32
|
+
id: string;
|
|
33
|
+
conversationId: string;
|
|
34
|
+
senderId: string;
|
|
35
|
+
kind: string;
|
|
36
|
+
};
|
|
37
|
+
pin: {
|
|
38
|
+
pinnedBy: string;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
} | null;
|
|
41
|
+
};
|
|
42
|
+
export type HistoryExportAttachment = {
|
|
43
|
+
available: false;
|
|
44
|
+
reason: 'removed_or_changed' | 'expired_or_unavailable';
|
|
45
|
+
} | {
|
|
46
|
+
available: true;
|
|
47
|
+
url: string;
|
|
48
|
+
media: {
|
|
49
|
+
assetId: string;
|
|
50
|
+
byteSize: number;
|
|
51
|
+
fileName: string;
|
|
52
|
+
mimeType: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export interface HistoryExportStatus {
|
|
56
|
+
jobId: string;
|
|
57
|
+
state: 'ready' | 'complete' | 'cancelled';
|
|
58
|
+
createdAt: string;
|
|
59
|
+
expiresAt: string;
|
|
60
|
+
format: 'robono-history-v1';
|
|
61
|
+
}
|
|
62
|
+
export interface HistoryExportPage extends HistoryExportStatus {
|
|
63
|
+
records: HistoryExportRecord[];
|
|
64
|
+
nextCursor: HistoryExportCursor | null;
|
|
65
|
+
done: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare function validExportCursor(c: unknown): c is HistoryExportCursor;
|
|
68
|
+
/** Server-only export. Contains private message data; never expose the manage key in a browser. */
|
|
69
|
+
export declare class RobonoHistoryExport {
|
|
70
|
+
private options;
|
|
71
|
+
private endpoint;
|
|
72
|
+
constructor(options: {
|
|
73
|
+
apiKey: string;
|
|
74
|
+
baseUrl?: string;
|
|
75
|
+
fetch?: typeof fetch;
|
|
76
|
+
});
|
|
77
|
+
private request;
|
|
78
|
+
start(input: HistoryExportSelection): Promise<HistoryExportStatus>;
|
|
79
|
+
status(jobId: string): Promise<HistoryExportStatus>;
|
|
80
|
+
cancel(jobId: string): Promise<HistoryExportStatus>;
|
|
81
|
+
page(jobId: string, cursor?: HistoryExportCursor): Promise<HistoryExportPage>;
|
|
82
|
+
/** A fresh grant for a retained managed attachment in this export job. Never returns customer-owned URLs. */
|
|
83
|
+
attachment(input: {
|
|
84
|
+
jobId: string;
|
|
85
|
+
conversationId: string;
|
|
86
|
+
messageId: string;
|
|
87
|
+
assetId: string;
|
|
88
|
+
}): Promise<HistoryExportAttachment>;
|
|
89
|
+
/** Yields one bounded page at a time. Persist nextCursor only after saving that page. */
|
|
90
|
+
pages(jobId: string, cursor?: HistoryExportCursor): AsyncGenerator<HistoryExportPage>;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=history-export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-export.d.ts","sourceRoot":"","sources":["../src/history-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { RobonoError } from './errors.js';
|
|
2
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3
|
+
export function validExportCursor(c) {
|
|
4
|
+
if (!c || typeof c !== 'object')
|
|
5
|
+
return false;
|
|
6
|
+
const v = c;
|
|
7
|
+
return Number.isInteger(v.conversationIndex) && v.conversationIndex >= 0 && v.conversationIndex < 25 && ['conversation', 'members', 'messages'].includes(v.phase) && typeof v.after === 'string' && v.after.length <= 200 && !/[\u0000-\u001f\u007f]/.test(v.after) && (v.phase !== 'messages' || /^[0-9]{1,19}$/.test(v.after) && BigInt(v.after) <= 9223372036854775807n);
|
|
8
|
+
}
|
|
9
|
+
/** Server-only export. Contains private message data; never expose the manage key in a browser. */
|
|
10
|
+
export class RobonoHistoryExport {
|
|
11
|
+
options;
|
|
12
|
+
endpoint;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
const url = new URL(options.baseUrl ?? 'https://api.robono.com/v1/messaging/export');
|
|
16
|
+
if (url.protocol !== 'https:' || url.username || url.password || !options.apiKey.trim())
|
|
17
|
+
throw Error('Use an HTTPS export endpoint and a server API key.');
|
|
18
|
+
this.endpoint = url.href;
|
|
19
|
+
}
|
|
20
|
+
async request(operation, body) {
|
|
21
|
+
if (!uuid.test(body.jobId))
|
|
22
|
+
throw Error('Use a UUID job ID, and keep it unchanged when retrying.');
|
|
23
|
+
const response = await (this.options.fetch ?? fetch)(this.endpoint, { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(120000), headers: { authorization: 'Bearer ' + this.options.apiKey, 'content-type': 'application/json' }, body: JSON.stringify({ ...body, operation }) });
|
|
24
|
+
const data = await response.json().catch(() => null);
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
throw new RobonoError(data?.error?.message ?? 'History export failed.', { status: response.status, code: data?.error?.code ?? 'history_export_failed', retryable: response.status === 429 || response.status >= 500 });
|
|
27
|
+
if (data?.jobId !== body.jobId || data.format !== 'robono-history-v1' || !['ready', 'complete', 'cancelled'].includes(data.state) || !Number.isFinite(Date.parse(data.createdAt)) || !Number.isFinite(Date.parse(data.expiresAt)))
|
|
28
|
+
throw Error('The export service returned an unexpected job.');
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
start(input) {
|
|
32
|
+
if (!Array.isArray(input.conversationIds) || input.conversationIds.length < 1 || input.conversationIds.length > 25 || input.conversationIds.some(id => typeof id !== 'string' || !uuid.test(id)))
|
|
33
|
+
throw Error('Select 1–25 conversation IDs.');
|
|
34
|
+
for (const date of [input.from, input.until])
|
|
35
|
+
if (date !== undefined && (!Number.isFinite(Date.parse(date)) || Date.parse(date) < 0 || Date.parse(date) > Date.now()))
|
|
36
|
+
throw Error('Use valid past export dates.');
|
|
37
|
+
if (input.from && input.until && Date.parse(input.from) >= Date.parse(input.until))
|
|
38
|
+
throw Error('The start date must precede the end date.');
|
|
39
|
+
return this.request('start', { ...input, conversationIds: [...new Set(input.conversationIds.map(id => id.toLowerCase()))].sort() });
|
|
40
|
+
}
|
|
41
|
+
status(jobId) { return this.request('status', { jobId }); }
|
|
42
|
+
cancel(jobId) { return this.request('cancel', { jobId }); }
|
|
43
|
+
async page(jobId, cursor) {
|
|
44
|
+
if (cursor !== undefined && !validExportCursor(cursor))
|
|
45
|
+
throw Error('Keep the returned export cursor unchanged.');
|
|
46
|
+
const data = await this.request('page', { jobId, ...(cursor ? { cursor } : {}) });
|
|
47
|
+
if (!Array.isArray(data.records) || data.records.length > 200 || typeof data.done !== 'boolean' || (data.done ? data.nextCursor !== null : !validExportCursor(data.nextCursor)) || data.records.some(r => !r || !['conversation', 'member', 'message'].includes(r.type)))
|
|
48
|
+
throw Error('The export service returned an invalid page.');
|
|
49
|
+
if (!data.done && JSON.stringify(data.nextCursor) === JSON.stringify(cursor))
|
|
50
|
+
throw Error('The export cursor did not advance.');
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
/** A fresh grant for a retained managed attachment in this export job. Never returns customer-owned URLs. */
|
|
54
|
+
async attachment(input) {
|
|
55
|
+
if (!uuid.test(input.conversationId) || !/^[1-9][0-9]{0,18}$/.test(input.messageId) || BigInt(input.messageId) > 9223372036854775807n || !input.assetId.startsWith('robono-r2:') || !uuid.test(input.assetId.slice(10)))
|
|
56
|
+
throw Error('Use the attachment identifiers from this export.');
|
|
57
|
+
const data = await this.request('asset', input);
|
|
58
|
+
if (data.available === false && ['removed_or_changed', 'expired_or_unavailable'].includes(data.reason))
|
|
59
|
+
return data;
|
|
60
|
+
if (data.available !== true || data.media?.assetId !== input.assetId || !Number.isSafeInteger(data.media.byteSize) || data.media.byteSize < 1 || data.media.byteSize > 104857600 || typeof data.media.fileName !== 'string' || typeof data.media.mimeType !== 'string')
|
|
61
|
+
throw Error('The export service returned an invalid attachment.');
|
|
62
|
+
const url = new URL(data.url);
|
|
63
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.hash)
|
|
64
|
+
throw Error('The export service returned an invalid transfer.');
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
/** Yields one bounded page at a time. Persist nextCursor only after saving that page. */
|
|
68
|
+
async *pages(jobId, cursor) {
|
|
69
|
+
while (true) {
|
|
70
|
+
const page = await this.page(jobId, cursor);
|
|
71
|
+
yield page;
|
|
72
|
+
if (page.done)
|
|
73
|
+
return;
|
|
74
|
+
cursor = page.nextCursor;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=history-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-export.js","sourceRoot":"","sources":["../src/history-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
export { validateHistoryImportFile } from './history-import-validation.js';
|
|
3
3
|
/** Private JSON export: never print message bodies or keys. Validation does not contact Robono. */
|
|
4
4
|
export declare function runHistoryImport(args: string[], env?: NodeJS.ProcessEnv, log?: (line: string) => void): Promise<void>;
|
|
5
|
-
export declare function validateHistoryImportFile(value: any): asserts value is {
|
|
6
|
-
jobId: string;
|
|
7
|
-
conversationId: string;
|
|
8
|
-
authors: string[];
|
|
9
|
-
messages: HistoryImportMessage[];
|
|
10
|
-
};
|
|
11
5
|
//# sourceMappingURL=history-import-cli.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history-import-cli.d.ts","sourceRoot":"","sources":["../src/history-import-cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"history-import-cli.d.ts","sourceRoot":"","sources":["../src/history-import-cli.ts"],"names":[],"mappings":";AAMA,OAAO,EAAC,yBAAyB,EAAC,MAAM,gCAAgC,CAAC;AACzE,mGAAmG;AACnG,wBAAsB,gBAAgB,CAAC,IAAI,EAAC,MAAM,EAAE,EAAC,GAAG,GAAC,MAAM,CAAC,UAAsB,EAAC,GAAG,GAAC,CAAC,IAAI,EAAC,MAAM,KAAG,IAAgB,iBAkBzH"}
|
|
@@ -3,6 +3,8 @@ import { readFile, stat } from 'node:fs/promises';
|
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { RobonoHistoryImport } from './history-import.js';
|
|
5
5
|
import { RobonoError } from './errors.js';
|
|
6
|
+
import { validateHistoryImportFile } from './history-import-validation.js';
|
|
7
|
+
export { validateHistoryImportFile } from './history-import-validation.js';
|
|
6
8
|
/** Private JSON export: never print message bodies or keys. Validation does not contact Robono. */
|
|
7
9
|
export async function runHistoryImport(args, env = process.env, log = console.log) {
|
|
8
10
|
const flags = new Set(args.filter(a => a === '--apply' || a === '--help'));
|
|
@@ -14,8 +16,8 @@ export async function runHistoryImport(args, env = process.env, log = console.lo
|
|
|
14
16
|
if (rest.length !== 2 || rest[0] !== '--file' || !rest[1])
|
|
15
17
|
throw Error('Use --file history.json, with --apply when ready.');
|
|
16
18
|
const info = await stat(rest[1]);
|
|
17
|
-
if (!info.isFile() || info.size >
|
|
18
|
-
throw Error('Use a JSON file no larger than
|
|
19
|
+
if (!info.isFile() || info.size > 268435456)
|
|
20
|
+
throw Error('Use a JSON file no larger than 256 MiB.');
|
|
19
21
|
let input;
|
|
20
22
|
try {
|
|
21
23
|
input = JSON.parse(await readFile(rest[1], 'utf8'));
|
|
@@ -47,37 +49,6 @@ export async function runHistoryImport(args, env = process.env, log = console.lo
|
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
|
-
export function validateHistoryImportFile(value) {
|
|
51
|
-
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
52
|
-
const id = (v) => typeof v === 'string' && v.length > 0 && v.length <= 200 && v === v.trim() && !/[\u0000-\u001f\u007f]/.test(v);
|
|
53
|
-
if (!value || !uuid.test(value.jobId) || !uuid.test(value.conversationId) || !Array.isArray(value.authors) || !value.authors.length || value.authors.length > 10000 || value.authors.some((a) => !id(a)) || new Set(value.authors).size !== value.authors.length)
|
|
54
|
-
throw Error('Provide UUID jobId and conversationId and a unique authors list.');
|
|
55
|
-
if (!Array.isArray(value.messages) || !value.messages.length || value.messages.length > 10000)
|
|
56
|
-
throw Error('Include 1–10,000 messages.');
|
|
57
|
-
const ids = new Map(), order = new Map();
|
|
58
|
-
for (const [index, message] of value.messages.entries()) {
|
|
59
|
-
const fail = (reason) => { throw Error(`Message ${index + 1}: ${reason}`); };
|
|
60
|
-
if (!message || !id(message.sourceId) || ids.has(message.sourceId) || !value.authors.includes(message.authorId))
|
|
61
|
-
fail('use a unique sourceId and an author from the authors list.');
|
|
62
|
-
const sent = Date.parse(message.sentAt);
|
|
63
|
-
if (typeof message.sentAt !== 'string' || !Number.isFinite(sent) || sent < 0 || sent > Date.now())
|
|
64
|
-
fail('sentAt must be a valid past date.');
|
|
65
|
-
const c = message.content;
|
|
66
|
-
if (!c || !['text', 'image', 'video', 'voice', 'document'].includes(c.kind) || Object.keys(c).some(k => !['kind', 'text', 'media'].includes(k)))
|
|
67
|
-
fail('provide supported content only.');
|
|
68
|
-
if (c.kind === 'text' && (typeof c.text !== 'string' || !c.text.trim() || c.text.length > 10000 || c.media))
|
|
69
|
-
fail('text must contain 1–10,000 characters.');
|
|
70
|
-
if (c.kind !== 'text' && (!c.media || typeof c.media.assetId !== 'string' || !c.media.assetId.startsWith('robono-r2:')))
|
|
71
|
-
fail('upload attachments privately through importer.upload first. Public source URLs are not accepted.');
|
|
72
|
-
if (message.replyToSourceId !== undefined && !id(message.replyToSourceId))
|
|
73
|
-
fail('replyToSourceId must be an original source ID.');
|
|
74
|
-
ids.set(message.sourceId, sent);
|
|
75
|
-
order.set(message.sourceId, index);
|
|
76
|
-
}
|
|
77
|
-
for (const [index, message] of value.messages.entries())
|
|
78
|
-
if (message.replyToSourceId !== undefined && (!ids.has(message.replyToSourceId) || ids.get(message.replyToSourceId) > Date.parse(message.sentAt) || message.replyToSourceId === message.sourceId || ids.get(message.replyToSourceId) === Date.parse(message.sentAt) && order.get(message.replyToSourceId) >= index))
|
|
79
|
-
throw Error(`Message ${index + 1}: the original reply target must exist and precede this message.`);
|
|
80
|
-
}
|
|
81
52
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
82
53
|
runHistoryImport(process.argv.slice(2)).catch(error => { console.error(error instanceof Error ? error.message : 'History import failed.'); process.exitCode = 1; });
|
|
83
54
|
//# sourceMappingURL=history-import-cli.js.map
|