// ==UserScript==
// @name Final Earth QOL Tweaks
// @namespace http://tampermonkey.net/
// @version 0.2.1
// @description Various UI tweaks
// @author Natty_Boh[29066]
// @match https://www.finalearth.com/*
// @match https://finalearth.com/*
// @icon https://www.google.com/s2/favicons?domain=finalearth.com
// @grant GM_addStyle
// @grant GM.getValue
// @grant GM.xmlHttpRequest
// @grant GM.setValue
// ==/UserScript==
(async function() {
const config = { attributes: false, childList: true, subtree: false};
async function buildSettings() {
const settingsObj = new Object();
settingsObj.key = await GM.getValue("feqol_apiKey", "");
settingsObj.font = await GM.getValue("feqol_font", false);
settingsObj.elo = await GM.getValue("feqol_elo", false);
settingsObj.copy = await GM.getValue("feqol_copy", false);
settingsObj.activity = await GM.getValue("feqol_activity", false);
settingsObj.quick = await GM.getValue("feqol_quick", false);
settingsObj.chat = await GM.getValue("feqol_chat", false);
settingsObj.list = await GM.getValue("feqol_chatList", "");
return settingsObj;
}
const settings = await buildSettings();
const checkPage = async function(mutationsList, observer) {
if (document.getElementById("main") && !document.getElementById("scriptSettings")) {
settingsButton();
}
if (settings.elo && settings.key !== "" && document.querySelector(".inform") && !document.getElementById("elo_info")) {
addElo();
}
if (settings.copy && document.querySelector(".wartop")) {
copyWarPage();
}
if (settings.copy && document.querySelector(".forces-in-country__user")){
copyForces();
}
if (settings.activity && settings.key !== "" && document.querySelector(".wartable1") && !document.getElementById("status_icon")) {
setActivity();
}
if (settings.quick && document.getElementById("main") && !document.getElementById("quickGroup")) {
quickButtons();
}
};
const checkChat = async function (mutationsList, observer) {
highlight();
}
const observer = new MutationObserver(checkPage);
observer.observe(document.getElementById("content"), config);
if (settings.chat) {
const chatObserver = new MutationObserver(checkChat);
chatObserver.observe(document.querySelector(".chat-box-wrap"), {attributes: false, childList: true, subtree: true});
}
if (settings.font) {
GM_addStyle ( `
#banner, .scills, .general, .skillsContent, .lead_ttl, .cat_list, .hq_stg_list,
.hq_stg_tbl td, h1, h2, h3, h4, h5, h6, .readall, .delall, th,
.clearall, .btn_faded, .submit_pref, a.back, .log, .menu_cont,
.subbut, .pr_button, .btn_stl, .wargroup, .newsbutton a, .but_link a, .butlink,
.largelink, .captcha_ttl, .training_status, .readytimer, .font_f, .command_ttl, .search_force,
.help_section strong, .help_section th {
font-family: Cinzel_Bold, serif !important;
}
` );
GM_addStyle ( `
.control_bar_txt {
font-family: Cinzel_Bold, serif !important;
letter-spacing: normal !important;
}
` );
}
function addElo() {
const userId = document.querySelector('.p2pchat').id
const url = `https://www.finalearth.com/api/user?id=${userId}&key=${settings.key}`
GM.xmlHttpRequest({
method: 'GET',
url: url,
onload: function (response) {
if (response.status === 200) {
const json = JSON.parse(response.responseText)
const elo = json.data.rating
const elem = document.querySelector ( '.inform > br' )
const info = `<span id = "elo_info">Elo: </span> ${elo} <br>`
if(elem && !document.getElementById("elo_info")) {
elem.insertAdjacentHTML('afterend', info);
}
}
},
onerror: function (error) {
console.log('Something went wrong')
}
})
}
function copyWarPage() {
if (!document.getElementById('copy_button')) {
const elem = document.querySelectorAll( '.wargroup > div' )
const countryName = document.querySelector('.wartop > h5 > b > a').innerText
let str = `**__${countryName}__**`
elem.forEach(e => {
if(e.childNodes.item(3).innerText.includes("Friendly")) {
if(userTeam === "Axis") {
str += "\n" + ":red_circle: **Axis Units**"
} else {
str += "\n" + ":green_circle: **Allies Units**"
}
}
if(e.childNodes.item(3).innerText.includes("Enemy")) {
if(userTeam === "Axis") {
str += "\n" + ":green_circle: **Allies Units**"
} else {
str += "\n" + ":red_circle: **Axis Units**"
}
}
for (let i = 3; i < e.childNodes.length; i = i + 2) {
str += "\n" + e.childNodes.item(i).innerText
}
})
const button = `<a class="back" id="copy_button" style=" position: absolute;right: 30px; top: 180px;">Copy</a>`
const wartop = document.querySelector( '.wartop' )
if(wartop) {
wartop.insertAdjacentHTML('beforeend', button);
const copyButton = document.getElementById('copy_button');
copyButton.addEventListener('click', function () {
navigator.clipboard.writeText(str);
});
}
}
}
async function copyForces() {
if (!document.getElementById('copy_button')) {
let axisStr = ":red_circle: Axis Forces:\n"
let alliesStr = ":green_circle: Allies Forces:\n"
const leftSide = document.querySelector(".ForcesinCountry > div:nth-child(1) > div ")
const rightSide = document.querySelector(".ForcesinCountry > div:nth-child(2) > div")
const rightPlayerList = rightSide.querySelector(".mCSB_container")
const leftPlayerList = leftSide.querySelector(".mCSB_container")
for (let i = 0; i < rightPlayerList.children.length; i++) {
if (rightSide.children[0].attributes[0].nodeValue === "#00D8A3") {
alliesStr += rightPlayerList.children[i].innerText + "\n"
}
else if (rightSide.children[0].attributes[0].nodeValue === "#FF7272") {
axisStr += rightPlayerList.children[i].innerText + "\n"
}
let elt = rightPlayerList.children[i].querySelector("div > a");
if(elt) {
fetchActivity(elt.href.split("=")[1], elt)
}
}
for (let i = 0; i < leftPlayerList.children.length; i++) {
if (leftSide.children[0].attributes[0].nodeValue === "#00D8A3") {
alliesStr += leftPlayerList.children[i].innerText + "\n"
}
else if (leftSide.children[0].attributes[0].nodeValue === "#FF7272") {
axisStr += leftPlayerList.children[i].innerText + "\n"
}
let elt = leftPlayerList.children[i].querySelector("div > a");
if(elt) {
fetchActivity(elt.href.split("=")[1], elt)
}
}
let button = `<a class="back" id="copy_button" style="margin-right: 15px;">Copy</a>`
let back = document.querySelector( '.back' )
if(back) {
back.insertAdjacentHTML('beforebegin', button);
const copyButton = document.getElementById('copy_button');
copyButton.addEventListener('click', function () {
navigator.clipboard.writeText(alliesStr + axisStr);
});
}
}
}
let cachedStatuses;
let statusesLastUpdated;
let countryNameAtLastCache;
async function setActivity() {
const table = document.querySelector(".wartable1");
for (let i = 0; i < table.rows.length; i++) {
const row = table.rows[i]
const children = Array.from(row.cells[1].childNodes)
for (let j = 0; j < children.length; j++) {
const e = children[j]
if (e && e.href && e.href.includes("userID")) {
const uid = e.href.split("=")[1]
await fetchActivity(uid, e);
}
}
}
}
async function fetchActivity(uid, e) {
const re = /(?:Scanning enemy and friendly forces in )/;
const url = `https://www.finalearth.com/api/user?id=${uid}&key=${settings.key}`
if (!cachedStatuses
|| Date.now()/1000 - statusesLastUpdated > 180
|| (countryNameAtLastCache != document.querySelector('.wartop > h5 > b > a')?.innerText
&& countryNameAtLastCache != document.querySelector(".command_ttl")?.innerText.split(re)[1])) { //init/refresh cache
console.log("refresh cache");
cachedStatuses = new Map();
}
if (!cachedStatuses.has(uid)) {
console.log("waiting...")
await new Promise(resolve => setTimeout(resolve, 500));
GM.xmlHttpRequest({
method: 'GET',
url: url,
onload: function (response) {
if (response.status === 200) {
console.log("calling api...")
const json = JSON.parse(response.responseText)
const action = json.data.lastAction
cachedStatuses.set(uid, action)
statusesLastUpdated = Date.now()/1000;
countryNameAtLastCache = document.querySelector('.wartop > h5 > b > a')?.innerText
?? document.querySelector(".command_ttl")?.innerText.split(re)[1]
displayIcon(action, e)
}
},
onerror: function (error) {
console.log('Something went wrong')
}
})
} else {
console.log("using cached result")
displayIcon(cachedStatuses.get(uid), e);
}
}
function displayIcon(action, e) {
const online = `<a id="status_icon" style="display: inline-block; vertical-align: middle; height: 12px; width: 12px; background: url(/img/chat/tab_icons.png) left top; background-position: -14px -12px;"></a>`
const idle = `<a id="status_icon" style="display: inline-block; vertical-align: middle; height: 12px; width: 12px; background: url(/img/chat/tab_icons.png) left top; background-position: -48px -12px;"></a>`
const offline = `<a id="status_icon" style="display: inline-block; vertical-align: middle; height: 12px; width: 12px; background: url(/img/chat/tab_icons.png) left top; background-position: -82px -12px;"></a>`
if(e) {
if ( Date.now()/1000 - action < 300) {
e.insertAdjacentHTML('afterbegin', online);
}
else if (Date.now()/1000 - action >= 300 && Date.now()/1000 - action <= 3600) {
e.insertAdjacentHTML('afterbegin', idle);
}
else if (Date.now()/1000 - action >= 3600) {
e.insertAdjacentHTML('afterbegin', offline);
}
}
}
async function highlight(){
document.querySelectorAll('.message > a').forEach( e => {
if (e.textContent.includes(userName)) {
e.style.color = 'mediumBlue'
}
});
const keywordsToHighlight = await settingToArray();
keywordsToHighlight.push(userName)
document.querySelectorAll('.message > span').forEach( e => {
const text = e.textContent.toLowerCase();
if (keywordsToHighlight.some(element => text.includes(element.toLowerCase()))) {
e.style.backgroundColor = 'lightBlue'
}
});
}
async function settingToArray() {
const string = await GM.getValue("feqol_chatList", "");
if (string !== "") {
const arr = string.split(',');
return arr.map(e =>{ return e.trim()});
}
return [];
}
function settingsButton() {
const hudButton = document.getElementById("show_hide_HUD");
const settings = `<a id=scriptSettings class="btn_stl" style="position: absolute; top: 14px; left: 137px;z-index: 5000;margin: 0;">Settings</a>`
hudButton.insertAdjacentHTML('afterend', settings);
const settingsButton = document.getElementById('scriptSettings');
settingsButton.addEventListener('click', function () {
showScriptSettings()
})
}
function quickButtons() {
const groupTravel = `<a id=quickGroup class="btn_stl" style="position: absolute; top: 14px; left: 223px;z-index: 5000;margin: 0;" href="/Formations/grouptravel">Group Travel</a>`
const hudButton = document.getElementById("show_hide_HUD");
hudButton.insertAdjacentHTML('afterend', groupTravel);
const news = `<a id=quickGroup class="btn_stl" style="position: absolute; top: 14px; left: 342px;z-index: 5000;margin: 0;" href="/world/worldNews">World News</a>`
hudButton.insertAdjacentHTML('afterend', news);
}
//insert script settings form over existing content
function showScriptSettings() {
const elem = document.getElementById ('content')
elem.insertAdjacentHTML('beforebegin', html);
elem.remove()
populateExistingSettings()
const settingsButton = document.getElementById('saveSettingsButton');
settingsButton.addEventListener('click', function () {
setSettings()
});
}
//save settings and redirect to HQ page so normal content will show again
async function setSettings() {
await GM.setValue("feqol_apiKey", document.getElementById('settingKey').value)
await GM.setValue("feqol_font", document.getElementById('fontCheckbox').checked)
await GM.setValue("feqol_elo", document.getElementById('eloCheckbox').checked)
await GM.setValue("feqol_copy", document.getElementById('copyCheckbox').checked)
await GM.setValue("feqol_activity", document.getElementById('activityCheckbox').checked)
await GM.setValue("feqol_quick", document.getElementById('quickCheckbox').checked)
await GM.setValue("feqol_chat", document.getElementById('chatCheckbox').checked)
await GM.setValue("feqol_chatList", document.getElementById('chatList').value)
location.reload();
}
//prefill form with the existing settings
async function populateExistingSettings() {
document.getElementById('settingKey').value = await GM.getValue("feqol_apiKey", "");
document.getElementById('fontCheckbox').checked = await GM.getValue("feqol_font", false);
document.getElementById('eloCheckbox').checked = await GM.getValue("feqol_elo", false);
document.getElementById('copyCheckbox').checked = await GM.getValue("feqol_copy", false);
document.getElementById('activityCheckbox').checked = await GM.getValue("feqol_activity", false);
document.getElementById('quickCheckbox').checked = await GM.getValue("feqol_quick", false);
document.getElementById('chatCheckbox').checked = await GM.getValue("feqol_chat", false);
document.getElementById('chatList').value = await GM.getValue("feqol_chatList", "");
}
const html = `<div id="content"> <div class="bigdiv"><form id="frm1" style="padding-top: 25px">
API key: <input type="text" id="settingKey"><br><br>
<h2>Feature Toggles:</h2>
<input type="checkbox" id="fontCheckbox"> Use Allies font <br>
<input type="checkbox" id="eloCheckbox"> Show elo on profile pages * <br>
<input type="checkbox" id="copyCheckbox"> Add copy button on war page and forces page <br>
<input type="checkbox" id="activityCheckbox"> Add activity indicators on war and forces page * <br>
<input type="checkbox" id="quickCheckbox"> Add quick buttons (group travel and world news) <br> <br>
<input type="checkbox" id="chatCheckbox"> Highlight my name and mentions in chat <br> <br>
Mentions to highlight: <input type="text" id="chatList"><br>(input a comma separated list, username is included by default, can add nicknames or keywords.<br>
<br><br>
</form>
<br><a><button id="saveSettingsButton" class="btn_stl">Save and Close</button></a><br><br>
<br><br>
<p> * API key required for feature </p>
</div> </div>`
})();