- // ==UserScript==
- // @name Toffu
- // @namespace https://greasyfork.org/en/scripts/419870-toffu
- // @version 0.9
- // @description Autofills Woffu schedule
- // @author DonNadie
- // @match https://*.woffu.com/*
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- (async function() {
- 'use strict';
-
- let userToken;
- let departmentId;
- let scheduleId;
- let calendarId;
- let userId;
-
- const api = async (route, params, method) =>
- {
- const BASE_URL = "https://" + location.hostname + '/api/';
-
- if (typeof params === "undefined" || params == null) {
- params = {};
- }
- if (typeof method === "undefined" || method == null) {
- method = "get";
- }
-
- let request = {
- method: method,
- };
-
- let paramList;
-
- // json body or already a FormData
- if (params instanceof FormData || params instanceof URLSearchParams) {
- paramList = params;
- } else if (typeof params === "string") {
- request.headers = {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- };
- paramList = params;
- } else {
- if (method === "post") {
- paramList = new FormData();
- } else {
- paramList = new URLSearchParams();
- }
-
- for (let k in params) {
- if (!params.hasOwnProperty(k)) {continue;}
- paramList.append(k, params[k]);
- }
- }
-
- if (method === "post" || method === "put") {
- request.body = paramList;
- } else {
- route = route + "?" + paramList;
- }
-
- if (typeof request.headers === "undefined") {
- request.headers = {};
- }
- if (userToken) {
- request.headers['Authorization'] = "Bearer " + userToken;
- }
-
- return new Promise((resolve, reject) => {
- fetch(BASE_URL + route, request)
- .then(response => response.json().then(resolve))
- .catch(error => reject(error));
- });
- };
-
- const getUserToken = async () => {
- const response = await api('users/token');
-
- if (response) {
- return response.Token;
- }
- return null;
- };
-
- const parseUserToken = (token) => {
- token = token.split(".")[1];
- return JSON.parse(atob(token));
- };
-
- const getPresence = async (userId, start, end) => {
- return (await api('users/' + userId + '/diaries/presence', {
- fromDate: start,
- toDate: end,
- pageIndex: 0,
- pageSize: 31,
- }, "get")).Diaries;
- };
-
- const getDateRange = () => {
- let response = {};
- const inputs = document.querySelectorAll('.react-datepicker__input-container input');
- const format = understandDateFormat();
- [
- {
- val: inputs[0].value,
- field: "start",
- },
- {
- val: inputs[1].value,
- field: "end",
- }
- ].forEach(date => {
- const parts = date.val.split(format.separator);
- let parsedDate = {};
- parts.forEach((part, i) => {
- parsedDate[format.map[i]] = part;
- });
-
- response[date.field] = parsedDate.year + "-" + parsedDate.month + "-" + parsedDate.day;
- });
-
- return response;
- };
-
- const understandDateFormat = () => {
- const separator = new Date().toLocaleDateString().replaceAll(/\d/g, '').substr(0, 1);
- let map;
-
- if (getLang() === 'en') {
- map = {
- 0: "month",
- 1: "day",
- 2: "year"
- };
- } else {
- map = {
- 0: "day",
- 1: "month",
- 2: "year"
- };
- }
-
- return {
- separator,
- map
- };
- }
-
- const getLang = () => {
- // can't use document.lang as those retards take ages to properly set it
- return getAllH2Contents().includes('Mi Presencia') ? 'es' : 'en';
- }
-
- const getDayTemplate = (day) => {
- const date = day.Date.split('T')[0];
-
- const li = document.createElement('li');
- li.dataset.removable = true;
- li.style.marginLeft = "5px";
- li.innerHTML = `
- <div class="form-check">
- <input class="form-check-input" type="checkbox" value="` + day.DiaryId + `" data-date="` + date + `" id="autodate-` + date + `" checked>
- <label class="form-check-label" for="autodate-` + date + `" style="display: inline-block;">
- ` + date + `<span class="text-danger"> ` + day.DiffFormatted.Values[0] + `h</span>
- </label>
- </div>
- `;
-
- return li;
- }
-
- const calculateSlotTime = (start, end) => {
- const startDate = new Date();
- startDate.setHours(parseInt(start));
- startDate.setMinutes(parseInt(start.split(':')[1]));
-
- const endDate = new Date();
- endDate.setHours(parseInt(end));
- endDate.setMinutes(parseInt(end.split(':')[1]));
-
- return parseInt(Math.abs(endDate - startDate) / 36e5);
- }
-
- const createSlot = (start, end, order) => {
- return {
- "Motive":null,
- "In":{
- "Time": start,
- "new":true,
- "SignStatus":1,
- "SignType":3,
- "SignId":0,
- "AgreementEventId":null,
- "RequestId":null
- },
- "Out":{
- "Time": end,
- "new":true,
- "SignStatus":1,
- "SignType":3,
- "SignId":0,
- "AgreementEventId":null,
- "RequestId":null
- },
- "totalSlot": calculateSlotTime(start, end),
- "order": order
- };
- }
-
- const getTimeWindows = () => {
- const totalSlots = 2;
- const slots = [];
-
- for (let i = 1; i <= totalSlots; i++) {
- slots.push(createSlot(document.querySelector('[name="af-window-' + i + '-start"]').value, document.querySelector('[name="af-window-' + i + '-end"]').value, i));
- }
-
- return slots;
- };
-
- const submitAutofill = async (selectedDays, submitButton) => {
- if (selectedDays.length < 1) {
- return;
- }
-
- submitButton.setAttribute('disabled', 'disabled');
-
- const slots = getTimeWindows();
-
- for (const selectedDay of selectedDays) {
- const dairyId = selectedDay.value;
- const date = selectedDay.dataset.date;
- const params = {
- "DiaryId": dairyId,
- "UserId": userId,
- "Date": date + "T00:00:00.000",
- "DepartmentId": departmentId,
- "JobTitleId":null,
- "CalendarId": calendarId,
- "ScheduleId": scheduleId,
- "AgreementId":null,
- "TrueStartTime":null,
- "TrueEndTime":null,
- "TrueBreaksHours":1,
- "Accepted":false,
- "Comments":null,
- "Slots": slots
- };
-
- // one by one to prevent getting banned
- try {
- await api("diaries/" + dairyId + "/workday/slots/self", JSON.stringify(params), 'put');
- } catch (e) {console.log(e)}
- }
-
- alert("done, reload to actually check if it worked");
- submitButton.removeAttribute('disabled');
-
- };
-
- const getContainerTemplate = () => {
- const div = document.createElement('div');
- div.classList.add('dropdown');
- div.style.display = 'inline-block';
-
- div.innerHTML = `
- <style>
- .dropdown {
- left: 20%;
- top: 10px;
- position: absolute;
- z-index: 99999;
- }
- .text-danger {
- color: red;
- }
- .dropdown-menu {
- background-color: white;
- padding: 5px;
- border: 2px solid
- }
- .dropdown-menu:not(.show) {
- display: none;
- }
- </style>
- <ul class="dropdown-menu" style="overflow: auto; text-align: left">
- <li style="margin-left: 5px">
- <strong>Entrada - Salida</strong>
- <div>
- <input name="af-window-1-start" value="09:00" type="time" class="form-control" style="padding-right:0px">
- <input name="af-window-1-end" value="14:00" type="time" class="form-control" style="margin-left: 0px">
- </div>
- <div>
- <input name="af-window-2-start" value="16:00" type="time" class="form-control" style="padding-right:0px">
- <input name="af-window-2-end" value="19:00" type="time" class="form-control" style="margin-left: 0px">
- </div>
- <button class="btn btn-primary" type="button" style="width:100%">Autofill</button>
- <hr>
- </li>
- </ul>
- `;
- const submitButton = div.querySelector('button');
- submitButton.addEventListener('click', () => {
- submitAutofill(div.querySelectorAll('input:checked'), submitButton);
- });
- const ul = div.querySelector('ul');
-
- const button = document.createElement('button');
- button.classList.add('btn', 'btn-secondary', 'dropdown-toggle')
- button.type = "button";
- button.innerHTML = 'AutoFill <i class="fa fa-chevron-down"></i>';
- button.addEventListener('click', () => {
- ul.classList.toggle('show');
- });
-
- div.appendChild(button);
- div.appendChild(ul);
-
- return div;
- }
-
- const setGlobalData = (day) => {
- departmentId = day.DepartmentId;
- scheduleId = day.ScheduleId;
- calendarId = day.CalendarId;
- userId = day.UserId;
- };
-
- const removeOldEntries = () => {
- document.querySelectorAll('li[data-removable="true"]').forEach(el => {
- el.remove();
- });
- }
-
- const showUnfilledDays = async (ul) => {
- removeOldEntries();
- const user = parseUserToken(userToken);
- const dateRange = getDateRange();
- const days = await getPresence(user.UserId, dateRange.start, dateRange.end);
- const pendingDays = [];
-
- setGlobalData(days[0]);
-
- for (const day of days) {
- // we only want negative days
- if (parseInt(day.DiffFormatted.Values[0]) < 0 && !day.TrueStartTime && day.IsUserEditable) {
- pendingDays.push(day);
- }
- }
-
- for (const day of pendingDays) {
- const tpl = getDayTemplate(day);
-
- ul.appendChild(tpl);
- }
- };
-
- const getAllH2Contents = () => {
- let str = '';
- for (const el of document.querySelectorAll('h2')) {
- str += ' ' + el.innerText;
- }
- return str;
- }
-
- const onLoaded = async () => {
- if (document.querySelectorAll('.react-datepicker__input-container input').length < 2) {
- setTimeout(onLoaded, 1000 * 1)
- return;
- }
-
- userToken = await getUserToken();
-
- // not logged in
- if (!userToken) {
- return;
- }
-
- const container = getContainerTemplate();
-
- document.body.prepend(container);
-
- container.querySelector('button').addEventListener('click', () => {
- const ul = container.querySelector('ul');
- showUnfilledDays(ul);
- });
-
- };
-
- onLoaded();
- })();