ljovcheg [3191064]
当前为
// ==UserScript==
// @name NearestEventTimer
// @namespace NearestEventTimer
// @version 1.0.8
// @grant GM_getValue
// @grant GM_setValue
// @description ljovcheg [3191064]
// @author ljovcheg [3191064]
// @match https://www.torn.com/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_info
// @license mit
// ==/UserScript==
(function () {
'use strict';
throwRed(`started | v${GM_info.script?.version || null}`);
GM_addStyle(`
#eventTimer {
background-color: rgba(0, 0, 0, 0.5);
margin-top: 5px;
margin-bottom: 7px;
border-radius: 5px;
padding: 7px;
overflow: hidden;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
line-height: 16px;
font-weight: 100;
}
#eventTimer:hover{
background-color: rgba(0, 0, 0, 0.9);
}
`);
let apiKey;;
let events;
let userEventStartTime;
let userEventStartTimeUnix;
let nearestEvent;
let timeOffset = 0;
let div; // timer div object
let timer; // holding timeout
const updateInterval = 1800; //30min
function injectDiv() {
if (document.getElementById("eventTimer")) {
checkCache();
return;
}
let tornClock = document.querySelector(".tc-clock-tooltip");
if (tornClock) {
let p = tornClock.appendChild(document.createElement("div"));
p.addEventListener("click", divClicked);
p.innerHTML = `<div id="eventTimer">...</div>`;
div = document.getElementById('eventTimer');
checkCache();
} else {
throwRed(".tc-clock-tooltip not found")
}
}
injectDiv();
function checkCache() {
let currentTimeStamp = Math.round(Date.now() / 1000);
// read cache
apiKey = GM_getValue('timer_api_key', null);
events = GM_getValue('events', null);
timeOffset = GM_getValue('timeOffset', 0);
userEventStartTime = GM_getValue('userEventStartTime', null);
let lastUpdated = GM_getValue('updated', null);
if (!lastUpdated || currentTimeStamp - lastUpdated > updateInterval || !events || !userEventStartTime) {
if (apiKey) {
fetchData();
} else {
throwRed("No api key")
setText("no apy key");
}
} else {
calculate();
}
}
async function fetchData() {
setText("fetching torn...");
const tornData = await GM_fetch('torn', 'calendar');
if (tornData.calendar) {
let json = tornData.calendar;
if (json.events && json.competitions) {
events = json["events"].concat(json.competitions);
} else {
events = json.events;
}
GM_setValue('events', events)
} else if (tornData.error) {
throwRed(tornData.error);
setText(tornData.error.error);
return;
}
setText("fetching user...");
const userData = await GM_fetch('user', 'calendar,timestamp');
if (userData.calendar) {
let json = userData.calendar;
if (json.start_time) {
userEventStartTime = json.start_time.toLowerCase().split(" tct")[0];
GM_setValue('userEventStartTime', userEventStartTime);
}
compareTimestamp(userData);
throwRed({
userEventStartTime: userEventStartTime,
timeOffset: timeOffset
})
} else if (userData.error) {
throwRed(tornData.error);
setText(userData.error.error);
return;
}
let currentTimeStamp = Math.round(Date.now() / 1000);
GM_setValue('updated', currentTimeStamp);
calculate();
}
async function GM_fetch(page, selections) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://api.torn.com/v2/${page}/?selections=${selections}&key=${apiKey}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
onload: function (response) {
try {
if (!response || !response.responseText) {
return reject(new Error("Empty response"));
}
const json = JSON.parse(response.responseText);
resolve(json);
} catch (err) {
reject(err);
}
},
onerror: function (err) {
reject(err);
},
});
});
}
function calculate() {
let currentTimeStamp = Math.round(Date.now() / 1000);
nearestEvent = getNearestEvent(events, currentTimeStamp);
if (nearestEvent) {
//userEventStartTimeUnix = setTimeOnUnix(nearestEvent.start + timeOffset, userEventStartTime);
userEventStartTimeUnix = setTimeOnUnix(nearestEvent.start, userEventStartTime);
doTimer();
}
}
function doTimer() {
let currentTimeStamp = Math.round(Date.now() / 1000);
let dif = userEventStartTimeUnix - currentTimeStamp;
let str = secondsToTime(dif)
setText(`
${nearestEvent.title}<br>
<b>in: ${str}</b>
`)
if (!timer) timer = setInterval(doTimer, 1000);
}
function secondsToTime(totalSeconds) {
const days = Math.floor(totalSeconds / 86400); // 1 day = 86400 seconds
totalSeconds %= 86400;
const hours = Math.floor(totalSeconds / 3600);
totalSeconds %= 3600;
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const paddedHours = hours < 10 ? "0" + hours : hours;
const paddedMinutes = minutes < 10 ? "0" + minutes : minutes;
const paddedSeconds = seconds < 10 ? "0" + seconds : seconds;
if (days > 0) {
// Include days and limit hours to 0–23
return `${days} day${days > 1 ? "s" : ""} ${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
} else {
// No days, just hours/minutes/seconds
return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
}
}
function getNearestEvent(events, current_time) {
let longestWord = ""; // for testing
if (!events || events.length === 0) return null;
let dummyList = []; // no idea why sice doesn't work
for (let i = 0; i < events.length; i++) {
let event = events[i]
let diff = (event.start - current_time);
let eventTitle = event.title;
if (eventTitle.length > longestWord.length) longestWord = eventTitle;
event.diff = diff;
if (diff >= 0) {
dummyList.push(event)
}
}
events = dummyList;
events.sort(function (a, b) {
return a.diff - b.diff;
});
let nearestEvent = events[0];
let minDifference = Math.abs(events[0].start - current_time);
for (let i = 1; i < events.length; i++) {
const diff = Math.abs(events[i].start - current_time);
if (diff < minDifference) {
minDifference = diff;
nearestEvent = events[i];
}
}
return nearestEvent;
}
function setTimeOnUnix(unixTime, timeString) {
const [targetHour, targetMinute] = timeString.split(":").map(Number);
const date = new Date(unixTime * 1000);
date.setUTCHours(targetHour);
date.setUTCMinutes(targetMinute);
date.setUTCSeconds(0);
date.setUTCMilliseconds(0);
return Math.floor(date.getTime() / 1000);
}
function divClicked() {
if (apiKey === null) apiKey = '';
let w = prompt("Api key", apiKey);
if (w || w === "" && w !== null) {
//save key
GM_setValue('timer_api_key', w);
apiKey = w;
}
if (apiKey && w !== null) fetchData();
}
function setText(data) {
div.innerHTML = data
}
function throwRed(data) {
console.log(`%cnextEvent${(typeof data !== 'object') ? ': ' + data : ''}`, 'background: #212c37; color: white;padding:10px; border-radius:3px;', (typeof data === 'object') ? data : '');
}
function compareTimestamp(data) {
if (!data.timestamp) return;
let currentTimeStamp = Math.round(Date.now() / 1000);
timeOffset = currentTimeStamp - data.timestamp;
GM_setValue('timeOffset', timeOffset)
}
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址