// ==UserScript==
// @name TORN: Dowload WarReport as CSV
// @namespace http://torn.city.com.dot.com.com
// @version 1.0.3
// @description Displays a button that allows users to download a csv version of their war report
// @author Ironhydedragon[2428902]
// @match https://www.torn.com/war.php?step=rankreport*
// @license MIT
// @run-at document-end
// ==/UserScript==
//////// GLOBAL VARIABLES ////////
const PDA_API_KEY = '###PDA-APIKEY###';
function isPDA() {
const PDATestRegex = !/^(###).+(###)$/.test(PDA_API_KEY);
return PDATestRegex;
}
let GLOBAL_STATE = {
// userId: USER_ID,
factionId: undefined,
reportId: undefined,
};
//////// MODEL /////////
function getGlobalState() {
return GLOBAL_STATE;
}
function setGlobalState(newState) {
GLOBAL_STATE = { ...getGlobalState(), ...newState };
}
function getApiKey() {
return localStorage.getItem('tornDownloadCsvApiKey');
}
function setApikey(apiKey) {
localStorage.setItem('tornDownloadCsvApiKey', apiKey);
}
// function getUserId() {
// return getGlobalState().userId;
// }
// function setUserId(value, currentState) {
// currentState = currentState || getGlobalState();
// const newState = { ...currentState, userId: value };
// return setGlobalState(newState);
// }
function getFactionId() {
return getGlobalState().factionId;
}
function setFactionId(value, currentState) {
currentState = currentState || getGlobalState();
const newState = { ...currentState, factionId: value };
return setGlobalState(newState);
}
function getReportId() {
return getGlobalState().reportId;
}
function setReportId(value, currentState) {
currentState = currentState || getGlobalState();
const newState = { ...currentState, reportId: value[0] };
return setGlobalState(newState);
}
async function fetchPlayerData(apiKey) {
try {
const response = await fetch(`https://api.torn.com/user/?selections=profile&key=${apiKey}`);
const data = await response.json();
if (data.error && (data.error.error === 'Incorrect key' || data.error.error === 'Access level of this key is not high enough')) {
throw new Error(`Something went wrong: ${data.error.error}`);
}
return data;
} catch (error) {
console.error(error);
}
}
//////// UTIL FUNCITONS ////////
async function requireElement(selectors, conditionsCallback) {
try {
await new Promise((res, rej) => {
maxCycles = 500;
let current = 1;
const interval = setInterval(() => {
if (document.querySelector(selectors)) {
if (conditionsCallback === undefined) {
clearInterval(interval);
return res();
}
if (conditionsCallback(document.querySelector(selectors))) {
clearInterval(interval);
return res();
}
}
if (current === maxCycles) {
clearInterval(interval);
rej('Timeout: Could not find element on page');
}
current++;
}, 10);
});
} catch (err) {
console.error(err);
}
}
//////// API FORM CODE ////////
function submitFormCallback() {
const inputEl = document.querySelector('#api-form__input');
const submitBtnEl = document.querySelector('#api-form__submit');
const apiKey = inputEl.value;
if (apiKey.length !== 16) {
inputEl.style.border = `2px solid ${red}`;
submitBtnEl.disabled = true;
return;
}
setApikey(apiKey);
dismountApiForm();
window.location.reload();
}
function inputValidatorCallback(event) {
const inputEl = document.querySelector('#api-form__input');
const submitBtnEl = document.querySelector('#api-form__submit');
if (event.target.value.length === 16) {
submitBtnEl.disabled = false;
inputEl.style.border = '1px solid #444';
}
if (event.target.value.length !== 16) {
submitBtnEl.disabled = true;
}
}
function renderApiFormStylesheet() {
const apiFormStylesheetHTML = `
<style>
#api-form.header-wrapper-top {
display: flex;
}
#api-form.header-wrapper-top .container {
display: flex;
justify-content: start;
align-items: center;
padding-left: 20px;
}
#api-form.header-wrapper-top h2 {
display: block;
text-align: center;
margin: 0;
width: 172px;
}
#api-form.header-wrapper-top input {
background: linear-gradient(0deg,#111,#000);
border-radius: 5px;
box-shadow: 0 1px 0 hsla(0,0%,100%,.102);
box-sizing: border-box;
color: #9f9f9f;
display: inline;
font-weight: 400;
height: 24px;
width: clamp(170px, 50%, 250px);
margin: 0 0 0 21px;
outline: none;
padding: 0 10px 0 10px;
font-size: 12px;
font-style: italic;
vertical-align: middle;
border: 0;
text-shadow: none;
z-index: 100;
}
#api-form.header-wrapper-top a {
margin: 0 8px;
}
</style>`;
document.head.insertAdjacentHTML('beforeend', apiFormStylesheetHTML);
}
function renderApiForm() {
const topHeaderBannerEl = document.querySelector('#topHeaderBanner');
const apiFormHTML = `
<div id="api-form" class="header-wrapper-top">
<div class="container clear-fix">
<h2>API Key</h2>
<input
id="api-form__input"
type="text"
placeholder="Enter a full-acces API key..."
/>
<a href="#" id="api-form__submit" type="btn" disabled><span class="link-text">Submit</span</button>
</div>
</div>`;
topHeaderBannerEl.insertAdjacentHTML('afterbegin', apiFormHTML);
// set event liseners
//// Event listeners
document.querySelector('#api-form__submit').addEventListener('click', submitFormCallback);
document.querySelector('#api-form__input').addEventListener('input', inputValidatorCallback);
document.querySelector('#api-form__input').addEventListener('keyup', (event) => {
if (event.key === 'Enter' || event.keyCode === 13) {
submitFormCallback();
}
});
}
function dismountApiForm() {
document.querySelector('#api-form').remove();
}
function apiFormController() {
renderApiFormStylesheet();
renderApiForm();
}
//////// CSV RELATED CODE ////////
async function fetchRankedWarReport(reportID, apiKey) {
const response = await fetch(`https://api.torn.com/torn/${reportID}?selections=rankedwarreport&key=${apiKey}`);
return await response.json();
}
function createWarReportContent(dataObject) {
let rows = [];
dataObject = dataObject.rankedwarreport.factions;
for (const faction in dataObject) {
const factionName = dataObject[faction].name;
rows.push(factionName);
// const first = Object.keys(dataObject[faction].members)[0];
// const headerRow = Object.keys(dataObject[faction].members[first]);
const headerRow = ['Members', 'Level', 'Attacks', 'Score'];
rows.push(headerRow);
for (const member in dataObject[faction].members) {
// rows.push(Object.values(dataObject[faction].members[member]));
const rawRow = Object.values(dataObject[faction].members[member]);
const customRow = rawRow
.filter((item, index) => index !== 1)
.map((item, index) => {
if (index === 0) {
return `${item} [${member}]`;
}
return item;
});
rows.push(customRow);
console.log(member, customRow); // TEST
}
}
return rows.map((row) => (Array.isArray(row) ? row.map((value) => `"${value}"`).join(';') : `"${row}"`)).join('\r\n');
}
function downloadCsv(data, fileName) {
const blob = new Blob([data], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${fileName}.csv`;
a.addEventListener('click', () => {});
a.click();
}
// async function copyToClipBoard(data) {
// try {
// console.log('copyCSV'); // TEST
// const blob = new Blob([data], { type: 'text/csv' });
// // const clipboardItem = new ClipboardItem({
// // 'text/plain': await new Promise((res) => {
// // res(blob);
// // }),
// // });
// navigator.clipboard.writeText([await blob.text()]);
// } catch (error) {
// console.error(error); // TEST
// }
// }
async function exportCsvClickHandler(e) {
try {
const warReportData = await fetchRankedWarReport(getReportId(), getApiKey());
const warReportContent = createWarReportContent(warReportData);
downloadCsv(warReportContent, `Ranked War Report [${getReportId()}]`);
// copyToClipBoard(warReportContent);
e.target.classList.add('disable');
} catch (error) {
console.error(error); // TEST
}
}
//////// VIEW ////////
function renderStylesheet() {
const stylesheetHTML = `
<style>
#export-csv {
float: right;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px
}
#export-csv:hover {
cursor: pointer;
}
#export-csv.disable {
color: #999;
}
#export-csv svg {
padding-right: 2px
fill: currentcolor;
width: 15px;
height: 16px;
}
#export-csv.disable csv {
fill: #999;
}
</style>`;
const headEl = document.querySelector('head');
headEl.insertAdjacentHTML('beforeend', stylesheetHTML);
}
function renderExportCsvEl() {
const linkHTML = `
<span id="export-csv">
<svg
viewBox="0 0 64 64"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
xmlns:serif="http://www.serif.com/"
style="fill: currentcolor; /* fill-rule: evenodd; */ /* clip-rule: evenodd; */ /* stroke-linejoin: round; */ /* stroke-miterlimit: 2; */"
stroke="currentcolor"
>
<g id="SVGRepo_iconCarrier">
<rect id="Icons" x="-576" y="-128" width="1280" height="800" style="fill: none"></rect>
<path id="download" d="M48.089,52.095l0,4l-32.049,0l0,-4l32.049,0Zm-16.025,-4l-16.024,-16l8.098,0l-0.049,-24l15.975,0l0.048,24l7.977,0l-16.025,16Z"></path>
</g>
</svg>
Export CSV
</span>`;
const titleContainerEl = document.querySelector('.war-report-wrap .title-black');
titleContainerEl.insertAdjacentHTML('beforeend', linkHTML);
document.querySelector('#export-csv').addEventListener('click', exportCsvClickHandler);
}
// function apiFormController() {} // TODO
//////// CONTROLLERS ////////
async function initController() {
try {
renderStylesheet();
if (!getApiKey() && !isPDA()) {
renderApiFormStylesheet();
renderApiForm();
return;
}
if (isPDA()) {
setApikey(PDA_API_KEY);
}
const playerData = await fetchPlayerData(getApiKey());
const factionId = playerData.faction.faction_id;
setFactionId(factionId);
const urlParams = new URLSearchParams(window.location.href);
const reportId = urlParams.get('rankID').match(/\d*/);
setReportId(reportId);
} catch (error) {
console.error(error);
}
}
async function rankedWarCsvController() {
try {
await requireElement('.war-report-wrap .title-black');
renderExportCsvEl();
} catch (error) {
console.error(error); // TEST
}
}
//// Promise race conditions
// necessary as PDA scripts are inject after window.onload
// const PDAPromise = new Promise((res, rej) => {
// if (document.readyState === 'complete') res();
// });
// const browserPromise = new Promise((res, rej) => {
// window.addEventListener('load', () => res());
// });
(async () => {
try {
console.log('🔫 WarReport CSV script is on!'); // TEST
// await Promise.race([PDAPromise, browserPromise]);
await initController();
if (getApiKey()) {
await rankedWarCsvController();
}
} catch (error) {
console.error(error); // TEST
}
})();