mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
Fix worker getting stuck during sync: add timeouts, stuck detection, and safety limits
- Add invokeWithTimeout wrapper for TDLib API calls (2min timeout per call) - Add stuck detection to getChannelMessages: break if from_message_id doesn't advance - Add stuck detection to getTopicMessages: same protection for topic scanning - Add stuck detection to getForumTopicList: break if pagination offsets don't advance - Add max page limit (5000) to all scanning loops to prevent infinite pagination - Add mutex wait timeout (30min) to prevent indefinite blocking when holder hangs - Add cycle timeout (4h default, configurable via WORKER_CYCLE_TIMEOUT_MINUTES) - Fix end-of-page detection to use actual limit value instead of hardcoded 100 Co-authored-by: xCyanGrizzly <53275238+xCyanGrizzly@users.noreply.github.com>
This commit is contained in:
16
worker/dist/upload/channel.d.ts
vendored
Normal file
16
worker/dist/upload/channel.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Client } from "tdl";
|
||||
export interface UploadResult {
|
||||
messageId: bigint;
|
||||
}
|
||||
/**
|
||||
* Upload one or more files to a destination Telegram channel.
|
||||
* For multipart archives, each file is sent as a separate message.
|
||||
* Returns the **final** (server-assigned) message ID of the first uploaded message.
|
||||
*
|
||||
* IMPORTANT: `sendMessage` returns a *temporary* message immediately.
|
||||
* The actual file upload happens asynchronously in TDLib. We listen for
|
||||
* `updateMessageSendSucceeded` to get the real server-side message ID and
|
||||
* to make sure the upload is fully committed before we clean up temp files
|
||||
* or close the TDLib client (which would cancel pending uploads).
|
||||
*/
|
||||
export declare function uploadToChannel(client: Client, chatId: bigint, filePaths: string[], caption?: string): Promise<UploadResult>;
|
||||
137
worker/dist/upload/channel.js
vendored
Normal file
137
worker/dist/upload/channel.js
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
import path from "path";
|
||||
import { stat } from "fs/promises";
|
||||
import { config } from "../util/config.js";
|
||||
import { childLogger } from "../util/logger.js";
|
||||
const log = childLogger("upload");
|
||||
/**
|
||||
* Upload one or more files to a destination Telegram channel.
|
||||
* For multipart archives, each file is sent as a separate message.
|
||||
* Returns the **final** (server-assigned) message ID of the first uploaded message.
|
||||
*
|
||||
* IMPORTANT: `sendMessage` returns a *temporary* message immediately.
|
||||
* The actual file upload happens asynchronously in TDLib. We listen for
|
||||
* `updateMessageSendSucceeded` to get the real server-side message ID and
|
||||
* to make sure the upload is fully committed before we clean up temp files
|
||||
* or close the TDLib client (which would cancel pending uploads).
|
||||
*/
|
||||
export async function uploadToChannel(client, chatId, filePaths, caption) {
|
||||
let firstMessageId = null;
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
const filePath = filePaths[i];
|
||||
const fileCaption = i === 0 && caption ? caption : undefined;
|
||||
const fileName = path.basename(filePath);
|
||||
let fileSizeMB = 0;
|
||||
try {
|
||||
const s = await stat(filePath);
|
||||
fileSizeMB = Math.round(s.size / (1024 * 1024));
|
||||
}
|
||||
catch {
|
||||
// Non-critical
|
||||
}
|
||||
log.info({ chatId: Number(chatId), fileName, sizeMB: fileSizeMB, part: i + 1, total: filePaths.length }, "Uploading file to channel");
|
||||
const serverMsgId = await sendAndWaitForUpload(client, chatId, filePath, fileCaption, fileName, fileSizeMB);
|
||||
if (i === 0) {
|
||||
firstMessageId = serverMsgId;
|
||||
}
|
||||
// Rate limit delay between uploads
|
||||
if (i < filePaths.length - 1) {
|
||||
await sleep(config.apiDelayMs);
|
||||
}
|
||||
}
|
||||
if (firstMessageId === null) {
|
||||
throw new Error("Upload failed: no messages sent");
|
||||
}
|
||||
log.info({ chatId: Number(chatId), messageId: Number(firstMessageId), files: filePaths.length }, "All uploads confirmed by Telegram");
|
||||
return { messageId: firstMessageId };
|
||||
}
|
||||
/**
|
||||
* Send a single file message and wait for Telegram to confirm the upload.
|
||||
* Returns the final server-assigned message ID.
|
||||
*/
|
||||
async function sendAndWaitForUpload(client, chatId, filePath, caption, fileName, fileSizeMB) {
|
||||
// Send the message — this returns a temporary message immediately
|
||||
const tempMsg = (await client.invoke({
|
||||
_: "sendMessage",
|
||||
chat_id: Number(chatId),
|
||||
input_message_content: {
|
||||
_: "inputMessageDocument",
|
||||
document: {
|
||||
_: "inputFileLocal",
|
||||
path: filePath,
|
||||
},
|
||||
caption: caption
|
||||
? {
|
||||
_: "formattedText",
|
||||
text: caption,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
}));
|
||||
const tempMsgId = tempMsg.id;
|
||||
log.debug({ fileName, tempMsgId }, "Message queued, waiting for upload confirmation");
|
||||
// Wait for the actual upload to complete
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
let lastLoggedPercent = 0;
|
||||
// Timeout: 10 minutes per GB, minimum 10 minutes
|
||||
const timeoutMs = Math.max(10 * 60_000, (fileSizeMB / 1024) * 10 * 60_000);
|
||||
const timer = setTimeout(() => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(new Error(`Upload timed out after ${Math.round(timeoutMs / 60_000)}min for ${fileName}`));
|
||||
}
|
||||
}, timeoutMs);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleUpdate = (update) => {
|
||||
// Track upload progress via updateFile events
|
||||
if (update?._ === "updateFile") {
|
||||
const file = update.file;
|
||||
if (file?.remote?.is_uploading_active && file.expected_size > 0) {
|
||||
const uploaded = file.remote.uploaded_size ?? 0;
|
||||
const total = file.expected_size;
|
||||
const percent = Math.round((uploaded / total) * 100);
|
||||
if (percent >= lastLoggedPercent + 20) {
|
||||
lastLoggedPercent = percent - (percent % 20);
|
||||
log.info({ fileName, uploaded, total, percent: `${percent}%` }, "Upload progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
// The money event: upload succeeded, we get the final server message ID
|
||||
if (update?._ === "updateMessageSendSucceeded") {
|
||||
const msg = update.message;
|
||||
const oldMsgId = update.old_message_id;
|
||||
if (oldMsgId === tempMsgId) {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
const finalId = BigInt(msg.id);
|
||||
log.info({ fileName, tempMsgId, finalMsgId: Number(finalId) }, "Upload confirmed by Telegram");
|
||||
resolve(finalId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Upload failed
|
||||
if (update?._ === "updateMessageSendFailed") {
|
||||
const oldMsgId = update.old_message_id;
|
||||
if (oldMsgId === tempMsgId) {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
const errorMsg = update.error?.message ?? "Unknown upload error";
|
||||
reject(new Error(`Upload failed for ${fileName}: ${errorMsg}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer);
|
||||
client.off("update", handleUpdate);
|
||||
};
|
||||
client.on("update", handleUpdate);
|
||||
});
|
||||
}
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
//# sourceMappingURL=channel.js.map
|
||||
1
worker/dist/upload/channel.js.map
vendored
Normal file
1
worker/dist/upload/channel.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/upload/channel.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user