您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add NPC attack time to the news ticker using Loot Rangers for Torn
// ==UserScript== // @name Torn NPC Attack Time Newsfeed // @namespace npc.timing // @version v1.1.5 // @description Add NPC attack time to the news ticker using Loot Rangers for Torn // @author IceBlueFire [776] // @license MIT // @match https://www.torn.com/* // @exclude https://www.torn.com/newspaper.php // @exclude https://www.torn.com/item.php // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant unsafeWindow // @grant GM_xmlhttpRequest // @require https://code.jquery.com/jquery-1.8.2.min.js // @connect api.lzpt.io // ==/UserScript== /******************** CONFIG SETTINGS ********************/ const color = "#8abeef"; // Any hex-code for the color to appear in the news feed as const format = 24; // Time format. 12 = 12:00 AM format; 24 = 23:59 format const local = false; // Adjust the timer to be local time or not. true = local; false = UTC /****************** END CONFIG SETTINGS *******************/ const lzpt = getAttackTimes(); const { fetch: originalFetch } = unsafeWindow; unsafeWindow.fetch = async (...args) => { var [resource, config] = args; var response = await originalFetch(resource, config); if(response.url.indexOf('?sid=newsTicker') === -1) return response; const json = () => response.clone().json() .then((data) => { data = { ...data }; lzpt.then(function(result) { var attackOrder = ''; var attackString = ''; var attackLink = ''; var attackTarget = 0; // If there's no clear time set if(result.time.clear == 0 && result.time.attack === false) { attackString = result.time.reason ? 'NPC attacking will resume after '+result.time.reason : 'No attack currently set.'; } else { // Build the string for the attack order $.each(result.order, function(key, value) { if(result.npcs[value].next){ // If there's an attack happening right now, cross out NPCs that are in the hospital if(result.time.attack === true) { if(result.npcs[value].hosp_out >= result.time.current) { attackOrder += '<span style="text-decoration: line-through">'+result.npcs[value].name+'</span>, '; } else { attackOrder += result.npcs[value].name+', '; } } else { attackOrder += result.npcs[value].name+', '; } } // Adjust the current target based on if an attack is going and who isn't in the hospital yet if(result.time.attack === true) { if(result.npcs[value].hosp_out <= result.time.current) { // Check if the NPC is currently out of the hospital if(attackTarget == 0) { attackTarget = value; } } } }); // Check if target has been set, otherwise default to first in attack order if(attackTarget == 0) { attackTarget = result.order[0]; } // Clean up the attack order string attackOrder = attackOrder.slice(0, -2)+'.'; // Check if an attack is currently happening and adjust the message accordingly if(result.time.attack === true) { attackString = 'NPC attack is underway! Get in there and get some loot!'; attackLink = 'loader.php?sid=attack&user2ID='+attackTarget; } else { attackString = 'NPC attack set for '+utcformat(result.time.clear)+'. Order is: '+attackOrder; attackLink = 'loader.php?sid=attack&user2ID='+attackTarget; } } // Insert the custom news item into the news ticker let attackItem = {ID: 0, headline: '<span style="color:'+color+'; font-weight: bold;" id="icey-npctimer">'+attackString+'</span>', countdown: true, endTime: result.time.clear, link: attackLink, isGlobal: true, type: 'generalMessage'}; data.headlines.unshift(attackItem); }, function(err) { console.log(err); // Error: "It broke" }); return data }) response.json = json; response.text = async () =>JSON.stringify(await json()); return response; }; function modifyContent() { return new Promise((resolve, reject) => { var ticker = document.querySelector('.news-ticker-countdown'); ticker.style.color = color; var wrap = ticker.parentNode.parentNode.parentNode; var svg = wrap.children[0]; svg.setAttribute('fill', color); svg.setAttribute('viewBox', "0 0 24 24"); svg.setAttribute('height', '14'); svg.setAttribute('width', '14'); svg.children[0].setAttribute('d', 'M17.457 3L21 3.003l.002 3.523-5.467 5.466 2.828 2.829 1.415-1.414 1.414 1.414-2.474 2.475 2.828 2.829-1.414 1.414-2.829-2.829-2.475 2.475-1.414-1.414 1.414-1.415-2.829-2.828-2.828 2.828 1.415 1.415-1.414 1.414-2.475-2.475-2.829 2.829-1.414-1.414 2.829-2.83-2.475-2.474 1.414-1.414 1.414 1.413 2.827-2.828-5.46-5.46L3 3l3.546.003 5.453 5.454L17.457 3zm-7.58 10.406L7.05 16.234l.708.707 2.827-2.828-.707-.707zm9.124-8.405h-.717l-4.87 4.869.706.707 4.881-4.879v-.697zm-14 0v.7l11.241 11.241.707-.707L5.716 5.002l-.715-.001z'); // console.log(svg); resolve('Content updated'); }); } const newstickerObserver = new MutationObserver((mutationsList, observer) => { if ($(".news-ticker-slide #icey-npctimer").length == 1) { // If it's showing the slide for NPCs // Once changes are observed, disconnect the observer to avoid infinite loop newstickerObserver.disconnect(); // Modify the content of .news-ticker-wrapper modifyContent() .then(() => { // Re-observe the element after modifications and asynchronous operations are complete startNewstickerObserver(); }) .catch(error => console.error('Error updating content:', error)); } }); function startNewstickerObserver() { const target = document.querySelector('.news-ticker-slider-wrapper'); if (target) { newstickerObserver.observe(target, { childList: true, // Set true if children of the target node are being added or removed. attributes: false, // Set true if attributes of the target node are being modified. subtree: true, // Set true if changes to descendants of the target node are to be observed. characterData: false // Set true if data of the target node itself is being modified. }); } } /******************** HELPER FUNCTIONS ********************/ function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 observer.observe(document.body, { childList: true, subtree: true }); }); } // Make sure the news ticker with the injected div is loaded waitForElm('#icey-npctimer').then((elm) => { startNewstickerObserver(); //console.log('Element is ready'); }); // Format the time in the appropriate fashion function utcformat(d){ d= new Date(d * 1000); if(local) { var tail= ' LT', D= [d.getFullYear(), d.getMonth()+1, d.getDate()], T= [d.getHours(), d.getMinutes(), d.getSeconds()]; } else { var tail= ' TCT', D= [d.getUTCFullYear(), d.getUTCMonth()+1, d.getUTCDate()], T= [d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()]; } if(format == 12) { /* 12 hour format */ if(+T[0]> 12){ T[0]-= 12; tail= 'PM '+tail; } else tail= 'AM '+tail; } var i= 3; while(i){ --i; if(D[i]<10) D[i]= '0'+D[i]; if(T[i]<10) T[i]= '0'+T[i]; } return T.join(':')+ tail; } // Fetch the NPC details from Loot Rangers async function getAttackTimes() { return new Promise(resolve => { const request_url = `https://api.lzpt.io/loot`; GM_xmlhttpRequest ({ method: "GET", url: request_url, headers: { "Content-Type": "application/json" }, onload: response => { try { const data = JSON.parse(response.responseText); if(!data) { console.log('No response from Loot Rangers'); } else { return resolve(data) } } catch (e) { console.error(e); } }, onerror: (e) => { console.error(e); } }) }); }