// ==UserScript==
// @name InvadedLands Reactions
// @namespace http://tampermonkey.net/
// @version 6.9
// @description Blocks reaction packets, shows GUI, resends with chosen emoji, updates client UI
// @author PillowPB
// @match *https://invadedlands.net/*
// @grant none
// @license Creative Commons license,
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const reactionMap = {
"1": "👍", "2": "❤️", "3": "😂",
"4": "😮", "5": "😢", "6": "😡"
};
let isHacking = true;
let hackedPostId = null;
let lastClickEvent = null;
// packets are very cool i do not hate them
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this._method = method;
this._url = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
if (isHacking && this._url?.includes('/react?reaction_id=')) {
const postIdMatch = this._url.match(/\/posts\/(\d+)\/react/);
hackedPostId = postIdMatch ? postIdMatch[1] : null;
console.log('[invadedlands is the best] Blocked reaction packet for post', hackedPostId);
const x = lastClickEvent?.clientX || window.innerWidth / 2;
const y = lastClickEvent?.clientY || window.innerHeight / 2;
if (hackedPostId) showReactionMenu(hackedPostId, x, y);
return; // block original packet
}
return originalSend.apply(this, arguments);
};
// positioning menu mouse
document.addEventListener('click', e => lastClickEvent = e, true);
function sendCustomReaction(postId, reactionId) {
isHacking = false;
const xfToken = document.querySelector('input[name="_xfToken"]')?.value;
if (!xfToken) {
console.warn('Missing _xfToken');
return;
}
const formData = new FormData();
formData.append('_xfToken', xfToken);
formData.append('_xfRequestUri', window.location.pathname);
formData.append('_xfWithData', '1');
formData.append('_xfResponseType', 'json');
fetch(`/posts/${postId}/react?reaction_id=${reactionId}`, {
method: 'POST',
body: new URLSearchParams(formData),
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
console.log(`[omg] Sent reaction ${reactionId} to post ${postId}`, data);
// Update reaction count UI
const postElem = document.querySelector(`[data-content*="post-${postId}"]`);
if (postElem) {
const reactionBtn = postElem.querySelector(`a.messageReactionLink[href*="reaction_id=${reactionId}"]`);
if (reactionBtn) {
let countSpan = reactionBtn.querySelector('.reactionCount');
if (countSpan) {
countSpan.textContent = (parseInt(countSpan.textContent) || 0) + 1;
} else {
const span = document.createElement('span');
span.className = 'reactionCount';
span.textContent = '1';
reactionBtn.appendChild(span);
}
reactionBtn.classList.add('reactionSelected');
setTimeout(() => reactionBtn.classList.remove('reactionSelected'), 1500);
}
}
document.dispatchEvent(new CustomEvent('reactionSent', { detail: { postId, reactionId } }));
})
.catch(error => {
console.error('[WTF] Failed to send reaction:', error);
})
.finally(() => {
setTimeout(() => isHacking = true, 100);
});
}
function showReactionMenu(postId, x, y) {
document.querySelectorAll('.reaction-menu-container').forEach(el => el.remove());
const menu = document.createElement('div');
menu.className = 'reaction-menu-container';
menu.style.cssText = `
position: fixed;
left: ${Math.min(x, window.innerWidth - 220)}px;
top: ${Math.min(y, window.innerHeight - 120)}px;
background: #2d2d2d;
border-radius: 8px;
padding: 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
z-index: 99999;
box-shadow: 0 2px 10px rgba(0,0,0,0.5);
border: 1px solid #444;
`;
Object.entries(reactionMap).forEach(([id, emoji]) => {
const btn = document.createElement('div');
btn.className = 'reaction-emoji-btn';
btn.textContent = emoji;
btn.title = `Reaction ID: ${id}`;
btn.style.cssText = `
font-size: 24px;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
text-align: center;
`;
btn.addEventListener('mouseenter', () => {
btn.style.background = '#3e3e3e';
btn.style.transform = 'scale(1.2)';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = 'transparent';
btn.style.transform = 'scale(1)';
});
btn.addEventListener('click', () => {
sendCustomReaction(postId, id);
menu.remove();
});
menu.appendChild(btn);
});
document.body.appendChild(menu);
setTimeout(() => {
const clickHandler = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', clickHandler);
}
};
document.addEventListener('click', clickHandler);
}, 10);
}
console.log('Reaction invadedlands initialized');
})();