Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 28 additions & 21 deletions adapters/doubao/ask.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ domain: www.doubao.com
strategy: cookie
browser: true
timeoutSeconds: 180

args:
text:
positional: true
Expand All @@ -18,9 +17,7 @@ args:
type: int
default: 60
description: Max seconds to wait

columns: [Role, Text]

pipeline:
- navigate: https://www.doubao.com/chat
- wait: 2
Expand All @@ -29,10 +26,10 @@ pipeline:
const text = ${{ args.text | json }};
const timeout = ${{ args.timeout }} || 60;

// Fill composer and submit
// Find input - updated for Doubao frontend changes (2026-05)
const candidates = [
'textarea[data-testid="chat_input_input"]',
'textarea[placeholder*="发消息"]',
'textarea.semi-input-textarea',
'textarea[placeholder*=""]',
'textarea',
];
let composer = null;
Expand All @@ -41,7 +38,6 @@ pipeline:
if (node) { composer = node; break; }
}
if (!composer) return [{ Role: 'System', Text: 'Could not find Doubao input element' }];

composer.focus();
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
if (setter) setter.call(composer, text);
Expand All @@ -50,29 +46,40 @@ pipeline:
composer.dispatchEvent(new Event('change', { bubbles: true }));
await new Promise(r => setTimeout(r, 500));

// Click send or press Enter
const root = document.querySelector('[data-testid="chat_input"]') || document.body;
const buttons = Array.from(root.querySelectorAll('button, [role="button"]'));
const lastBtn = buttons[buttons.length - 1];
if (lastBtn) lastBtn.click();
else composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));

await new Promise(r => setTimeout(r, 1500));
// Click send button - updated: walk up from textarea to find SVG button
let container = composer.parentElement;
let depth = 0;
while (container && depth < 6) {
const btns = Array.from(container.querySelectorAll('button')).filter(b => {
const svg = b.querySelector('svg');
return svg && !b.disabled && b.offsetParent !== null;
});
if (btns.length > 0) {
const rightmost = btns.reduce((a, b) => a.offsetLeft > b.offsetLeft ? a : b);
rightmost.click();
break;
}
container = container.parentElement;
depth++;
}

// Poll for response
// Poll for response using updated selectors (2026-05)
await new Promise(r => setTimeout(r, 3000));
const pollInterval = 2;
const maxPolls = Math.ceil(timeout / pollInterval);
let lastCandidate = '';
let stableCount = 0;

for (let i = 0; i < maxPolls; i++) {
await new Promise(r => setTimeout(r, pollInterval * 1000));
const msgs = document.querySelectorAll('[data-testid="receive_message"] [data-testid="message_text_content"]');
if (msgs.length === 0) continue;
const lastMsg = msgs[msgs.length - 1];
const candidate = (lastMsg.innerText || '').trim();
const blocks = Array.from(document.querySelectorAll('[data-plugin-identifier*="block_type:10000"]'));
const assistantBlocks = blocks.filter(b => !b.className?.toString().includes('justify-end'));
if (assistantBlocks.length === 0) continue;
const lastBlock = assistantBlocks[assistantBlocks.length - 1];
const candidate = (lastBlock.innerText || '').trim();
if (!candidate) continue;
if (candidate === lastCandidate) { stableCount++; } else { lastCandidate = candidate; stableCount = 1; }
if (candidate === lastCandidate) { stableCount++; }
else { lastCandidate = candidate; stableCount = 1; }
if (stableCount >= 2) break;
}

Expand Down
41 changes: 24 additions & 17 deletions adapters/doubao/read.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,35 @@ browser: true
columns: [Role, Text]

