NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Factorial – Clock me in // @description Automatically fills in the shifts in a calendar month // @version 1.5 // @copyright 2019, jbrey // @author jbrey // @license MIT // @namespace http://openuserjs.org/users/jbrey // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js // @require https://gist.github.com/raw/2625891/waitForKeyElements.js // @match *://app.factorialhr.com/attendance/clock-in/* // @grant none // ==/UserScript== (function () { 'use strict'; const workingHoursSeed = 8; const workingHoursDeltaSeed = 0; const timeForLunchDeltaSeed = 40; const timeForLunchMin = 0.75; const morningStartFromHour = 8.70; const morningStartDeltaSeed = 15; const morningEndFromHour = 14; const morningEndDeltaSeed = 30; function getRandomDelta(delta) { const n = Math.floor(Math.random() * delta + 1); return Math.ceil(n / 5) * 5; } function getTimeInMins(time) { return Number.parseFloat((time / 60).toFixed(2)); } function getTimeString(hour, min) { return ('0' + hour).slice(-2) + ':' + ('0' + min).slice(-2); } function getMins(time) { const mins = time - Math.floor(time); return Math.floor(Number.parseFloat((mins * 60).toFixed(0)) / 5) * 5; } function getRandomTimings() { const workingHoursDelta = getRandomDelta(workingHoursDeltaSeed); const workingHoursDeltaMins = getTimeInMins(workingHoursDelta); const workingHours = workingHoursSeed + workingHoursDeltaMins; const timeForLunchDelta = getRandomDelta(timeForLunchDeltaSeed); const timeForLunchDeltaMins = getTimeInMins(timeForLunchDelta); const timeForLunch = timeForLunchMin + timeForLunchDeltaMins; const morningStartDelta = getRandomDelta(morningStartDeltaSeed); const morningStartDeltaMins = getTimeInMins(morningStartDelta); const morningStart = morningStartFromHour + morningStartDeltaMins; const morningEndDelta = getRandomDelta(morningEndDeltaSeed); const morningEndDeltaMins = getTimeInMins(morningEndDelta); const morningWorkingHours = Number.parseFloat(((morningEndFromHour + morningEndDeltaMins) - (morningStartFromHour + morningStartDeltaMins)).toFixed(2)); const eveStart = morningEndFromHour + morningEndDeltaMins + timeForLunch; const eveEnd = eveStart + (workingHours - morningWorkingHours); const eveWorkingHours = Number.parseFloat((eveEnd - eveStart).toFixed(2)); const morningStartStr = getTimeString(Math.floor(morningStart), getMins(morningStart)); const morningEndStr = getTimeString(morningEndFromHour, morningEndDelta); const eveStartStr = getTimeString(Math.floor(eveStart), getMins(eveStart)); const eveEndStr = getTimeString(Math.floor(eveEnd), getMins(eveEnd)); return [morningStartStr, morningEndStr, eveStartStr, eveEndStr] } // Calls the setter for an element in a component oriented framework like react // and dispatchs the associated event function setReactValue(element, value) { const { set: valueSetter } = Object.getOwnPropertyDescriptor(element, 'value') || {} const prototype = Object.getPrototypeOf(element) const { set: prototypeValueSetter } = Object.getOwnPropertyDescriptor(prototype, 'value') || {} if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { prototypeValueSetter.call(element, value) } else if (valueSetter) { valueSetter.call(element, value) } // dispatch the event to notify react element.dispatchEvent(new Event('change', { bubbles: true })); } // Iterates through the list of days to add the shifts function addShifts() { const days = Array.from(document.querySelectorAll('[class^="tr__"]')); // filter out bank holidays, vacations and weekends const workDays = days.filter(day => { return (/tbody/i.test(day.parentNode.tagName) && !/disabled/i.test(day.className) && !/holiday|vacaciones|permisos retribuidos/i.test(day.innerText)); }); // add shifts where necessary workDays.forEach(workDay => { // get button to add a shift const addShiftButton = workDay.querySelector('button'); // check how many shifts already exist const inputFields = workDay.querySelectorAll('input'); // if there are less than 2 shifts, add one if ((addShiftButton) && (inputFields.length < 4)) { workDay.querySelector('button').click(); } }); // add values to the shifts workDays.forEach(workDay => { // get inputs for a day const inputFields = workDay.querySelectorAll('input'); const dailySchedule = getRandomTimings(); // fill in shifts let index = 0; inputFields.forEach(inputField => { // enabled? if ((!inputField.disabled) && (!inputField.value)) { setReactValue(inputField, dailySchedule[index]); // TODO: find out why the value must be set twice for react to pick it up... !!! setReactValue(inputField, dailySchedule[index]); } // find the shift index = (index >= 3) ? 0 : index + 1; }); }); workDays.forEach(workDay => { // get all buttons for a workday const buttons = workDay.querySelectorAll('button'); // cycle through them buttons.forEach(button => { // is this the submit button and is it enabled? if ((button.innerText) && (/submit|guardar/i.test(button.innerText)) && (!button.disabled)) { button.click(); } }); }); } // Injects into the page a button that calls the logic of this script function setButton(button) { if ((button[0].innerText) && (/clock in|entrada/i.test(button[0].innerText))) { const container = button[0].parentNode.parentNode; const newContainer = button[0].parentNode.cloneNode(); const newButton = button[0].cloneNode(); newButton.insertAdjacentHTML( 'afterbegin', `<div class="_2U_uz_2-jO"><div class="box___nBFPS width_full___GXPW7 height_s32___1gzeu padding_x_s8___1Pgn7 padding_y_s4___1nJEZ border_radius_abs016___27iFP background_primary1000___YyQFE border_color_lighter000___JTTqK border_width_s2___wC6c4 border_style_solid___1kBV3"><div class="box___nBFPS padding_x_s2___BwA3m flex_direction_row___1rGE2 align_items_center___MjjdX"><div class="box___nBFPS padding_x_s4___2sUGX overflow_x_hidden___vhjK1"><span class="text___2TOkD size_200___2HuYx weight_semibold___2R9kP color_grey000___2seTn"><span class="_29A1e">Clock me in!</span>` ); newButton.setAttribute('id', 'clockmein'); newButton.addEventListener("click", addShifts, false); newContainer.appendChild(newButton); container.appendChild(newContainer); } } waitForKeyElements("button:contains('Entrada'), button:contains('Clock in')", setButton, false); })();