Skip to content

Commit adb3282

Browse files
committed
Modularize/break out into functions and make more robust / dynamic button selector with fallbacks
1 parent 33757d8 commit adb3282

File tree

3 files changed

+1006
-378
lines changed

3 files changed

+1006
-378
lines changed

index.js

Lines changed: 279 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,243 @@ function truncate(str, n) {
99
return subString[0].slice(0, subString[0].lastIndexOf('.') + 1); // find nearest end of sentence
1010
}
1111

12+
// Function to get the correct input selector
13+
async function getInputSelector(page) {
14+
const placeholderText = 'To rewrite text, enter or paste it here and press "Paraphrase."';
15+
const inputSelectors = [
16+
'#inputText',
17+
'#paraphraser-input-box',
18+
`div[placeholder="${placeholderText}"]`,
19+
];
20+
21+
for (const selector of inputSelectors) {
22+
const exists = await page.$(selector) !== null;
23+
if (exists) {
24+
return selector; // Return the selector if found
25+
}
26+
}
27+
28+
console.error('Error: Unable to find a valid input selector.');
29+
return null; // Return null if no valid selector is found
30+
}
31+
32+
// Function to get the input field using the provided selector
33+
async function getInputField(page, inputSelector) {
34+
if (!inputSelector) {
35+
return null; // Return null if the inputSelector is not provided
36+
}
37+
38+
try {
39+
const input = await page.waitForSelector(inputSelector, { visible: true, timeout: 5000 });
40+
return input; // Return the input field if found
41+
} catch (error) {
42+
console.error(`Error: Unable to find the input field with the selector: ${inputSelector}`);
43+
return null; // Return null if the input field is not found
44+
}
45+
}
46+
47+
// Function to clear the input field
48+
async function clearInputField(page, inputSelector, inputField) {
49+
await inputField.click();
50+
await inputField.type(' ');
51+
52+
// Clear the text area using JavaScript
53+
await page.evaluate((selector) => {
54+
const inputElement = document.querySelector(selector);
55+
if (inputElement) {
56+
inputElement.textContent = '';
57+
if (inputElement.value) {
58+
inputElement.value = ''; // Clear value for input elements
59+
}
60+
}
61+
}, inputSelector);
62+
// console.log('Input cleared using JavaScript');
63+
64+
// Additional step to ensure complete clearing using keyboard commands
65+
await page.focus(inputSelector);
66+
await page.keyboard.down('Control');
67+
await page.keyboard.press('KeyA');
68+
await page.keyboard.up('Control');
69+
await page.keyboard.press('Backspace'); // Using 'Backspace' instead of 'Delete' for broader compatibility
70+
// console.log('Input cleared using keyboard commands');
71+
}
72+
73+
// Function to get inputContent
74+
function getInputContent(page, inputSelector) {
75+
return page.evaluate((selector) => {
76+
const inputElement = document.querySelector(selector);
77+
if (inputElement) {
78+
return inputElement.textContent;
79+
}
80+
return null;
81+
}, inputSelector);
82+
}
83+
84+
// Function to input text into the specified field
85+
async function inputString(page, inputSelector, inputField, text) {
86+
// Attempt to change textContent directly using JavaScript
87+
await page.evaluate((selector, textString) => {
88+
const inputElement = document.querySelector(selector);
89+
if (inputElement) {
90+
inputElement.textContent = textString;
91+
if (inputElement.value !== undefined) {
92+
inputElement.value = textString; // For input elements
93+
}
94+
}
95+
}, inputSelector, text);
96+
97+
// Attempt to set clipboard content and paste it
98+
await page.evaluate(async (textString) => {
99+
// eslint-disable-next-line no-undef
100+
await navigator.clipboard.writeText(textString);
101+
}, text);
102+
await page.focus(inputSelector);
103+
await page.keyboard.down('Control');
104+
await page.keyboard.press('KeyV');
105+
await page.keyboard.up('Control');
106+
107+
const inputContent = await getInputContent(page, inputSelector);
108+
if (inputContent !== text) {
109+
// Attempt to type it out
110+
inputField.type(text);
111+
}
112+
}
113+
114+
// Function to get the correct button selector
115+
async function getButtonSelector(page) {
116+
const buttonSelectors = [
117+
'button.quillArticleBtn',
118+
'[aria-label="Rephrase (Cmd + Return)"] button',
119+
'[aria-label="Paraphrase (Cmd + Return)"] button',
120+
"//div[contains(text(), 'Paraphrase') or contains(text(), 'Rephrase')]/ancestor::button",
121+
];
122+
123+
for (const selector of buttonSelectors) {
124+
let exists;
125+
if (selector.startsWith('//')) { // XPath selector
126+
exists = await page.$x(selector).length > 0;
127+
} else { // CSS selector
128+
exists = await page.$(selector) !== null;
129+
}
130+
if (exists) {
131+
return selector; // Return the selector if found
132+
}
133+
}
134+
135+
console.error('Error: Unable to find a valid button selector.');
136+
return null; // Return null if no valid selector is found
137+
}
138+
139+
// Function to click the appropriate button
140+
async function clickParaphraseButton(page, buttonSelector) {
141+
if (!buttonSelector) {
142+
return false; // Return false if the buttonSelector is not provided
143+
}
144+
145+
let button;
146+
if (buttonSelector.startsWith('//')) { // XPath selector
147+
const [firstButton] = await page.$x(buttonSelector);
148+
button = firstButton;
149+
} else { // CSS selector
150+
button = await page.$(buttonSelector);
151+
}
152+
153+
if (button) {
154+
await button.click();
155+
return true;
156+
}
157+
return false;
158+
}
159+
160+
// Function to check if the form is submitted
161+
async function waitForFormSubmission(page, buttonSelector) {
162+
try {
163+
// Wait for paraphrasing to complete - button to become enabled
164+
// await page.waitForSelector(`${buttonSelector}:not([disabled])`);
165+
166+
// await second div in buttonSelector to be removed from dom
167+
// await page.waitForSelector(`${buttonSelector} div:nth-child(2)`, { hidden: true });
168+
169+
// Implement the logic to check if the form is submitted
170+
// const isDisabled = await page.$eval(buttonSelector, (button) => button.disabled);
171+
// return isDisabled;
172+
173+
// Wait for the new div to appear within the button, indicating the process has started
174+
await page.waitForSelector(`${buttonSelector} div:nth-child(2)`, { visible: true });
175+
176+
// Then, wait for the new div to disappear, indicating the process has finished
177+
await page.waitForSelector(`${buttonSelector} div:nth-child(2)`, { hidden: true });
178+
179+
return true;
180+
} catch (error) {
181+
console.error('Error: Process did not complete in the expected time.');
182+
return false;
183+
}
184+
}
185+
186+
// Function to submit the form
187+
async function submitForm(page, buttonSelector) {
188+
/*
189+
// Try submitting the form using keyboard shortcut
190+
await page.focus(inputSelector);
191+
await page.keyboard.down('Control');
192+
await page.keyboard.press('Enter');
193+
await page.keyboard.up('Control');
194+
195+
// Check if the form submits
196+
const isSubmitted = await isProcessComplete(page, buttonSelector);
197+
if (isSubmitted) {
198+
return true;
199+
}
200+
*/
201+
202+
// Try clicking the paraphrase button as a fallback
203+
const isClicked = await clickParaphraseButton(page, buttonSelector);
204+
if (!isClicked) {
205+
console.log('Failed to find and click the paraphrase button.');
206+
return false;
207+
}
208+
209+
const isSubmitted = await waitForFormSubmission(page, buttonSelector);
210+
if (!isSubmitted) {
211+
return false;
212+
}
213+
214+
return true;
215+
}
216+
217+
// Function to get the output content
218+
async function getOutputContent(page, outputSelector) {
219+
try {
220+
const content = await page.evaluate((selector) => {
221+
const element = document.querySelector(selector);
222+
return element ? element.textContent : null;
223+
}, outputSelector);
224+
225+
if (content === null) {
226+
console.log('Output element not found or no content.');
227+
return null;
228+
}
229+
230+
return content;
231+
} catch (error) {
232+
console.error('Error retrieving output content:', error);
233+
return null;
234+
}
235+
}
236+
12237
async function quillbot(text) {
238+
let browser; // Declare browser outside of try-catch so it's accessible in finally
13239
try {
14-
// const inputSelector = 'div#inputText';
15-
// const inputSelector = 'div#paraphraser-input-box';
16-
const placeholderText = 'To rewrite text, enter or paste it here and press Paraphrase.';
17-
const inputSelector = `div[placeholder="${placeholderText}"]`;
18-
const buttonSelector = 'button.quillArticleBtn';
19-
const outputSelector = 'div#paraphraser-output-box';
240+
const outputSelector = '#paraphraser-output-box';
241+
20242
const numberOfCharacters = 125;
21243
let str = text.trim();
22244
const parts = [];
23245
let output = '';
24246

25247
// 125 words per paraphrase for a free account
248+
// So break up the text into parts of 125 words
26249
if (str.match(/(\w+)/g).length > numberOfCharacters) {
27250
while (str.match(/(\w+)/g).length > numberOfCharacters) {
28251
const part = truncate(str, numberOfCharacters).trim();
@@ -34,81 +257,79 @@ async function quillbot(text) {
34257
parts.push(str);
35258
}
36259

37-
const browser = await puppeteer.launch({ headless: true });
260+
browser = await puppeteer.launch({
261+
// headless: 'new',
262+
headless: false,
263+
});
38264

39265
const page = await browser.newPage();
40266
await page.goto('https://quillbot.com/', { waitUntil: 'networkidle0' });
41267

42268
// Wait for input
43-
const input = await page.waitForSelector(inputSelector);
269+
const inputSelector = await getInputSelector(page);
270+
const inputField = await getInputField(page, inputSelector);
271+
if (!inputField) {
272+
// Handle the case where the input field wasn't found
273+
console.log('Input field not found. Exiting script.');
274+
return; // Exit the function, browser will be closed in finally
275+
}
44276
console.log('Input found');
45277

278+
// Go through each part and paraphrase it
46279
for (let i = 0; i < parts.length; i += 1) {
47280
console.log('Paraphrasing part', i + 1, 'of', parts.length);
48281

49282
const part = parts[i];
50283

51-
// await page.evaluate((selector) => {
52-
// document.querySelector(selector).textContent = '';
53-
// }, inputSelector);
284+
// eslint-disable-next-line no-promise-executor-return
285+
await new Promise((resolve) => setTimeout(resolve, 1000));
286+
287+
// Clear the text area
288+
await clearInputField(page, inputSelector, inputField);
54289

55-
// Input the string in the text area
56-
/* await page.evaluate((selector, text) => {
57-
document.querySelector(selector).textContent = text;
58-
}, inputSelector, part); */
59-
// await input.type(part);
60-
// await page.focus(inputSelector);
61290
// eslint-disable-next-line no-promise-executor-return
62-
await new Promise((resolve) => setTimeout(resolve, 100));
63-
await input.click();
64-
await input.type(' ');
291+
await new Promise((resolve) => setTimeout(resolve, 1000));
292+
293+
// Input the string in the text area
294+
await inputString(page, inputSelector, inputField, part);
295+
65296
// eslint-disable-next-line no-promise-executor-return
66-
await new Promise((resolve) => setTimeout(resolve, 100));
67-
68-
await page.keyboard.down('Control');
69-
await page.keyboard.press('KeyA');
70-
await page.keyboard.up('Control');
71-
await page.keyboard.up('Delete');
72-
// Set clipboard content
73-
// eslint-disable-next-line no-loop-func
74-
await page.evaluate(async (textString) => {
75-
// eslint-disable-next-line no-undef
76-
await navigator.clipboard.writeText(textString);
77-
}, part);
78-
// await clipboardy.write(part);
79-
await page.keyboard.down('Control');
80-
await page.keyboard.press('KeyV');
81-
await page.keyboard.up('Control');
82-
// await input.type(' ');
83-
84-
// Generate the result
85-
await page.click(buttonSelector);
86-
87-
// wait for paraphrasing to complete - button to become enabled
88-
// await page.waitForSelector(`${buttonSelector}:not([disabled])`);
89-
// await second div in buttonSelector to be removed from dom
90-
await page.waitForSelector(`${buttonSelector} div:nth-child(2)`, { hidden: true });
91-
console.log('Paraphrasing complete');
92-
93-
const paraphrased = await page.evaluate(
94-
(selector) => document.querySelector(selector).textContent,
95-
outputSelector,
96-
);
97-
98-
output += paraphrased;
99-
}
297+
await new Promise((resolve) => setTimeout(resolve, 1000));
100298

101-
console.log('Paraphrased:');
102-
console.log(output); // Display result
299+
const buttonSelector = await getButtonSelector(page);
103300

104-
// await new Promise((resolve) => setTimeout(resolve, 20000));
301+
const isSubmitted = await submitForm(page, buttonSelector);
302+
if (!isSubmitted) {
303+
// Handle submission failure
304+
console.log('Form submission failed. Exiting script.');
305+
return;
306+
}
105307

106-
browser.close();
308+
// Get the paraphrased content
309+
const outputContent = await getOutputContent(page, outputSelector);
310+
if (outputContent) {
311+
output += outputContent;
312+
} else {
313+
// Handle the case where no output content is retrieved
314+
console.log('Output content not found. Exiting script.');
315+
return;
316+
}
107317

318+
console.log('Paraphrasing complete', i + 1, 'of', parts.length);
319+
320+
// eslint-disable-next-line no-promise-executor-return
321+
await new Promise((resolve) => setTimeout(resolve, 1000));
322+
}
323+
324+
console.log('Paraphrasing complete');
108325
return output;
109326
} catch (error) {
110327
console.log(`Error: ${error}`);
111328
return null;
329+
} finally {
330+
if (browser) {
331+
await browser.close();
332+
}
112333
}
113334
}
114335

0 commit comments

Comments
 (0)