NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
/* ___ _____ __________ / | / ___// ____/ __ \ File: 42SlotSniper.user.js / /| | \__ \/ __/ / / / / Created: Monday, 30th December 2019 3:29 pm / ___ |___/ / /___/ /_/ / Author: Alexandre SEO (aseo@student.42.fr) /_/ |_/____/_____/\____/ */ // ==UserScript== // @name 42 Slot Sniper // @version 1.4.4 // @include https://projects.intra.42.fr/projects/*/slots* // @run-at document-idle // @license GPL-3.0-or-later // @author aseo // @updateURL https://openuserjs.org/meta/DontBreakAlex/42_Slot_Sniper.meta.js // @downloadURL https://openuserjs.org/install/DontBreakAlex/42_Slot_Sniper.user.js // ==/UserScript== /** * Add a style element to the document head and insert css into it * @param {string} css The CSS content */ (css => { let head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } style = document.createElement('style'); style.type = 'text/css', style.innerHTML = css; head.appendChild(style); })('@keyframes pulse{0%{box-shadow:0 0 4px 1px #78c5d5}25%{box-shadow:0 0 4px 1px #79c268}50%{box-shadow:0 0 4px 1px #f5d63d}75%{box-shadow:0 0 4px 1px #e868a1}100%{box-shadow:0 0 4px 1px #bf63a6}}@keyframes pulse-bg{0%{background-color:#78c5d5}25%{background-color:#79c268}50%{background-color:#f5d63d}75%{background-color:#e868a1}100%{background-color:#bf63a6}}.is-loading{animation-name:pulse,pulse-bg;animation-duration:2s;animation-timing-function:ease-in-out;animation-direction:alternate;animation-iteration-count:infinite;color:white;border-style:hidden;}'); class Sniper { constructor(period, discord) { let date = new Date(); this.team = document.getElementById("calendar").dataset.indexUrl.split("=")[1]; this.project = /projects\/(.+)\//.exec(window.location.pathname)[1]; this.login = document.querySelector("span[data-login]").dataset.login; this.button = document.createElement("button"); this.begin = date.toISOString().slice(0, 10); date.setDate(date.getDate() + 3); this.end = date.toISOString().slice(0, 10); this.running = false; this.button.textContent = "SNIPER"; this.button.addEventListener("click", () => { if (this.running == false) this.bind(); else this.stop(); }); this.ignored = []; document.querySelector("div.fc-left").appendChild(this.button); } bind() { this.running = true; this.period = getSetting('period'); this.discord = getSetting('discord'); this.interval = setInterval(this.snipe.bind(this), this.period); this.snipe(); this.button.classList.add("is-loading"); this.button.textContent = "SNIPING"; } stop() { console.debug("SNIPING STOPPED !"); clearInterval(this.interval); this.button.textContent = "SNIPER"; this.button.classList.remove("is-loading"); this.running = false; } async snipe() { let response = await fetch( `https://projects.intra.42.fr/projects/${this.project}/slots.json?team_id=${this.team}&start=${this.begin}&end=${this.end}`, { "credentials": "include", "headers": { "Accept": "application/json, text/javascript, */*; q=0.01", "Accept-Language": "en-US,en;q=0.5", "X-CSRF-Token": document.querySelector("meta[name='csrf-token']").content, "X-Requested-With": "XMLHttpRequest", }, "method": "GET", "mode": "cors" }); response = await response.json(); if (response.length) { let prom = this.getCurrentSlots(); let slots = response.map(elem => { return new Slot(elem); }).sort((a, b) => { return a.timeSlots[0].date - b.timeSlots[0].date; }); await prom; for (let elem of slots) { let slot = elem.findSlot(this.corrections); if (slot && this.ignored.indexOf(slot.id) < 0) { let src = 'https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3'; let audio = new Audio(src); audio.play(); let message = "Found slot for " + slot.date.toLocaleString() + "\nDo you want to take it ?"; this.logMessage(`Found slot for ${slot.date.toLocaleString()}`); let hour = slot.date.getHours(); let today = new Date(); let day = slot.date.getDate(); let month = slot.date.getMonth(); if ( (hour >= 10 && hour <= 20 && day == today.getDate() && month == today.getMonth()) || window.confirm(message) ) { elem.takeSlot(this, slot.id); this.logMessage(`Took slot for ${slot.date.toLocaleString()}`); this.stop(); } else { this.ignored.push(slot.id); } return; } } console.info("Cannot take any slot because you already have corrections at the same time") } } async getCurrentSlots() { let parser = new DOMParser(); let page = await fetch(`https://projects.intra.42.fr/${this.project}/${this.login}`); page = await page.text(); page = parser.parseFromString(page, 'text/html'); let slots = page.querySelectorAll("div.time span[data-long-date]"); if (slots.length) { this.corrections = Array.from(slots).map(elem => { return new Date(elem.dataset.longDate.slice(0, -6)); }) } else this.corrections = []; } async logMessage(message) { console.log(message); if (this.discord) { return fetch(this.discord, { headers: { "Content-Type": "application/json" }, method: "POST", body: `{"content":"${message}"}` }); } } } class Slot { constructor(array) { let begin = new Date(array.start), end = new Date(array.end); let parsedArray = array.ids.split(','); let duration = (end - begin) / parsedArray.length; this.timeSlots = parsedArray.map((elem, index) => { return { date: new Date(begin.getTime() + duration * index), id: elem }; }) } takeSlot(sniper, id) { fetch( `https://projects.intra.42.fr/projects/${sniper.project}/slots/${id}.json?team_id=${sniper.team}`, { "credentials": "include", "headers": { "Accept": "application/json, text/javascript, */*; q=0.01", "Accept-Language": "en-US,en;q=0.5", "X-CSRF-Token": document.querySelector("meta[name='csrf-token']").content, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "XMLHttpRequest" }, "body": `start=${sniper.begin}&end=${sniper.end}&_method=put`, "method": "POST", "mode": "cors" }); } takeFirstSlot(sniper) { this.takeSlot(sniper, this.timeSlots[0].id); } findSlot(corrections) { for (let elem of this.timeSlots) { if (!corrections.includes(elem.date)) return elem; } return false; } } const defaultValues = { period: 30000, discord: null } function getSetting(name) { return window.localStorage.getItem(name) || defaultValues[name]; } (async () => { let sniper = new Sniper; })();