@gryt/core 0.29.0 → 0.30.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.
|
@@ -36,6 +36,10 @@ export interface ImportDeps {
|
|
|
36
36
|
transfersAtOnce?: number;
|
|
37
37
|
/** Send a big import's text as one sealed upload. Only for apps that read `import_bundle`. */
|
|
38
38
|
bundles?: boolean;
|
|
39
|
+
/** Whether this device's history already holds an entry, so a second run skips it. */
|
|
40
|
+
alreadyHas?: (entryId: string) => Promise<boolean>;
|
|
41
|
+
/** The server's file size limit, so a file over it is never sealed and sent to be refused. */
|
|
42
|
+
maxFileBytes?: number | null;
|
|
39
43
|
}
|
|
40
44
|
export interface ImportProgress {
|
|
41
45
|
messages: {
|
|
@@ -63,5 +67,5 @@ export interface ImportControl {
|
|
|
63
67
|
/** For a 429's Retry-After header. Only the seconds form: anything else gets the default wait. */
|
|
64
68
|
export declare function retryAfterMs(header: string | null): number;
|
|
65
69
|
export declare const importId: (discordId: string) => string;
|
|
66
|
-
/**
|
|
70
|
+
/** Text first, then 20 messages at a time: their files go straight through, then those messages go. */
|
|
67
71
|
export declare function runDiscordImport(messages: DiscordPackageMessage[], deps: ImportDeps, control: ImportControl, report: (p: ImportProgress) => void): Promise<ImportProgress>;
|
|
@@ -24,12 +24,16 @@ function nameOf(url) {
|
|
|
24
24
|
return "file";
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
27
|
+
/** Text first, then 20 messages at a time: their files go straight through, then those messages go. */
|
|
28
28
|
export async function runDiscordImport(messages, deps, control, report) {
|
|
29
|
-
const
|
|
29
|
+
const fresh = [];
|
|
30
|
+
for (const m of messages)
|
|
31
|
+
if (!(await deps.alreadyHas?.(importId(m.discordId))))
|
|
32
|
+
fresh.push(m);
|
|
33
|
+
const withFiles = fresh.filter((m) => m.attachments.length > 0);
|
|
30
34
|
const urls = [...new Set(withFiles.flatMap((m) => m.attachments))];
|
|
31
35
|
const p = {
|
|
32
|
-
messages: { done:
|
|
36
|
+
messages: { done: messages.length - fresh.length, total: messages.length },
|
|
33
37
|
downloaded: { done: 0, total: urls.length },
|
|
34
38
|
sent: { done: 0, total: urls.length },
|
|
35
39
|
missing: 0,
|
|
@@ -80,7 +84,7 @@ export async function runDiscordImport(messages, deps, control, report) {
|
|
|
80
84
|
await deps.sleep(SEND_GAP_MS);
|
|
81
85
|
}
|
|
82
86
|
};
|
|
83
|
-
const textOnly =
|
|
87
|
+
const textOnly = fresh.filter((m) => m.attachments.length === 0).map((m) => ({ id: importId(m.discordId), sentAt: m.sentAt, text: m.text }));
|
|
84
88
|
if (!(deps.bundles && textOnly.length > BUNDLE_ABOVE && !control.stopped() && (await sendBundle(textOnly))))
|
|
85
89
|
await sendAll(textOnly);
|
|
86
90
|
const missing = new Set();
|
|
@@ -98,74 +102,85 @@ export async function runDiscordImport(messages, deps, control, report) {
|
|
|
98
102
|
}
|
|
99
103
|
return null;
|
|
100
104
|
};
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!(await deps.store.has(url))) {
|
|
114
|
-
const file = await downloadFile(url);
|
|
115
|
-
if (file) {
|
|
105
|
+
const uploaded = new Map();
|
|
106
|
+
const tooLarge = new Set();
|
|
107
|
+
const finished = new Set();
|
|
108
|
+
/* The file is only kept if it can't go up now, so a stop or a failure never downloads it twice. */
|
|
109
|
+
const transfer = async (url) => {
|
|
110
|
+
if (finished.has(url))
|
|
111
|
+
return;
|
|
112
|
+
finished.add(url);
|
|
113
|
+
let file = await deps.store.take(url);
|
|
114
|
+
if (!file) {
|
|
115
|
+
file = await downloadFile(url);
|
|
116
|
+
if (file)
|
|
116
117
|
p.bytes += file.bytes.length;
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
118
|
+
else if (!control.stopped()) {
|
|
120
119
|
missing.add(url);
|
|
121
120
|
p.missing = missing.size;
|
|
122
121
|
}
|
|
123
122
|
}
|
|
123
|
+
if (control.stopped()) {
|
|
124
|
+
if (file)
|
|
125
|
+
await deps.store.put(url, file);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
124
128
|
p.downloaded.done++;
|
|
125
129
|
tick();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
if (file) {
|
|
131
|
+
let up;
|
|
132
|
+
try {
|
|
133
|
+
up = deps.maxFileBytes && file.bytes.length > deps.maxFileBytes ? { tooLarge: true } : await uploadFile({ name: nameOf(url), ...file });
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
await deps.store.put(url, file);
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
if (!up) {
|
|
140
|
+
await deps.store.put(url, file);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if ("tooLarge" in up) {
|
|
134
144
|
tooLarge.add(url);
|
|
135
145
|
p.tooLarge = tooLarge.size;
|
|
136
146
|
}
|
|
137
|
-
else
|
|
147
|
+
else
|
|
138
148
|
uploaded.set(url, up);
|
|
139
|
-
|
|
140
|
-
if (up)
|
|
141
|
-
await deps.store.drop(url);
|
|
149
|
+
await deps.store.drop(url);
|
|
142
150
|
}
|
|
143
151
|
p.sent.done++;
|
|
144
152
|
tick();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
for (
|
|
153
|
+
};
|
|
154
|
+
const atOnce = deps.transfersAtOnce ?? TRANSFERS_AT_ONCE;
|
|
155
|
+
for (let i = 0; i < withFiles.length && !control.stopped(); i += FILE_BATCH) {
|
|
156
|
+
const chunk = withFiles.slice(i, i + FILE_BATCH);
|
|
157
|
+
const todo = [...new Set(chunk.flatMap((m) => m.attachments))];
|
|
158
|
+
let next = 0;
|
|
159
|
+
await Promise.all(Array.from({ length: atOnce }, async () => {
|
|
160
|
+
while (!control.stopped()) {
|
|
161
|
+
await hold();
|
|
162
|
+
if (next >= todo.length)
|
|
163
|
+
break;
|
|
164
|
+
await transfer(todo[next++]);
|
|
165
|
+
}
|
|
166
|
+
}));
|
|
148
167
|
if (control.stopped())
|
|
149
168
|
break;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
pending = [];
|
|
165
|
-
}
|
|
169
|
+
await sendAll(chunk.map((m) => {
|
|
170
|
+
const attachments = {};
|
|
171
|
+
for (const url of m.attachments) {
|
|
172
|
+
const up = uploaded.get(url);
|
|
173
|
+
if (up)
|
|
174
|
+
attachments[up.id] = up.key;
|
|
175
|
+
}
|
|
176
|
+
const big = m.attachments.filter((u) => tooLarge.has(u)).length;
|
|
177
|
+
const lost = m.attachments.length - Object.keys(attachments).length - big;
|
|
178
|
+
const lostNote = lost ? `${lost === 1 ? "A file" : `${lost} files`} from Discord couldn’t be downloaded.` : "";
|
|
179
|
+
const bigNote = big ? `${big === 1 ? "A file" : `${big} files`} from Discord ${big === 1 ? "was" : "were"} too big for this server.` : "";
|
|
180
|
+
const text = [m.text, lostNote, bigNote].filter(Boolean).join("\n");
|
|
181
|
+
return { id: importId(m.discordId), sentAt: m.sentAt, text, ...(Object.keys(attachments).length ? { attachments } : {}) };
|
|
182
|
+
}));
|
|
166
183
|
}
|
|
167
|
-
if (pending.length)
|
|
168
|
-
await sendAll(pending);
|
|
169
184
|
tick();
|
|
170
185
|
return p;
|
|
171
186
|
}
|
package/package.json
CHANGED