MAPSTUDY Comment Bot

Auto-comment & delete in MAPSTUDY

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         MAPSTUDY Comment Bot
// @namespace    http://tampermonkey.net/
// @version      0.0.0.0.5
// @description  Auto-comment & delete in MAPSTUDY
// @ Note:If the bot isn't detecting lesson/event properly, try opening DevTools (F12), then refresh the page
// @icon         https://mapstudy.edu.vn/assets/images/logo/logo-64.png
// @author       Quang
// @license      MIT
// @match        https://mapstudy.edu.vn/*
// @grant        GM_xmlhttpRequest
// @connect      api.mapstudy.edu.vn
// ==/UserScript==

(function () {
    'use strict';
    //Remove bottom line if you understand
    alert("If the bot isn't detecting lesson/event properly, try opening DevTools (F12), then refresh the page.");
    //Remove above line if you understand
    let detectedCourseId = null; let type = null; const userId = 410039; const maxTeacherId = 42; const AUTH_TOKEN = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQxMDAzOSwic2Vzc2lvbiI6IjE3NTU2Njc4NTU3MzgiLCJpYXQiOjE3NTU2Njc4NTUsImV4cCI6MTc1NTg2ODI1NX0.K72ufWffzyOKzvxXapeI5dLQw2e041nWwl0AB-kjrm8'; const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...rest) { const match = url.match(/\/course\/(\d+)\//); const matchLesson = url.match(/\/lesson\/(\d+)\//); const matchEvent = url.match(/\/event-comment\/(\d+)/); if (match && match[1]) { detectedCourseId = parseInt(match[1], 10); console.log('[BOT] Detected courseId:', detectedCourseId); type = 'course'; } if (matchLesson && matchLesson[1]) { detectedCourseId = parseInt(matchLesson[1], 10); console.log('[BOT] Detected lessonId:', detectedCourseId); type = 'lesson'; } if (matchEvent && matchEvent[1]) { detectedCourseId = parseInt(matchEvent[1], 10); console.log('[BOT] Detected eventId:', detectedCourseId); type = 'event-comment'; } return originalOpen.call(this, method, url, ...rest); }; const openBtn = document.createElement('button'); openBtn.textContent = 'Open'; openBtn.style.cssText = ` position: fixed; display:none; top: 0; left: 0; z-index: 1100; border-radius: 0.5em; padding: 0.5em; background: #000000b5; color: #8dff92; backdrop-filter: blur(0.5em); cursor:grab; pointer-events: all !important; `; openBtn.onclick = () => { container.style.display = 'block'; openBtn.style.display = 'none'; }; const container = document.createElement('div'); container.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.5); color: white; backdrop-filter: blur(0.5em); padding: 1em; border-radius: 1em; text-align: center; user-select: none; z-index:1000; cursor:grab; pointer-events: all !important; `; const closeBtn = document.createElement('button'); closeBtn.textContent = 'Close'; closeBtn.style.cssText = `color: #ffb8b8;`; closeBtn.onclick = () => { container.style.display = 'none'; openBtn.style.display = 'block'; }; const title = document.createElement('h1'); title.textContent = 'MAPSTUDY COMMENT BOT'; const inputComment = document.createElement('input'); inputComment.placeholder = 'Enter comment (e.g. Hello)'; inputComment.style.cssText = ` background: transparent; border: 0.1em solid; display: block; padding: 0.5em; margin: 0.3em auto 1em auto; color: white; `; const inputAmount = document.createElement('input'); inputAmount.placeholder = 'Amount to send (Bugs)'; inputAmount.style.cssText = inputComment.style.cssText; const inputTeacher = document.createElement('input'); inputTeacher.placeholder = 'TeacherId (random default)'; inputTeacher.style.cssText = inputComment.style.cssText; const inputDate = document.createElement('input'); inputDate.type = 'datetime-local'; inputDate.style.cssText = inputComment.style.cssText; const selectTheme = document.createElement('select'); const placeholderOption = document.createElement('option'); placeholderOption.text = 'Select theme'; placeholderOption.value = ''; placeholderOption.disabled = true; placeholderOption.selected = true; selectTheme.appendChild(placeholderOption); const options = ['default', 'gradient-text','COMMENT_UNIVERSE', 'COMMENT_BACK_TO_SCHOOL_2025', 'COMMENT_NATIONAL_DAY_2025', 'COMMENT_BIRTHDAY_2025']; options.forEach(text => { const option = document.createElement('option'); option.value = text; option.text = text; selectTheme.appendChild(option); }); selectTheme.style.cssText = inputComment.style.cssText; selectTheme.style.color = 'red'; const btnContainer = document.createElement('div'); btnContainer.style.cssText = `text-align: center; margin: 0.5em 0;`; const sendBtn = document.createElement('button'); sendBtn.textContent = 'Send now'; sendBtn.style.cssText = `padding: 0.5em; border: 0.1em solid; margin: 0 0.5em;`; const deleteBtn = document.createElement('button'); deleteBtn.textContent = 'Delete All'; deleteBtn.style.cssText = sendBtn.style.cssText; btnContainer.appendChild(sendBtn); btnContainer.appendChild(deleteBtn); container.appendChild(closeBtn); container.appendChild(title); container.appendChild(inputComment); container.appendChild(inputAmount); container.appendChild(inputTeacher); container.appendChild(inputDate); container.appendChild(selectTheme); container.appendChild(btnContainer); document.body.appendChild(container); document.body.appendChild(openBtn); const style = document.createElement('style'); style.innerHTML = ` input::placeholder { color: #ccc; font-style: italic; } `; document.body.appendChild(style); let isDragging = false; let offsetX, offsetY; container.addEventListener("mousedown", (e) => { isDragging = true; offsetX = e.clientX - container.offsetLeft; offsetY = e.clientY - container.offsetTop; container.style.cursor = 'grabbing'; container.style.opacity = 0.7; document.body.style.pointerEvents = 'none'; }); container.addEventListener("mousemove", (e) => { if (isDragging) { container.style.left = (e.clientX - offsetX) + "px"; container.style.top = (e.clientY - offsetY) + "px"; }; }); container.addEventListener("mouseup", () => { isDragging = false; container.style.cursor = 'grab'; container.style.opacity = 1; document.body.style.pointerEvents = 'all'; }); openBtn.addEventListener("mousedown", (e) => { isDragging = true; offsetX = e.clientX - openBtn.offsetLeft; offsetY = e.clientY - openBtn.offsetTop; openBtn.style.cursor = 'grabbing'; openBtn.style.opacity = 0.7; document.body.style.pointerEvents = 'none'; }); openBtn.addEventListener("mousemove", (e) => { if (isDragging) { openBtn.style.left = (e.clientX - offsetX) + "px"; openBtn.style.top = (e.clientY - offsetY) + "px"; }; }); openBtn.addEventListener("mouseup", () => { isDragging = false; openBtn.style.cursor = 'grab'; openBtn.style.opacity = 1; document.body.style.pointerEvents = 'all'; }); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; async function sendComments(message, count, countTeach, date, theme) { if (!detectedCourseId) { alert('Course ID not detected. Interact with the course page first.'); return; }; function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; if(countTeach!== null && countTeach >= 0 && countTeach <= 42){ for (let i = 1; i <= count; i++) { await sendOne(i, countTeach, message,date, theme); await delay(500); }; } else{ for (let i = 1; i <= count; i++) { const teacherId = i % maxTeacherId || maxTeacherId; await sendOne(i, teacherId, message,date, theme); await delay(500); }; }; alert("All comment send!"); location.reload(); } async function sendOne(index, teacherId, message, date, theme, retries = 2, retriedWithTeacher1 = false) { let payload; let data; let contentType; if (type === 'event-comment') { payload = { body: message, createdAt: date, resourceSid: "830fef63a054", parentComment: null, pinOrder: 1, resource: "course", resourceId: detectedCourseId, sid: "830fef63a054", sysId: 1, teacher: null, teacherId: teacherId, theme: theme, updatedAt: date }; data = JSON.stringify(payload); contentType = 'application/json'; } else { payload = { body: message, createdAt: date, resourceSid: "830fef63a054", images: [], parentComment: null, pinOrder: 1, resource: "course", resourceId: detectedCourseId, sid: "830fef63a054", sysId: 1, teacher: null, teacherId: teacherId, theme: theme, updatedAt: date }; data = `data=${encodeURIComponent(JSON.stringify(payload))}`; contentType = 'application/x-www-form-urlencoded'; }; let url = `https://api.mapstudy.edu.vn/v1/${type}/${detectedCourseId}/comment?sysId=1`; if(type === 'event-comment'){ url = `https://api.mapstudy.edu.vn/v1/event-comment/${detectedCourseId}?sysId=1`; }; return new Promise(resolve => { GM_xmlhttpRequest({ method: "POST", url: url, headers: { "Content-Type": contentType, "Authorization": AUTH_TOKEN }, data: data, onload: () => { console.log(`[BOT] Sent comment ${index} with teacherId ${teacherId}`); resolve(true); }, onerror: async (err) => { console.warn(`[BOT] Failed comment ${index} with teacherId ${teacherId}, retries left: ${retries}`, err); if (retries > 0) { await delay(500); await sendOne(index, teacherId, retries - 1, retriedWithTeacher1).then(resolve); } else if (!retriedWithTeacher1 && teacherId !== 1) { console.log(`[BOT] Retrying comment ${index} with teacherId 1`); await delay(500); await sendOne(index, 1, 2, true).then(resolve); } else { resolve(false); }; } }); }); } async function deleteAllComments(type) { if (!detectedCourseId) { alert('Course ID not detected.'); return; }; let url = `https://api.mapstudy.edu.vn/v1/${type}/${detectedCourseId}/comment?sysId=1`; if(type === 'event-comment'){ url = `https://api.mapstudy.edu.vn/v1/event-comment/${detectedCourseId}?sysId=1`; } GM_xmlhttpRequest({ method: "GET", url: url, headers: { "Authorization": AUTH_TOKEN }, onload: function(res) { const json = JSON.parse(res.responseText); const comments = json.data.comments; let myComments = []; if (type === 'event-comment') { myComments = comments.filter(c => c.userId === userId && c.user.username === 'quanghacker' ); } else { myComments = comments.filter(c => c.userId === userId && c.teacherId >= 0 && c.teacherId <= maxTeacherId ); } let deleted = 0; for (const cmt of myComments) { let deleteUrl = ''; if (type === 'event-comment') { deleteUrl = `https://api.mapstudy.edu.vn/v1/event-comment/${cmt.id}?sysId=1`; } else { deleteUrl = `https://api.mapstudy.edu.vn/v1/_/_/comment/${cmt.id}?sysId=1`; } GM_xmlhttpRequest({ method: "DELETE", url: deleteUrl, headers: { "Authorization": AUTH_TOKEN }, onload: () => { console.log(`[BOT] Deleted comment ID ${cmt.id}`); deleted++; if (deleted === myComments.length) { alert(`Deleted ${deleted} comments`); location.reload(); } } }); }; } }); } sendBtn.onclick = async () => { const message = inputComment.value.trim(); const count = parseInt(inputAmount.value.trim(), 10); const countTeach = inputTeacher.value ? parseInt(inputTeacher.value.trim(), 10) : null; const parsedDate = new Date(inputDate.value); const date = !isNaN(parsedDate.getTime()) ? parsedDate.toISOString() : new Date(new Date().getTime() + 7 * 60 * 60 * 1000).toISOString(); const theme = selectTheme.value || 'default'; if (!message || isNaN(count) || count <= 0) { alert('Please enter a valid message and amount.'); return; } if(countTeach !== null && countTeach >= 0 && countTeach <= 42){ await sendComments(message, count, countTeach , date, theme); } else{ await sendComments(message, count, null, date, theme); } }; deleteBtn.onclick = () => { deleteAllComments(type); };
})();