pipeline:
- navigate: https://www.doubao.com/chat
- wait: 2
- wait: 1
- evaluate: |
(function() {
(async () => {
const clean = (value) => (value || '').replace(/\u00a0/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
const messageList = document.querySelector('[data-testid="message-list"]');
if (!messageList) return [{ Role: 'System', Text: 'No visible Doubao messages were found.' }];

const roots = Array.from(messageList.querySelectorAll('[data-testid="union_message"]'));
if (roots.length === 0) return [{ Role: 'System', Text: 'No visible Doubao messages were found.' }];
// If on homepage, click first chat item to enter a conversation
if (!window.location.href.match(/\/chat\/\d+/)) {
const chatItems = document.querySelectorAll('[class*="chat-item"]');
if (chatItems.length > 0) {
chatItems[0].click();
await new Promise(r => setTimeout(r, 3000));
}
}

// Find all message blocks (block_type:10000 = user/assistant messages)
const blocks = Array.from(document.querySelectorAll('[data-plugin-identifier*="block_type:10000"]'));

if (blocks.length === 0) return [{ Role: 'System', Text: 'No messages found.' }];

const turns = [];
for (const el of roots) {
let role = '';
if (el.querySelector('[data-testid="send_message"]')) role = 'User';
else if (el.querySelector('[data-testid="receive_message"]')) role = 'Assistant';
if (!role) continue;
const textEl = el.querySelector('[data-testid="message_text_content"]');
const text = textEl ? clean(textEl.innerText) : clean(el.innerText);
if (text) turns.push({ Role: role, Text: text });
for (const block of blocks) {
const text = clean(block.innerText);
if (!text || text.length < 2) continue;

// User messages are right-aligned (justify-end), assistant messages are not
const cls = block.className?.toString() || '';
const role = cls.includes('justify-end') ? 'User' : 'Assistant';
turns.push({ Role: role, Text: text });
}

if (turns.length === 0) return [{ Role: 'System', Text: 'No visible Doubao messages were found.' }];
return turns;
return turns.length > 0 ? turns : [{ Role: 'System', Text: 'No readable messages.' }];
})()
36 changes: 24 additions & 12 deletions adapters/doubao/send.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,23 @@ description: Send a message to Doubao web chat
domain: www.doubao.com
strategy: cookie
browser: true

args:
text:
positional: true
type: string
required: true
description: Message to send

columns: [Status, SubmittedBy, InjectedText]

pipeline:
- navigate: https://www.doubao.com/chat
- wait: 2
- wait: 1
- evaluate: |
(async () => {
const text = ${{ args.text | json }};

// Find input textarea - updated for Doubao frontend changes (2026-05)
const candidates = [
'textarea[data-testid="chat_input_input"]',
'textarea[placeholder*="发消息"]',
'textarea.semi-input-textarea',
'textarea[placeholder*=""]',
'textarea',
];
let composer = null;
Expand All @@ -33,7 +31,6 @@ pipeline:
if (node) { composer = node; break; }
}
if (!composer) return [{ Status: 'Error', SubmittedBy: 'none', InjectedText: 'Could not find Doubao input' }];

composer.focus();
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
if (setter) setter.call(composer, text);
Expand All @@ -42,9 +39,24 @@ pipeline:
composer.dispatchEvent(new Event('change', { bubbles: true }));
await new Promise(r => setTimeout(r, 500));

const root = document.querySelector('[data-testid="chat_input"]') || document.body;
const buttons = Array.from(root.querySelectorAll('button, [role="button"]'));
const lastBtn = buttons[buttons.length - 1];
if (lastBtn) { lastBtn.click(); return [{ Status: 'Success', SubmittedBy: 'button', InjectedText: text }]; }
// Find send button: walk up from textarea, find buttons with SVG icon
let container = composer.parentElement;
let depth = 0;
while (container && depth < 6) {
const btns = Array.from(container.querySelectorAll('button')).filter(b => {
const svg = b.querySelector('svg');
return svg && !b.disabled && b.offsetParent !== null;
});
if (btns.length > 0) {
const rightmost = btns.reduce((a, b) => a.offsetLeft > b.offsetLeft ? a : b);
rightmost.click();
return [{ Status: 'Success', SubmittedBy: 'button', InjectedText: text }];
}
container = container.parentElement;
depth++;
}

// Fallback: press Enter
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
return [{ Status: 'Success', SubmittedBy: 'enter', InjectedText: text }];
})()