mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-10 22:01:16 +00:00
fix: bot send confirmation, preview picker dialog nesting, upload button
- Bot: wait for updateMessageSendSucceeded/Failed before marking send complete (was returning on temp message, actual send was async) - Preview picker: move ArchivePreviewPicker outside parent Dialog to fix Radix nested dialog focus trap conflict - Upload: add explicit "Upload Preview" button always visible in the action bar alongside "Pick Preview" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,11 @@ export async function closeBotClient(): Promise<void> {
|
||||
*
|
||||
* The fromChatId is the Telegram chat ID from the DB (e.g. -1003767441152).
|
||||
* The messageId is the TDLib message ID stored in the DB.
|
||||
*
|
||||
* IMPORTANT: forwardMessages with send_copy returns a *temporary* message
|
||||
* synchronously. The actual file copy/send is asynchronous inside TDLib.
|
||||
* We must listen for updateMessageSendSucceeded / updateMessageSendFailed
|
||||
* to know whether the message actually reached the user.
|
||||
*/
|
||||
export async function copyMessageToUser(
|
||||
fromChatId: bigint,
|
||||
@@ -94,7 +99,73 @@ export async function copyMessageToUser(
|
||||
log.warn({ err, chatId: fromChatId.toString() }, "getChat failed for source channel");
|
||||
}
|
||||
|
||||
const result = await withFloodWait(
|
||||
// Wait for the actual send to complete, not just the temporary message.
|
||||
// Pattern mirrors worker/src/upload/channel.ts sendAndWaitForUpload.
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let settled = false;
|
||||
let tempMsgId: number | null = null;
|
||||
|
||||
// Timeout: 5 minutes for the copy to complete
|
||||
const TIMEOUT_MS = 5 * 60_000;
|
||||
const timer = setTimeout(() => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
`copyMessageToUser timed out after ${TIMEOUT_MS / 60_000}min ` +
|
||||
`(from=${fromChatId}, msg=${messageId}, to=${toUserId})`
|
||||
)
|
||||
);
|
||||
}
|
||||
}, TIMEOUT_MS);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleUpdate = (update: any) => {
|
||||
if (update?._ === "updateMessageSendSucceeded") {
|
||||
const oldMsgId = update.old_message_id;
|
||||
if (tempMsgId !== null && oldMsgId === tempMsgId) {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
const finalId = update.message?.id;
|
||||
log.info(
|
||||
{ tempMsgId, finalMsgId: finalId, toUserId: toUserId.toString() },
|
||||
"Message copy confirmed by Telegram"
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update?._ === "updateMessageSendFailed") {
|
||||
const oldMsgId = update.old_message_id;
|
||||
if (tempMsgId !== null && oldMsgId === tempMsgId) {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
const errorMsg = update.error?.message ?? "Unknown send error";
|
||||
const errorCode = update.error?.code ?? 0;
|
||||
log.error(
|
||||
{ tempMsgId, errorCode, errorMsg, toUserId: toUserId.toString() },
|
||||
"Message copy failed"
|
||||
);
|
||||
reject(new Error(`copyMessageToUser failed: [${errorCode}] ${errorMsg}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer);
|
||||
c.off("update", handleUpdate);
|
||||
};
|
||||
|
||||
// Attach listener BEFORE sending to avoid missing fast completions
|
||||
c.on("update", handleUpdate);
|
||||
|
||||
// Send the copy — returns a temporary message immediately
|
||||
withFloodWait(
|
||||
() =>
|
||||
c.invoke({
|
||||
_: "forwardMessages",
|
||||
@@ -105,14 +176,37 @@ export async function copyMessageToUser(
|
||||
remove_caption: false,
|
||||
}),
|
||||
"copyMessageToUser"
|
||||
)
|
||||
.then((result) => {
|
||||
// forwardMessages returns { messages: [tempMsg, ...] }
|
||||
const messages = (result as { messages?: Array<{ id: number }> })?.messages;
|
||||
if (messages && messages.length > 0 && messages[0]) {
|
||||
tempMsgId = messages[0].id;
|
||||
log.debug(
|
||||
{ tempMsgId, toUserId: toUserId.toString() },
|
||||
"forwardMessages returned temp message, waiting for send confirmation"
|
||||
);
|
||||
|
||||
// forwardMessages returns immediately with temp messages — check result
|
||||
const messages = (result as { messages?: unknown[] })?.messages;
|
||||
log.info(
|
||||
{ messageCount: messages?.length ?? 0, result: JSON.stringify(result).slice(0, 500) },
|
||||
"forwardMessages result"
|
||||
} else {
|
||||
// No temp message returned — likely an error in the API call itself
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
`forwardMessages returned no messages (result: ${JSON.stringify(result).slice(0, 300)})`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -335,6 +335,7 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw
|
||||
}, [filtered]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-2xl max-h-[80vh] flex flex-col gap-0 p-0">
|
||||
<DialogHeader className="px-6 pt-6 pb-4 border-b border-border space-y-3">
|
||||
@@ -408,6 +409,20 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw
|
||||
Pick Preview
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 gap-1.5 text-xs"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
)}
|
||||
Upload Preview
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -512,7 +527,9 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
|
||||
{/* Archive preview picker modal */}
|
||||
</Dialog>
|
||||
|
||||
{/* Archive preview picker modal — rendered as sibling to avoid nested Dialog issues */}
|
||||
{pkg && pkg.archiveType !== "DOCUMENT" && !pkg.isMultipart && (
|
||||
<ArchivePreviewPicker
|
||||
packageId={pkg.id}
|
||||
@@ -525,6 +542,6 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user