MAPSTUDY Comment Bot

Auto-comment & delete in MAPSTUDY

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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