您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays a button that allows users to download a csv version of their war report
// ==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 } })();