worklog-cli
Version:
A CLI tool for managing worklogs and timesheets
213 lines (180 loc) • 6.86 kB
JavaScript
// 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 };