Fix MAX bulk chat menu actions
This commit is contained in:
+52
-8
@@ -410,6 +410,7 @@ app.post("/chat/menu-probe", async (req, res) => {
|
||||
const labels = Array.isArray(req.body?.labels) && req.body.labels.length > 0
|
||||
? req.body.labels.map((label) => String(label || ""))
|
||||
: ["\u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0447\u0430\u0442", "\u0443\u0434\u0430\u043b\u0438\u0442\u044c", "delete chat", "delete conversation", "remove chat"];
|
||||
const probeAction = Boolean(req.body?.probeAction);
|
||||
if (!externalChatId) {
|
||||
return res.json({ success: false, error: "externalChatId is required.", chatUrl: null });
|
||||
}
|
||||
@@ -417,12 +418,21 @@ app.post("/chat/menu-probe", async (req, res) => {
|
||||
const result = await withPageLock(async () => {
|
||||
const p = await ensurePage("incoming");
|
||||
const menuOpened = await openSidebarChatMenu(p, externalChatId, chatUrl, labels);
|
||||
const actionClicked = menuOpened && probeAction
|
||||
? await clickActionByTextWithRetry(p, labels, 1500)
|
||||
: false;
|
||||
if (actionClicked) {
|
||||
await p.waitForTimeout(700);
|
||||
}
|
||||
const visibleActions = actionClicked ? await readVisibleActionTexts(p) : [];
|
||||
await p.keyboard.press("Escape").catch(() => {});
|
||||
await clearSidebarSearch(p);
|
||||
return {
|
||||
success: menuOpened,
|
||||
error: menuOpened ? null : "MAX chat menu was not opened.",
|
||||
chatUrl: await getCurrentChatUrl(p)
|
||||
chatUrl: await getCurrentChatUrl(p),
|
||||
actionClicked,
|
||||
visibleActions
|
||||
};
|
||||
}, "incoming");
|
||||
res.json(result);
|
||||
@@ -3264,6 +3274,8 @@ async function openSidebarChatMenu(p, externalChatId, chatUrl, expectedLabels) {
|
||||
if (!opened) {
|
||||
const menuTexts = await readVisibleActionTexts(p);
|
||||
console.warn(`[chat/menu] expected action labels not found; labels=${expectedLabels.join("|")} visible=${menuTexts.slice(0, 20).join("|")}`);
|
||||
await p.keyboard.press("Escape").catch(() => {});
|
||||
await p.waitForTimeout(150);
|
||||
}
|
||||
return opened;
|
||||
};
|
||||
@@ -4973,14 +4985,22 @@ async function applyChatMenuActionInMax(p, externalChatId, chatUrl, actionLabels
|
||||
return { success: false, error: `MAX ${actionName} menu was not opened.` };
|
||||
}
|
||||
|
||||
if (!(await clickActionByText(p, actionLabels))) {
|
||||
if (!(await clickActionByTextWithRetry(p, actionLabels, 1500))) {
|
||||
await p.keyboard.press("Escape").catch(() => {});
|
||||
await clearSidebarSearch(p);
|
||||
return { success: false, error: `MAX ${actionName} action was not found in the chat menu.` };
|
||||
}
|
||||
|
||||
await p.waitForTimeout(700);
|
||||
await clickActionByText(p, confirmLabels);
|
||||
if (!(await clickActionByTextWithRetry(p, confirmLabels, 3000))) {
|
||||
const menuTexts = await readVisibleActionTexts(p);
|
||||
await p.keyboard.press("Escape").catch(() => {});
|
||||
await clearSidebarSearch(p);
|
||||
return {
|
||||
success: false,
|
||||
error: `MAX ${actionName} confirmation was not found. Visible actions: ${menuTexts.slice(0, 12).join(" | ")}`
|
||||
};
|
||||
}
|
||||
await p.waitForTimeout(1200);
|
||||
await clearSidebarSearch(p);
|
||||
return { success: true, error: null };
|
||||
@@ -5336,12 +5356,24 @@ async function clickActionByText(p, labels) {
|
||||
element.getAttribute("title") ||
|
||||
""
|
||||
).trim().toLowerCase();
|
||||
const candidates = Array.from(document.querySelectorAll("button,[role='button'],[role='menuitem'],span,div"))
|
||||
.filter((element) => element instanceof HTMLElement && isVisible(element));
|
||||
const match = candidates.find((element) => {
|
||||
const scoreMatch = (element) => {
|
||||
const text = textOf(element);
|
||||
return text && normalizedLabels.some((label) => text === label || text.includes(label));
|
||||
});
|
||||
if (!text) return 0;
|
||||
if (normalizedLabels.some((label) => text === label)) return 4;
|
||||
if (normalizedLabels.some((label) => text.startsWith(label))) return 3;
|
||||
if (normalizedLabels.some((label) => text.includes(label))) return 2;
|
||||
return 0;
|
||||
};
|
||||
const interactive = Array.from(document.querySelectorAll("button,[role='button'],[role='menuitem']"))
|
||||
.filter((element) => element instanceof HTMLElement && isVisible(element));
|
||||
const leafTextNodes = Array.from(document.querySelectorAll("span,div"))
|
||||
.filter((element) => element instanceof HTMLElement && isVisible(element))
|
||||
.filter((element) => element.querySelector("button,[role='button'],[role='menuitem']") == null)
|
||||
.filter((element) => element.children.length <= 1);
|
||||
const match = [...interactive, ...leafTextNodes]
|
||||
.map((element, index) => ({ element, index, score: scoreMatch(element) }))
|
||||
.filter((item) => item.score > 0)
|
||||
.sort((a, b) => b.score - a.score || a.index - b.index)[0]?.element || null;
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
@@ -5352,6 +5384,18 @@ async function clickActionByText(p, labels) {
|
||||
}, labels).catch(() => false);
|
||||
}
|
||||
|
||||
async function clickActionByTextWithRetry(p, labels, timeoutMs = 2000) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() <= deadline) {
|
||||
if (await clickActionByText(p, labels)) {
|
||||
return true;
|
||||
}
|
||||
await p.waitForTimeout(200);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function clickReactionNearMessage(p, rect, emoji) {
|
||||
return await p.evaluate(({ targetRect, requestedEmoji }) => {
|
||||
const isVisible = (element) => {
|
||||
|
||||
Reference in New Issue
Block a user