UNPKG

worklog-cli

Version:

A CLI tool for managing worklogs and timesheets

213 lines (180 loc) 6.86 kB
// utils/form.js const puppeteer = require('puppeteer-extra'); const StealthPlugin = require('puppeteer-extra-plugin-stealth'); puppeteer.use(StealthPlugin()); const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); const FORM_URL = process.env.FORM_URL; const EMAIL_SELECTOR = 'input[type="email"]'; const TEXTAREA_SELECTOR = 'textarea'; const DROPDOWN_SELECTORS = [ 'div[role="listbox"]', '.docssharedWizSelectPaperselect', '.quantumWizMenuPaperselectDropDown', '[role="combobox"]', 'select' ]; function formatTasks(logs) { return Object.entries(logs).map(([project, tasks]) => `#${project}\n${tasks.map(t => `- ${t}`).join('\n')}` ).join('\n\n'); } // Updated dropdown handler for Google Forms: finds dropdown by label and selects an option robustly async function selectDropdownOption(page, labelText, optionText) { const dropdowns = await page.$$('div[role="listbox"], div[role="combobox"]'); for (const dropdown of dropdowns) { const labelHandle = await dropdown.evaluateHandle(el => { const label = el.closest('.Qr7Oae')?.querySelector('.M7eMe'); return label; }); if (labelHandle) { const label = await labelHandle.evaluate(el => el?.innerText || ''); if (label.includes(labelText)) { await dropdown.click(); await delay(500); const options = await page.$$('div[role="option"] span'); for (const option of options) { const text = await option.evaluate(el => el.innerText); if (text.includes(optionText)) { await option.click(); await delay(500); return; } } throw new Error(`Option "${optionText}" not found for "${labelText}"`); } } } throw new Error(`Dropdown with label "${labelText}" not found`); } // Improved duration calculation and filling function async function fillDurationFromFirstTask(page, startTimeStr, removeTimeStr) { if (!startTimeStr) { console.warn('No startTime provided to calculate duration'); return; } const timeRegexHHmm = /^(\d{2}):(\d{2})$/; // Parse startTimeStr const startMatch = startTimeStr.match(timeRegexHHmm); if (!startMatch) { console.warn('Invalid startTime format, expected HH:mm'); return; } const [_, shh, smm] = startMatch; // Parse removeTimeStr or default to 0 let rhh = 0, rmm = 0; if (removeTimeStr) { const removeMatch = removeTimeStr.match(timeRegexHHmm); if (removeMatch) { [, rhh, rmm] = removeMatch; rhh = parseInt(rhh); rmm = parseInt(rmm); } } const now = new Date(); let startTime = new Date(now); startTime.setHours(parseInt(shh), parseInt(smm), 0, 0); if (startTime > now) { startTime.setDate(startTime.getDate() - 1); } let diffMs = now - startTime; // Subtract removeTime from diffMs const removeMs = (rhh * 60 * 60 + rmm * 60) * 1000; diffMs = Math.max(0, diffMs - removeMs); const diffSeconds = Math.floor(diffMs / 1000); const hours = Math.floor(diffSeconds / 3600); const minutes = Math.floor((diffSeconds % 3600) / 60); const seconds = diffSeconds % 60; await page.evaluate(({ h, m, s }) => { const inputs = Array.from(document.querySelectorAll('input[type="number"]')); function setVal(label, val) { const input = inputs.find(i => i.getAttribute('aria-label') === label); if (input) { input.value = ''; input.focus(); input.value = val; ['input', 'change', 'blur'].forEach(evt => input.dispatchEvent(new Event(evt, { bubbles: true }))); } } setVal('Hours', h); setVal('Minutes', m); setVal('Seconds', s); }, { h: hours, m: minutes, s: seconds }); await delay(500); } async function submitForm({ logs, startTime, time }) { const browser = await puppeteer.launch({ headless: false, executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', userDataDir: '/Users/ramyalakhani/puppeteer-profiles/my-profile', args: [ '--profile-directory=' + process.env.PUPPETEER_PROFILE_DIR, '--no-sandbox', '--disable-setuid-sandbox', ], timeout: 60000, }); let page; try { const pages = await browser.pages(); page = pages[0] || await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); await page.goto(FORM_URL, { waitUntil: 'networkidle2', timeout: 60000 }); // Fill email await page.waitForSelector(EMAIL_SELECTOR, { visible: true }); await page.evaluate((selector) => { const input = document.querySelector(selector); if (input) input.value = ''; }, EMAIL_SELECTOR); await page.type(EMAIL_SELECTOR, process.env.EMAIL); // Fill logs await page.waitForSelector(TEXTAREA_SELECTOR, { visible: true }); await page.evaluate((selector) => { const textarea = document.querySelector(selector); if (textarea) textarea.value = ''; }, TEXTAREA_SELECTOR); await page.type(TEXTAREA_SELECTOR, formatTasks(logs)); // Set today's date const today = new Date().toISOString().split('T')[0]; // format: YYYY-MM-DD await page.evaluate((date) => { const dateInput = document.querySelector('input[type="date"]'); if (dateInput) { dateInput.value = date; dateInput.dispatchEvent(new Event('input', { bubbles: true })); } }, today); await delay(500); // Compute duration from first task's start time to now await fillDurationFromFirstTask(page, startTime, time); // Select project dropdown - "Other" await selectDropdownOption(page, "Project", process.env.PROJECT); await delay(1000); await selectDropdownOption(page, "Work Area", process.env.WORK_AREA); // Write formatted tasks to todaytasks.txt const fs = require('fs'); const path = require('path'); const formattedTaskText = formatTasks(logs); const filePath = path.join(require('os').homedir(), 'Desktop', 'todaytasks.txt'); fs.writeFileSync(filePath, `\nToday's Tasks:\n\n${formattedTaskText}`, 'utf-8'); console.log('✅ Form submission completed'); await delay(2000); // Click the submit button await page.evaluate(() => { const buttons = Array.from(document.querySelectorAll('div[role="button"] span')); const submitBtn = buttons.find(span => span.textContent.trim() === 'Submit'); if (submitBtn) { submitBtn.closest('div[role="button"]').click(); } }); await delay(4000); // Wait for submission to complete await browser.close(); } catch (error) { console.error('Form error:', error); if (page) { await page.screenshot({ path: 'form-error.png' }); } } finally { // Uncomment the next line to close the browser automatically // await browser.close(); } } module.exports = { submitForm };