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;
})();