您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Blocks reaction fetch packets, shows GUI, resends with chosen emoji, updates client UI
当前为
// ==UserScript== // @name InvadedLands Reactions (Updated for fetch JSON) // @namespace http://tampermonkey.net/ // @version 7.1 // @description Blocks reaction fetch packets, shows GUI, resends with chosen emoji, updates client UI // @author PillowPB (updated by EclipseMaster) // @match *https://invadedlands.net/* // @grant none // @license Creative Commons // @run-at document-end // ==/UserScript== (function() { 'use strict'; // This update patches fetch() to block reactions and show custom menu as before. // Annoying update, but here we are... const reactionMap = { "1": "👍", "2": "❤️", "3": "😂", "4": "😮", "5": "😢", "6": "😡" }; let isHacking = true; let hackedPostId = null; let lastClickEvent = null; // Hook the fetch() to intercept reaction requests (new method) const originalFetch = window.fetch; window.fetch = function(input, init) { let url = input; if (input instanceof Request) url = input.url; if (typeof url === 'string' && url.includes('/react?reaction_id=')) { //URL pattern: /profile-posts/{id}/react const postIdMatch = url.match(/\/profile-posts\/(\d+)\/react/); hackedPostId = postIdMatch ? postIdMatch[1] : null; console.log('[invadedlands] Blocked reaction fetch for post', hackedPostId); const x = lastClickEvent?.clientX || window.innerWidth / 2; const y = lastClickEvent?.clientY || window.innerHeight / 2; if (hackedPostId) showReactionMenu(hackedPostId, x, y); // Block fetch return new Promise(() => {}); } return originalFetch.apply(this, arguments); }; // Keep XHR hook for any other reaction packets 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(/\/profile-posts\/(\d+)\/react/); hackedPostId = postIdMatch ? postIdMatch[1] : null; console.log('[invadedlands] Blocked reaction XHR 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); }; //menu positioning document.addEventListener('click', e => lastClickEvent = e, true); // Send reaction with JSON payload function sendCustomReaction(postId, reactionId) { isHacking = false; const xfToken = document.querySelector('input[name="_xfToken"]')?.value; if (!xfToken) { console.warn('Missing _xfToken'); return; } const payload = { _xfResponseType: "json", _xfWithData: 1, _xfRequestUri: window.location.pathname, _xfToken: xfToken, }; fetch(`/profile-posts/${postId}/react?reaction_id=${reactionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(payload), }) .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*="profile-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); }); } // Show reaction selection menu near click 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 (fetch patched with JSON payload)'); })();