// ==UserScript== // @name FUSE // @namespace https://github.com/jarekb84/FUSE // @version 2.0.0 // @author Jerry Batorski // @license Apache-2.0 // @description FUSE lets you enhance player stats in your fantasy football app by layering in your own custom data to simplify roster selection, free agent scouting, and trade evaluations. // @grant GM.xmlHttpRequest // @match https://fantasy.espn.com/* // @match https://fantasy.nfl.com/* // @match https://football.fantasysports.yahoo.com/* // @match https://*.football.cbssports.com/* // @match https://sleeper.com/* // @downloadURL none // ==/UserScript== (async function () { const version = '2.0.0'; const selectors = { playerInfo: 'fusePlayerInfo', showSettingsBtn: 'fuseShowSettings', settingPanel: { name: 'fuseSettingsPanel', tabs: 'fuseSettingsPanel__tabs', borisChen: 'fuseSettingsPanel__tabs__borisChen', subvertADown: 'fuseSettingsPanel__tabs__subvertADown', customData: 'fuseSettingsPanel__tabs__customData' }, localStorage: 'fuseStorage' } const DSTNames = getDSTNames(); await runBorisChenAutoUpdate(); runAutoUpdate(); async function runBorisChenAutoUpdate() { let savedData = getStoredData(); const isDaily = savedData.data.borisChen.autoFetch === 'Daily'; const hasBeenFetched = !!savedData.data.borisChen.lastFetched; const eligibleForAutoUpdate = isDaily && hasBeenFetched; if (!eligibleForAutoUpdate) { return Promise.resolve(); } const lastFetched = parseInt(savedData.data.borisChen.lastFetched, 10); const currentTime = Date.now(); const twentyFourHoursInMs = 300000; const isBelow24HoursOld = (currentTime - lastFetched) < twentyFourHoursInMs; if (isBelow24HoursOld) { return Promise.resolve(); } console.log('Fetching data from BorisChen'); const rawData = await fetchDataFromBorisChenWebsite(savedData.data.borisChen.scoring); savedData.data.borisChen.raw = rawData; savedData.data.borisChen.parsed = parseBorischenRawData(rawData); savedData.data.borisChen.lastFetched = Date.now(); saveToLocalStorage(savedData); return Promise.resolve(); } function parseBorischenRawData(rawTierData) { const players = {}; parseTierInfo(rawTierData.QB, players); parseTierInfo(rawTierData.RB, players); parseTierInfo(rawTierData.WR, players); parseTierInfo(rawTierData.TE, players); parseTierInfo(rawTierData.FLEX, players); parseTierInfo(splitUpDSTByPlatform(rawTierData.DST), players); parseTierInfo(rawTierData.K, players); return players; function parseTierInfo(raw, playerDictionary) { if (!raw) { return } const tiers = raw.split('\n'); tiers.forEach(tierRow => { const row = tierRow.split(': '); const tier = row[0].replace('Tier ', ''); const players = row[1]; players?.split(', ').forEach((player, index) => { addPlayerInfoToDictionary(player, `${tier}.${index + 1}`, playerDictionary); }); }); return playerDictionary; } function splitUpDSTByPlatform(raw) { if (!raw) { return; } const tiers = raw.split('\n'); const output = []; tiers.forEach(tierRow => { const row = tierRow.split(': '); const tier = row[0]; const teams = row[1]; let rowOutput = `${tier}: `; teams?.split(', ').forEach((team, index) => { // Team name is the last word const teamName = team.split(' ').pop(); if (index !== 0) { rowOutput += `, `; } rowOutput += `${teamName}`; }); output.push(rowOutput); }); return output.join('\n'); } } async function fetchDataFromBorisChenWebsite(scoring) { const scoreSuffix = { "PPR": "-PPR", "0.5 PPR": "-HALF", "Standard": "" } const positionsToGet = [ { key: 'QB', val: `QB` }, { key: 'RB', val: `RB${scoreSuffix[scoring]}` }, { key: 'WR', val: `WR${scoreSuffix[scoring]}` }, { key: 'TE', val: `TE${scoreSuffix[scoring]}` }, { key: 'FLEX', val: `FLX${scoreSuffix[scoring]}` }, { key: 'K', val: `K` }, { key: 'DST', val: `DST` }, ] let rawTierData = {}; const promises = positionsToGet.map(position => { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: makeUrlString(position.val), headers: { "Accept": "text/plain" }, onload: (response) => { if (response.status >= 200 && response.status < 400) { rawTierData[position.key] = response.responseText; resolve(); } else { reject(new Error('Request failed')); } }, onerror: () => { reject(new Error('Network error')); } }); }); }); await Promise.all(promises); return rawTierData; function makeUrlString(position) { return `https://s3-us-west-1.amazonaws.com/fftiers/out/text_${position}.txt`; } } function addPlayerInfoToDictionary(player, newInfo, playerDictionary) { const playerName = player.replace(' III', '').replace(' II', ''); let infoToSave = playerDictionary[playerName] if (infoToSave) { infoToSave += `|${newInfo}`; } else { infoToSave = newInfo; } const distNames = DSTNames[playerName] || []; if (distNames.length) { distNames.forEach(distName => { playerDictionary[distName] = infoToSave; }); return playerDictionary; } playerDictionary[playerName] = infoToSave; playerDictionary[player] = infoToSave; // some sites do use the II/III name const [first, ...rest] = playerName.split(' '); // Some sites truncate player names on some, but not all pages // ie Christian McCaffrey is sometimes shown as C. McCaffrey // storing both the long and short name allows the playerDictionary lookup // to work on all pages const shortName = `${first[0]}. ${rest.join(' ')}` playerDictionary[shortName] = infoToSave; // Sleeper shows Christian McCaffrey as C McCaffrey const shortNameWithoutPeriod = `${first[0]} ${rest.join(' ')}` playerDictionary[shortNameWithoutPeriod] = infoToSave; return playerDictionary; } function getDSTNames() { const teamNames = { '49ers': ['49ers', 'San Francisco', 'San Francisco 49ers', 'San Francisco. 49ers', 'SF'], 'Bears': ['Bears', 'Chicago', 'Chicago Bears', 'Chicago. Bears', 'CHI'], 'Bengals': ['Bengals', 'Cincinnati', 'Cincinnati Bengals', 'Cincinnati. Bengals', 'CIN'], 'Bills': ['Bills', 'Buffalo', 'Buffalo Bills', 'Buffalo. Bills', 'BUF'], 'Broncos': ['Broncos', 'Denver', 'Denver Broncos', 'Denver. Broncos', 'DEN'], 'Browns': ['Browns', 'Cleveland', 'Cleveland Browns', 'Cleveland. Browns', 'CLE'], 'Buccaneers': ['Buccaneers', 'Tampa Bay', 'Tampa Bay Buccaneers', 'Tampa Bay. Buccaneers', 'TB'], 'Cardinals': ['Cardinals', 'Arizona', 'Arizona Cardinals', 'Arizona. Cardinals', 'ARI'], 'Chargers': ['Chargers', 'Los Angeles', 'Los Angeles Chargers', 'Los Angeles. Chargers', 'LAC'], 'Chiefs': ['Chiefs', 'Kansas City', 'Kansas City Chiefs', 'Kansas City. Chiefs', 'KC'], 'Colts': ['Colts', 'Indianapolis', 'Indianapolis Colts', 'Indianapolis. Colts', 'IND'], 'Commanders': ['Commanders', 'Washington', 'Washington Commanders', 'Washington. Commanders', 'WAS'], 'Cowboys': ['Cowboys', 'Dallas', 'Dallas Cowboys', 'Dallas. Cowboys', 'DAL'], 'Dolphins': ['Dolphins', 'Miami', 'Miami Dolphins', 'Miami. Dolphins', 'MIA'], 'Eagles': ['Eagles', 'Philadelphia', 'Philadelphia Eagles', 'Philadelphia. Eagles', 'PHI'], 'Falcons': ['Falcons', 'Atlanta', 'Atlanta Falcons', 'Atlanta. Falcons', 'ATL'], 'Giants': ['Giants', 'New York', 'New York Giants', 'New York. Giants', 'NYG'], 'Jaguars': ['Jaguars', 'Jacksonville', 'Jacksonville Jaguars', 'Jacksonville. Jaguars', 'JAX'], 'Jets': ['Jets', 'New York', 'New York Jets', 'New York. Jets', 'NYJ'], 'Lions': ['Lions', 'Detroit', 'Detroit Lions', 'Detroit. Lions', 'DET'], 'Packers': ['Packers', 'Green Bay', 'Green Bay Packers', 'Green Bay. Packers', 'GB'], 'Panthers': ['Panthers', 'Carolina', 'Carolina Panthers', 'Carolina. Panthers', 'CAR'], 'Patriots': ['Patriots', 'New England', 'New England Patriots', 'New England. Patriots', 'NE'], 'Raiders': ['Raiders', 'Las Vegas', 'Las Vegas Raiders', 'Las Vegas. Raiders', 'LV'], 'Rams': ['Rams', 'Los Angeles', 'Los Angeles Rams', 'Los Angeles. Rams', 'LA'], 'Ravens': ['Ravens', 'Baltimore', 'Baltimore Ravens', 'Baltimore. Ravens', 'BAL'], 'Saints': ['Saints', 'New Orleans', 'New Orleans Saints', 'New Orleans. Saints', 'NO'], 'Seahawks': ['Seahawks', 'Seattle', 'Seattle Seahawks', 'Seattle. Seahawks', 'SEA'], 'Steelers': ['Steelers', 'Pittsburgh', 'Pittsburgh Steelers', 'Pittsburgh. Steelers', 'PIT'], 'Texans': ['Texans', 'Houston', 'Houston Texans', 'Houston. Texans', 'HOU'], 'Titans': ['Titans', 'Tennessee', 'Tennessee Titans', 'Tennessee. Titans', 'TEN'], 'Vikings': ['Vikings', 'Minnesota', 'Minnesota Vikings', 'Minnesota. Vikings', 'MIN'] } let dynamicTeamNames = {}; Object.values(teamNames).forEach(teamNamePermutations => { teamNamePermutations.forEach(teamName => { dynamicTeamNames[teamName] = teamNamePermutations; }) }); return dynamicTeamNames; } function injectFUSEInfoIntoFantasySite() { const spans = document.querySelectorAll(`.${selectors.playerInfo}`); spans.forEach(span => { span.remove(); }); const data = getStoredData().data; if (window.location.host === 'fantasy.espn.com') { updateESPNPlayerInfo(); } if (window.location.host === 'football.fantasysports.yahoo.com') { updateYahooPlayerInfo(); } if (window.location.host === 'fantasy.nfl.com') { updateNFLPlayerInfo(); } if (window.location.host === 'sleeper.com') { updateSleeperPlayerInfo(); } if (window.location.host.includes('football.cbssports.com')) { updateCBSPlayerInfo(); } function updateESPNPlayerInfo() { document.querySelectorAll('.player-column__bio .AnchorLink.link').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.player-column__bio', '.player-column__position'); }); } function updateYahooPlayerInfo() { document.querySelectorAll('.ysf-player-name a').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, 'td', '.ysf-player-detail', { fontWeight: '700' }); }); } function updateNFLPlayerInfo() { document.querySelectorAll('.playerName').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.playerNameAndInfo', 'em', { fontWeight: '900' }); }); } function updateSleeperPlayerInfo() { // matchup page document.querySelectorAll('.matchup-player-item .player-name > div:first-child').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.player-name', '.player-pos', { fontWeight: '900' }); }); // team page document.querySelectorAll('.team-roster-item .player-name').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.cell-player-meta', '.game-schedule-live-description', { fontWeight: '900' }); }); // players page document.querySelectorAll('.player-meta-container .name').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.name-container', '.position', { fontWeight: '900' }); }); // trend page document.querySelectorAll('.trending-list-item .name').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.player-details', '.position', { fontWeight: '900' }); }); // scores page document.querySelectorAll('.scores-content .player-meta .name').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, '.player-meta', '.position', { fontWeight: '900' }); }); } function updateCBSPlayerInfo() { document.querySelectorAll('.playerLink').forEach(playerNameEl => { insertFUSEPlayerInfo(playerNameEl, 'td', 'playerPositionAndTeam', { fontWeight: '900', marginLeft: '2px' }); }); } function insertFUSEPlayerInfo(playerNameEl, parentSelector, rowAfterPlayerNameSelector, styles) { const name = playerNameEl.innerText; const info = constructPlayerInfo(name); if (info) { const fuseInfo = createPlayerInfoElement(info, styles); const parentElement = playerNameEl.closest(parentSelector); const rowAfterPlayerName = parentElement?.querySelector(rowAfterPlayerNameSelector); if (rowAfterPlayerName) { rowAfterPlayerName.insertBefore(fuseInfo, rowAfterPlayerName.firstChild); } else { playerNameEl.after(fuseInfo); } } } function constructPlayerInfo(name) { let info = []; if (!name) { return ''; } const playerName = name.replace(' D/ST', '') const borisChenTier = data.borisChen.parsed[playerName]; const subvertADownValue = data.subvertADown.parsed[playerName]; const customDataValue = data.customData.parsed[playerName]; if (borisChenTier) { info.push(`${data.borisChen.prefix || ''}${borisChenTier}`) } if (subvertADownValue) { info.push(`${data.subvertADown.prefix || ''}${subvertADownValue}`) } if (customDataValue) { info.push(`${data.customData.prefix || ''}${customDataValue}`) } return info.join('/'); } function createPlayerInfoElement(info, { marginLeft = '0', marginRight = '2px', fontWeight = '900' } = {}) { const span = document.createElement('span'); span.className = selectors.playerInfo; span.style.marginRight = marginRight; span.style.marginLeft = marginLeft; span.style.fontWeight = fontWeight; span.textContent = info; return span; } } makePageButton(selectors.showSettingsBtn, '⚙', 100, editSettings); function editSettings() { if (document.getElementById(selectors.settingPanel.name)) { hideSettings(); return; } let settingsPanel = createMainSettingsPanel(); const savedData = getStoredData().data const borisChenTab = createBorisChenTab(savedData.borisChen) const toggleBorisChenTab = makeButton('BorisChen', () => { toggleTabs(borisChenTab.id) }); const subvertADownTab = createSubvertADownTab(savedData.subvertADown); const toggleSubvertADownTab = makeButton('SubvertADown', () => { toggleTabs(subvertADownTab.id) }); const customDataTab = createCustomDataTab(savedData.customData); const toggleCustomData = makeButton('CustomData', () => { toggleTabs(customDataTab.id) }); settingsPanel.appendChild(toggleBorisChenTab); settingsPanel.appendChild(toggleSubvertADownTab); settingsPanel.appendChild(toggleCustomData); settingsPanel.appendChild(borisChenTab); settingsPanel.appendChild(subvertADownTab); settingsPanel.appendChild(customDataTab); const saveBtn = makeButton('Save', () => { let state = getStoredData(); state.data.borisChen = { ...state.data.borisChen, ...getBorischenFormData() }; state.data.borisChen.parsed = parseBorischenRawData(state.data.borisChen.raw); state.data.subvertADown = { ...state.data.borisChen, ...getSubvertADownFormData() }; state.data.subvertADown.parsed = parseSubvertADownFormRawData(state.data.subvertADown.raw); state.data.customData = { ...state.data.customData, ...getCustomDataFormData() }; state.data.customData.parsed = parseCustomDataFormData(state.data.customData.raw, state.data.customData); saveToLocalStorage(state); hideSettings(); injectFUSEInfoIntoFantasySite(); }); settingsPanel.appendChild(saveBtn); settingsPanel.appendChild(makeButton('Hide', hideSettings)); settingsPanel.appendChild(createVersionElement()); document.body.insertBefore(settingsPanel, document.getElementById(selectors.showSettingsBtn).nextSibling); toggleTabs(borisChenTab.id); function createVersionElement() { const versionSpan = document.createElement('span'); versionSpan.textContent = `v${version}`; versionSpan.style.position = 'absolute'; versionSpan.style.bottom = '0'; versionSpan.style.right = '0';; versionSpan.style.fontSize = 'smaller'; return versionSpan; } function createMainSettingsPanel() { const settingsPanel = document.createElement('div'); settingsPanel.setAttribute('id', selectors.settingPanel.name); settingsPanel.style.position = 'fixed'; settingsPanel.style.top = '125px'; settingsPanel.style.right = '0'; settingsPanel.style.backgroundColor = '#f9f9f9'; settingsPanel.style.width = '410px'; settingsPanel.style.padding = '10px'; settingsPanel.style.zIndex = '9999999'; settingsPanel.style.textAlign = 'left'; settingsPanel.style.padding = '15px'; settingsPanel.style.border = '1px solid #ccc'; settingsPanel.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.1)'; return settingsPanel } function createBorisChenTab(savedData) { const tab = makeTabElement( selectors.settingPanel.borisChen, "To get the tier data from www.borisChen.co for your league's point values and paste the raw tier info into the below text areas." ); const prefixField = makeInputField( 'Prefix (optional)', `${selectors.settingPanel.borisChen}_prefix`, 'Ex: BC', savedData.prefix, ); tab.appendChild(prefixField); const positions = ['QB', 'RB', 'WR', 'TE', 'FLEX', 'DST', 'K']; if (window.GM?.info) { const dataSettings = document.createElement('div'); dataSettings.style.display = 'flex'; dataSettings.style.justifyContent = 'space-between'; dataSettings.style.marginBottom = '10px'; const scoringField = createDropdownField( 'Scoring', `${selectors.settingPanel.borisChen}_scoring`, ['Standard', '0.5 PPR', 'PPR'], savedData.scoring ); const autoFetchField = createDropdownField( 'AutoFetch', `${selectors.settingPanel.borisChen}_autoFetch`, ['Never', 'Daily'], savedData.autoFetch ); autoFetchField.style.visibility = !!savedData.lastFetched ? 'visible' : 'hidden'; const lastFetchedDateTime = formatTimestamp(savedData.lastFetched) const lastFetchedField = makeReadOnlyField('Last Fetched', `${selectors.settingPanel.borisChen}_lastFetched`, lastFetchedDateTime, savedData.lastFetched); lastFetchedField.style.visibility = !!savedData.lastFetched ? 'visible' : 'hidden'; const fetchDataBtn = makeButton('Fetch', async () => { const self = document.getElementById(`${selectors.settingPanel.borisChen}_fetchDataBtn`); self.disabled = true; const scoring = document.getElementById(`${selectors.settingPanel.borisChen}_scoring`).value const rawData = await fetchDataFromBorisChenWebsite(scoring); self.disabled = false; const lastFetchedTimestamp = Date.now() document.getElementById(`${selectors.settingPanel.borisChen}_lastFetched`).setAttribute('data-state', lastFetchedTimestamp) document.getElementById(`${selectors.settingPanel.borisChen}_lastFetched`).textContent = formatTimestamp(lastFetchedTimestamp); lastFetchedField.style.visibility = 'visible'; autoFetchField.style.visibility = 'visible'; for (const position of positions) { let positionInput = document.getElementById(`${selectors.settingPanel.borisChen}_${position}`); positionInput.value = rawData[position]; } }); fetchDataBtn.id = `${selectors.settingPanel.borisChen}_fetchDataBtn`; dataSettings.appendChild(scoringField); dataSettings.appendChild(autoFetchField); dataSettings.appendChild(lastFetchedField); tab.appendChild(dataSettings); tab.appendChild(fetchDataBtn); } for (const position of positions) { const positionField = makeTextAreaField( position, `${selectors.settingPanel.borisChen}_${position}`, savedData.raw[position], ); tab.appendChild(positionField); } return tab; } function getBorischenFormData() { const data = { raw: {}, prefix: document.getElementById(`${selectors.settingPanel.borisChen}_prefix`).value, scoring: document.getElementById(`${selectors.settingPanel.borisChen}_scoring`).value, autoFetch: document.getElementById(`${selectors.settingPanel.borisChen}_autoFetch`).value, lastFetched: document.getElementById(`${selectors.settingPanel.borisChen}_lastFetched`).getAttribute('data-state') }; const positions = ['QB', 'RB', 'WR', 'TE', 'FLEX', 'DST', 'K']; for (const position of positions) { data.raw[position] = document.getElementById(`${selectors.settingPanel.borisChen}_${position}`).value; } return data; } function createSubvertADownTab(savedData) { const tab = makeTabElement( selectors.settingPanel.subvertADown, "Copy data from https://subvertadown.com and paste the raw tier info into the below text areas." ); const prefixField = makeInputField( 'Prefix (optional)', `${selectors.settingPanel.subvertADown}_prefix`, 'Ex: SD', savedData.prefix, ); tab.appendChild(prefixField); const positions = ['DST', 'QB', 'K']; for (const position of positions) { const positionField = makeTextAreaField( position, `${selectors.settingPanel.subvertADown}_${position}`, savedData.raw[position], ); tab.appendChild(positionField); } return tab; } function getSubvertADownFormData() { const data = { raw: {}, prefix: document.getElementById(`${selectors.settingPanel.subvertADown}_prefix`).value }; const positions = ['QB', 'DST', 'K']; for (const position of positions) { data.raw[position] = document.getElementById(`${selectors.settingPanel.subvertADown}_${position}`).value; } return data; } function parseSubvertADownFormRawData(rawData) { const players = {}; processData(rawData.DST, players, true); processData(rawData.QB, players); processData(rawData.K, players); return players; function processData(input, players, isDST) { const lines = input.trim().split('\n'); let player = ''; lines.forEach(line => { // skip blank lines if (!line.trim()) { return; } if (!player) { // first line is player, next line is the value player = line.split('|')[0].trim(); if (isDST) { player = `${player}`; } } else { addPlayerInfoToDictionary(player, line.trim(), players); // reset for next iteration player = '' } }) return players; } } function createCustomDataTab(savedData) { const tab = makeTabElement( selectors.settingPanel.customData, "Paste in your own data from a spreadsheet or another website." ); const prefixField = makeInputField( 'Prefix (optional)', `${selectors.settingPanel.customData}_prefix`, 'Ex: C', savedData.prefix, ); tab.appendChild(prefixField); const dataSettings = document.createElement('div'); dataSettings.style.display = 'flex'; dataSettings.style.justifyContent = 'space-between'; dataSettings.style.marginBottom = '10px'; const delimiterField = createDropdownField( 'Delimiter', `${selectors.settingPanel.customData}_delimiter`, ['Tab', 'Space', 'Comma'], savedData.delimiter ); dataSettings.appendChild(delimiterField); const playerColumnField = createDropdownField( 'Player Column', `${selectors.settingPanel.customData}_playerColumn`, Array(20).fill().map((_, i) => i + 1), savedData.playerColumn ); dataSettings.appendChild(playerColumnField); const displayColumnField = createDropdownField( 'Display Columns', `${selectors.settingPanel.customData}_displayColumn`, ['All', ...Array(20).fill().map((_, i) => i + 1)], savedData.displayColumn ); dataSettings.appendChild(displayColumnField); tab.appendChild(dataSettings); const positionField = makeTextAreaField( 'Custom', `${selectors.settingPanel.customData}_custom`, savedData.raw['custom'], { height: '200px', placeholder: 'Patrick Mahomes, Regress to mean' } ); tab.appendChild(positionField); return tab; } function getCustomDataFormData() { const data = { raw: {}, prefix: document.getElementById(`${selectors.settingPanel.customData}_prefix`).value, delimiter: document.getElementById(`${selectors.settingPanel.customData}_delimiter`).value, playerColumn: document.getElementById(`${selectors.settingPanel.customData}_playerColumn`).value, displayColumn: document.getElementById(`${selectors.settingPanel.customData}_displayColumn`).value }; data.raw['custom'] = document.getElementById(`${selectors.settingPanel.customData}_custom`).value; return data; } function parseCustomDataFormData(rawData, savedData) { const players = {}; const lines = rawData.custom.split('\n'); const delimiters = { 'Tab': '\t', 'Space': ' ', 'Comma': ',' } for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.length === 0) continue; const columns = line.split(delimiters[savedData.delimiter]); const playerName = columns[savedData.playerColumn - 1]; if (!playerName) continue; let restOfData = []; if (savedData.displayColumn === 'All') { restOfData = [...columns.slice(0, savedData.playerColumn - 1), ...columns.slice(savedData.playerColumn)]; } else { restOfData.push(columns[savedData.displayColumn - 1]); } addPlayerInfoToDictionary(playerName, restOfData.join(',').trim(), players); } return players; } function hideAllTabs() { const tabs = document.querySelectorAll(`.${selectors.settingPanel.tabs}`); tabs.forEach(tab => { tab.style.display = 'none'; }); }; function showTab(tabId) { var element = document.getElementById(tabId); element.style.display = 'block'; } function toggleTabs(tabId) { hideAllTabs(); showTab(tabId); } } function hideSettings() { document.body.removeChild(document.getElementById(selectors.settingPanel.name)); } function getStoredData() { const storedData = localStorage.getItem(selectors.localStorage); const parsedData = JSON.parse(storedData) || {}; let defaults = { data: { borisChen: { raw: {}, parsed: {}, scoring: 'Standard', autoFetch: 'Daily', lastFetched: '' }, subvertADown: { raw: {}, parsed: {} }, customData: { raw: {}, parsed: {}, delimiter: 'Tab', playerColumn: 1, displayColumn: 'All' } } }; const result = mergeDeep(defaults, parsedData) console.log(result) return result; } function saveToLocalStorage(data) { localStorage.setItem(selectors.localStorage, JSON.stringify(data)); } function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); } // copy pasta from https://stackoverflow.com/a/34749873/276681 // didn't want to pull in lodash just yet function mergeDeep(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return mergeDeep(target, ...sources); } function makePageButton(id, text, offset, onClick) { const existingBtn = document.getElementById(id); if (document.contains(existingBtn)) { existingBtn.remove(); } const button = makeButton(text, onClick); button.id = id; button.style.position = 'fixed'; button.style.top = `${offset}px`; button.style.right = 0; button.style.color = 'green'; button.style.zIndex = '9999999'; button.style.fontSize = '16px'; button.style.padding = '1px 6px'; button.style.marginRight = '0'; const body = document.getElementsByTagName('body')[0]; body.appendChild(button); return button; } function makeButton(text, onClick) { const button = document.createElement('button'); button.innerHTML = text; button.style.padding = '5px'; button.style.marginRight = '5px'; button.style.backgroundColor = 'lightgray'; button.style.border = '1px solid black'; button.style.borderRadius = '5px'; button.style.cursor = 'pointer'; button.addEventListener('click', onClick); return button; } function makeTabElement(id, content) { const tab = document.createElement('div'); tab.id = id; tab.className = selectors.settingPanel.tabs; tab.style.padding = '10px'; tab.style.maxHeight = '70vh'; tab.style.overflowY = 'auto'; const helpText = document.createElement('p'); helpText.textContent = content; helpText.style.marginBottom = '10px'; tab.appendChild(helpText); return tab; } function makeLabelElement(text) { const label = document.createElement('label'); label.textContent = text; label.style.display = 'block'; return label; } function makeReadOnlyField(labelText, id, value, state) { const field = document.createElement('div'); const label = makeLabelElement(labelText); const displayValue = document.createElement('span'); displayValue.id = id; displayValue.setAttribute('data-state', state); displayValue.style.marginBottom = '10px'; displayValue.textContent = value; field.appendChild(label); field.appendChild(displayValue); return field; } function makeInputField(labelText, id, placeholder, value,) { const field = document.createElement('div'); const label = makeLabelElement(labelText) const input = document.createElement('input'); input.id = id; input.placeholder = placeholder; input.value = value || ''; input.style.marginBottom = '10px'; input.style.backgroundColor = 'white'; input.style.border = '1px solid black'; input.style.padding = '3px'; field.appendChild(label); field.appendChild(input); return field; } function makeTextAreaField(labelText, id, value = '', { width = '350px', height = '60px', placeholder = '' } = {}) { const field = document.createElement('div'); const label = makeLabelElement(labelText); const textarea = document.createElement('textarea'); textarea.id = id; textarea.value = value; textarea.style.width = width; textarea.style.height = height; textarea.style.marginBottom = '10px'; textarea.style.backgroundColor = 'white'; textarea.style.border = '1px solid black'; textarea.style.padding = '3px'; textarea.placeholder = placeholder; field.appendChild(label); field.appendChild(textarea); return field; } function createDropdownField(labelText, id, options, selectedValue) { const field = document.createElement('div'); const label = makeLabelElement(labelText); field.appendChild(label); const selectElement = document.createElement('select'); selectElement.id = id; options.forEach((option) => { const optionElement = document.createElement('option'); optionElement.value = option; optionElement.textContent = option; if (option.toString() === selectedValue.toString()) { optionElement.selected = true; } selectElement.appendChild(optionElement); }); field.appendChild(selectElement); return field; } function formatTimestamp(timestamp) { const date = new Date(parseInt(timestamp, 10)); if (isNaN(date.getTime())) { return ''; } // Extracting the date in YYYY/MM/DD format const dateOptions = { year: 'numeric', month: '2-digit', day: '2-digit' }; const formattedDate = new Intl.DateTimeFormat('en-US', dateOptions).format(date); // Extracting the time in 12-hour format with AM/PM const timeOptions = { hour: '2-digit', minute: '2-digit', hour12: true }; const formattedTime = new Intl.DateTimeFormat('en-US', timeOptions).format(date).toLowerCase(); // Convert to lowercase to get "am/pm" return `${formattedDate} @ ${formattedTime}`; } function runAutoUpdate() { if (window.fuse?.autoUpdateObserver) { window.fuse.autoUpdateObserver.disconnect(); } else { window.fuse = {}; } // Update player info whenever the user causes the page content to update // ie when applying position filters on the free agents page const observer = new MutationObserver(function () { // pause mutation checks while FUSE updates the page // avoids an infinite loop of updates observer.disconnect(); injectFUSEInfoIntoFantasySite(); // resume monitoring for mutations observer.observe(document.body, { childList: true, subtree: true }); }); observer.observe(document.body, { childList: true, subtree: true }); window.fuse.autoUpdateObserver = observer; } } )();