import type { Locator, Page } from "playwright"; import { humanDelay } from "@/lib/utils"; function normalizeDraftText(text: string): string { return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); } function isTextInserted(actual: string, expected: string): boolean { const a = normalizeDraftText(actual).trim(); const e = normalizeDraftText(expected).trim(); if (!e) return true; if (a === e) return true; if (a.length >= Math.min(e.length, 20)) return true; return a.includes(e.slice(0, Math.min(24, e.length))); } async function clearEditor(page: Page, editor: Locator) { await editor.scrollIntoViewIfNeeded().catch(() => undefined); await editor.click(); await humanDelay(120, 220); await page.keyboard.press("Meta+A"); await page.keyboard.press("Backspace"); await humanDelay(100, 180); } async function pasteTextIntoEditor(page: Page, editor: Locator, text: string): Promise { try { await page.context().grantPermissions(["clipboard-read", "clipboard-write"]); } catch { // ignore permission errors on some hosts } try { await page.evaluate(async (value) => { await navigator.clipboard.writeText(value); }, text); await editor.click(); await page.keyboard.press("Meta+v"); await humanDelay(250, 450); return true; } catch { return page.evaluate((value) => { const target = document.querySelector('[data-threadtools-editor="true"]') ?? document.querySelector('[role="dialog"] [contenteditable="true"]') ?? document.querySelector('[contenteditable="true"][role="textbox"]') ?? document.querySelector('[contenteditable="true"]'); if (!(target instanceof HTMLElement)) return false; target.focus(); const dataTransfer = new DataTransfer(); dataTransfer.setData("text/plain", value); const pasted = target.dispatchEvent( new ClipboardEvent("paste", { clipboardData: dataTransfer, bubbles: true, cancelable: true, }) ); return pasted !== false; }, text); } } async function typeWithLineBreaks(page: Page, text: string) { const normalized = normalizeDraftText(text); const paragraphs = normalized.split(/\n\n+/); for (let p = 0; p < paragraphs.length; p++) { if (p > 0) { await page.keyboard.press("Enter"); await humanDelay(40, 90); } const lines = paragraphs[p].split("\n"); for (let l = 0; l < lines.length; l++) { if (l > 0) { await page.keyboard.press("Shift+Enter"); await humanDelay(40, 90); } if (lines[l]) { await page.keyboard.insertText(lines[l]); await humanDelay(30, 70); } } } } export async function typeIntoEditor(page: Page, editor: Locator, text: string) { const normalized = normalizeDraftText(text); await clearEditor(page, editor); const pasted = await pasteTextIntoEditor(page, editor, normalized); if (pasted) { const value = await editor.innerText().catch(() => ""); if (isTextInserted(value, normalized)) return; } await clearEditor(page, editor); await typeWithLineBreaks(page, normalized); const value = await editor.innerText().catch(() => ""); if (!isTextInserted(value, normalized)) { await clearEditor(page, editor); await editor.click(); await page.keyboard.insertText(normalized); } }