import InADayParser from './libs/InADayParser'; import requestCreator from './libs/requestCreator'; import getTranslations from './i18n/extendedPlayerProfile'; import renderTodaysStats from './common/renderTodaysStats'; import showPopup from './utils/showPopup'; import showEnnoblementsPopup from './common/showEnnoblementsPopup'; import showHistoryPopup from './common/showHistoryPopup'; import hyphensToCamelCase from './utils/hyphensToCamelCase'; import { generatePaginationItems, getContainerStyles, setPage, getPage, } from './utils/pagination'; import getIDFromURL from './utils/getIDFromURL'; import getCurrentServer from './utils/getCurrentServer'; import getServerVersionCode from './utils/getServerVersionCode'; import { formatDate } from './utils/date'; import * as twutils from './utils/tribalwars'; import * as twhelputils from './utils/twhelp'; import { setItem, getItem } from './utils/localStorage'; // ==UserScript== // @name Extended player profile // @namespace https://github.com/tribalwarshelp/scripts // @downloadURL https://raw.githubusercontent.com/tribalwarshelp/scripts/master/dist/extendedPlayerProfile.js // @updateURL https://raw.githubusercontent.com/tribalwarshelp/scripts/master/dist/extendedPlayerProfile.js // @version 1.2.1 // @description Extended player profile // @author Kichiyaki https://dwysokinski.me/ // @match *://*/game.php*screen=info_player* // @grant none // @run-at document-end // ==/UserScript== const SERVER = getCurrentServer(); const VERSION = getServerVersionCode(SERVER); let PLAYER_ID = getIDFromURL(window.location.search); const CURRENT_PLAYER_ID = parseInt(game_data.player.id); if (isNaN(PLAYER_ID) || !PLAYER_ID) { PLAYER_ID = CURRENT_PLAYER_ID; } const LOCAL_STORAGE_KEY = 'kichiyaki_extended_player_profile' + PLAYER_ID; const PLAYER_QUERY = ` query player($server: String!, $id: Int!, $limit: Int, $sort: [String!], $filter: DailyPlayerStatsFilter) { player(server: $server, id: $id) { id name bestRank bestRankAt mostPoints mostPointsAt mostVillages mostVillagesAt servers joinedAt nameChanges { oldName newName changeDate } dailyGrowth } dailyPlayerStats(server: $server, limit: $limit, sort: $sort, filter: $filter) { items { rank rankAtt rankDef rankSup rankTotal points scoreAtt scoreAtt scoreDef scoreSup scoreTotal villages } } } `; const TRIBE_CHANGES_QUERY = ` query tribeChanges($server: String!, $limit: Int, $offset: Int, $sort: [String!], $filter: TribeChangeFilter!) { tribeChanges(server: $server, limit: $limit, offset: $offset, sort: $sort, filter: $filter) { total items { oldTribe { id tag } newTribe { id tag } createdAt } } } `; const TRIBE_CHANGES_PAGINATION_CONTAINER_ID = 'tribeChangesPagination'; const TRIBE_CHANGES_PER_PAGE = 15; const PLAYER_HISTORY_AND_PLAYER_DAILY_STATS_QUERY = ` query playerHistoryAndPlayerDailyStats($server: String!, $playerHistoryFilter: PlayerHistoryFilter!, $dailyPlayerStatsFilter: DailyPlayerStatsFilter!, $limit: Int, $offset: Int, $sort: [String!]) { playerHistory(server: $server, limit: $limit, offset: $offset, sort: $sort, filter: $playerHistoryFilter) { total items { totalVillages points rank scoreAtt rankAtt scoreDef rankDef scoreSup rankSup scoreTotal rankTotal tribe { id tag } createDate } } dailyPlayerStats(server: $server, limit: $limit, offset: $offset, sort: $sort, filter: $dailyPlayerStatsFilter) { items { points scoreAtt scoreAtt scoreDef scoreSup scoreTotal villages createDate } } } `; const PLAYER_HISTORY_PER_PAGE = 15; const ENNOBLEMENTS_QUERY = ` query ennoblements($server: String!, $limit: Int, $offset: Int, $sort: [String!], $filter: EnnoblementFilter!) { ennoblements(server: $server, limit: $limit, offset: $offset, sort: $sort, filter: $filter) { total items { village { id name x y } oldOwner { id name } oldOwnerTribe { id tag } newOwner { id name } newOwnerTribe { id tag } ennobledAt } } } `; const ENNOBLEMENTS_PER_PAGE = 15; const profileInfoTBody = document.querySelector('#player_info > tbody'); const actionContainer = PLAYER_ID === CURRENT_PLAYER_ID ? profileInfoTBody : document.querySelector( '#content_value > table > tbody > tr > td:nth-child(1) > table:nth-child(2) > tbody' ); const otherElementContainer = document.querySelector( PLAYER_ID === CURRENT_PLAYER_ID ? '#content_value > table:nth-child(7) > tbody > tr > td:nth-child(2)' : '#content_value > table > tbody > tr > td:nth-child(2)' ); const translations = getTranslations(); const loadDataFromCache = () => { return getItem(LOCAL_STORAGE_KEY); }; const cachePlayerData = (data = {}) => { setItem(LOCAL_STORAGE_KEY, data); }; const loadInADayData = async (type, { name, ...rest } = {}) => { try { const response = await fetch( TribalWars.buildURL('', { screen: 'ranking', mode: 'in_a_day', type, name: name ? name : '', }) ); const html = await response.text(); if (!html) { throw new Error(); } const res = new InADayParser(html, rest).parse(); if (res.length === 0) { throw new Error(); } return res[0]; } catch (error) { return { rank: 0, playerID: 0, score: 0, tribeID: 0, tribe: '', date: new Date(), }; } }; const loadData = async () => { const data = await requestCreator({ query: PLAYER_QUERY, variables: { server: SERVER, id: PLAYER_ID, limit: 1, sort: ['createDate DESC'], filter: { playerID: [PLAYER_ID], }, }, }); if (data.player) { const inADay = {}; const filter = { name: data.player.name, playerID: data.player.id, }; for (let type of [ 'kill_att', 'kill_def', 'kill_sup', 'loot_res', 'loot_vil', 'scavenge', 'conquer', ]) { inADay[hyphensToCamelCase(type.replace('kill_', ''))] = await loadInADayData(type, filter); } data.player.inADay = inADay; } cachePlayerData(data); return data; }; const renderTr = ({ title, data, id }) => { let tr = document.querySelector('#' + id); if (!tr) { tr = document.createElement('tr'); tr.id = id; tr.appendChild(document.createElement('td')); tr.appendChild(document.createElement('td')); profileInfoTBody.append(tr); } tr.children[0].innerHTML = title; tr.children[1].innerHTML = data; }; const renderPlayerServers = player => { let playerServers = document.querySelector('#playerServers'); if (!playerServers) { playerServers = document.createElement('table'); playerServers.id = 'playerServers'; playerServers.classList.add('vis'); playerServers.width = '100%'; playerServers.innerHTML = ` ${translations.playerServers} `; otherElementContainer.prepend(playerServers); } playerServers.querySelector('td').innerHTML = player.servers .sort() .map( server => `${server}` ) .join(''); }; const renderPlayerOtherNames = player => { let playerOtherNames = document.querySelector('#playerOtherNames'); if (!playerOtherNames) { playerOtherNames = document.createElement('div'); playerOtherNames.id = 'playerOtherNames'; playerOtherNames.width = '100%'; otherElementContainer.prepend(playerOtherNames); } playerOtherNames.innerHTML = ` ${player.nameChanges .map(nameChange => { return ` `; }) .join('')}
${translations.oldName} ${translations.newName} ${translations.date}
${nameChange.oldName} ${nameChange.newName} ${formatDate(nameChange.changeDate, { year: 'numeric', month: '2-digit', day: '2-digit', })}
`; }; const renderInADayRanks = player => { let inADayRanks = document.querySelector('#inADayRanks'); if (!inADayRanks) { inADayRanks = document.createElement('div'); inADayRanks.id = 'inADayRanks'; inADayRanks.width = '100%'; otherElementContainer.prepend(inADayRanks); } inADayRanks.innerHTML = `
${translations.inADayBestScores}
${translations.unitsDefeatedWhileAttacking} ${player.inADay.att.score.toLocaleString()} (${ player.inADay.att.rank }.)
${translations.unitsDefeatedWhileDefending} ${player.inADay.def.score.toLocaleString()} (${ player.inADay.def.rank }.)
${translations.unitsDefeatedWhileSupporting} ${player.inADay.sup.score.toLocaleString()} (${ player.inADay.sup.rank }.)
${translations.resourcesPlundered} ${player.inADay.lootRes.score.toLocaleString()} (${ player.inADay.lootRes.rank }.)
${translations.villagesPlundered} ${player.inADay.lootVil.score.toLocaleString()} (${ player.inADay.lootVil.rank }.)
${translations.resourcesGathered} ${player.inADay.scavenge.score.toLocaleString()} (${ player.inADay.scavenge.rank }.)
${translations.villagesConquered} ${player.inADay.conquer.score.toLocaleString()} (${ player.inADay.conquer.rank }.)
`; }; const render = ({ player, dailyPlayerStats }) => { [ { title: translations.joinedAt + ':', data: formatDate(player.joinedAt), id: 'joined_at', }, { title: translations.dailyGrowth + ':', data: player.dailyGrowth.toLocaleString(), id: 'dg', }, { title: translations.bestRank + ':', data: player.bestRank + ' ' + `(${formatDate(player.bestRankAt)})`, id: 'best_rank', }, { title: translations.mostPoints + ':', data: player.mostPoints.toLocaleString() + ' ' + `(${formatDate(player.mostPointsAt)})`, id: 'most_points', }, { title: translations.mostVillages + ':', data: player.mostVillages + ' ' + `(${formatDate(player.mostVillagesAt)})`, id: 'most_villages', }, ].forEach(data => { renderTr(data); }); renderInADayRanks(player); if (dailyPlayerStats && dailyPlayerStats.items.length > 0) { renderTodaysStats(otherElementContainer, dailyPlayerStats.items[0]); } if (player.nameChanges.length > 0) { renderPlayerOtherNames(player); } if (player.servers.length > 0) { renderPlayerServers(player); } }; const renderTribeChanges = (e, currentPage, tribeChanges) => { const paginationItems = generatePaginationItems({ total: tribeChanges.total, limit: TRIBE_CHANGES_PER_PAGE, currentPage, }); const html = `
${paginationItems.join('')}
${tribeChanges.items .map(tribeChange => { let rowHTML = '' + ``; if (tribeChange.newTribe) { rowHTML += ``; } else { rowHTML += ''; } if (tribeChange.oldTribe) { rowHTML += ``; } else { rowHTML += ''; } return rowHTML + ''; }) .join('')}
${translations.date} ${translations.newTribe} ${translations.oldTribe}
${formatDate(tribeChange.createdAt)}${tribeChange.newTribe.tag}-${tribeChange.oldTribe.tag}-
`; showPopup({ e, title: translations.tribeChanges, id: 'tribeChanges', html, }); document .querySelectorAll('#' + TRIBE_CHANGES_PAGINATION_CONTAINER_ID + ' a') .forEach(el => { el.addEventListener('click', handleShowTribeChangesButtonClick); }); }; const handleShowTribeChangesButtonClick = async e => { e.preventDefault(); const page = getPage(e.target); if (!isNaN(page)) { const data = await requestCreator({ query: TRIBE_CHANGES_QUERY, variables: { filter: { playerID: [PLAYER_ID], }, sort: ['createdAt DESC'], offset: TRIBE_CHANGES_PER_PAGE * (page - 1), limit: TRIBE_CHANGES_PER_PAGE, server: SERVER, }, }); renderTribeChanges(e, page, data.tribeChanges); } }; const handleShowPlayerHistoryClick = async e => { e.preventDefault(); const page = getPage(e.target); if (!isNaN(page)) { try { const filter = { playerID: [PLAYER_ID], }; const { playerHistory, dailyPlayerStats } = await requestCreator({ query: PLAYER_HISTORY_AND_PLAYER_DAILY_STATS_QUERY, variables: { server: SERVER, playerHistoryFilter: filter, offset: PLAYER_HISTORY_PER_PAGE * (page - 1), limit: PLAYER_HISTORY_PER_PAGE, sort: ['createDate DESC'], dailyPlayerStatsFilter: filter, }, }); showHistoryPopup(e, playerHistory, dailyPlayerStats, { currentPage: page, limit: PLAYER_HISTORY_PER_PAGE, onPageChange: handleShowPlayerHistoryClick, tribe: false, }); } catch (error) { console.log('cannot load player history', error); } } }; const handleShowPlayerEnnoblementsClick = async e => { e.preventDefault(); const page = getPage(e.target); if (!isNaN(page)) { const data = await requestCreator({ query: ENNOBLEMENTS_QUERY, variables: { filter: { or: { oldOwnerID: [PLAYER_ID], newOwnerID: [PLAYER_ID], }, }, offset: ENNOBLEMENTS_PER_PAGE * (page - 1), limit: ENNOBLEMENTS_PER_PAGE, sort: ['ennobledAt DESC'], server: SERVER, }, }); showEnnoblementsPopup(e, data.ennoblements, { currentPage: page, limit: ENNOBLEMENTS_PER_PAGE, onPageChange: handleShowPlayerEnnoblementsClick, }); } }; const handleExportPlayerVillagesButtonClick = e => { e.preventDefault(); Dialog.show( translations.exportedVillages, `` ); }; const wrapAction = action => { const actionWrapperTd = document.createElement('td'); actionWrapperTd.colSpan = '2'; actionWrapperTd.append(action); const actionWrapperTr = document.createElement('tr'); actionWrapperTr.appendChild(actionWrapperTd); return actionWrapperTr; }; const renderActions = () => { const linkToTWHelp = document.createElement('a'); linkToTWHelp.href = twhelputils.buildPlayerURL(VERSION, SERVER, PLAYER_ID); linkToTWHelp.innerHTML = translations.action.linkToTWHelp; actionContainer.appendChild(wrapAction(linkToTWHelp)); const showTribeChanges = document.createElement('a'); showTribeChanges.href = '#'; setPage(showTribeChanges, '1'); showTribeChanges.innerHTML = translations.action.showTribeChanges; showTribeChanges.addEventListener('click', handleShowTribeChangesButtonClick); actionContainer.appendChild(wrapAction(showTribeChanges)); const showPlayerHistory = document.createElement('a'); showPlayerHistory.href = '#'; setPage(showPlayerHistory, '1'); showPlayerHistory.innerHTML = translations.action.showHistory; showPlayerHistory.addEventListener('click', handleShowPlayerHistoryClick); actionContainer.appendChild(wrapAction(showPlayerHistory)); const showEnnoblements = document.createElement('a'); showEnnoblements.href = '#'; setPage(showEnnoblements, '1'); showEnnoblements.innerHTML = translations.action.showEnnoblements; showEnnoblements.addEventListener('click', handleShowPlayerEnnoblementsClick); actionContainer.appendChild(wrapAction(showEnnoblements)); const exportPlayerVillages = document.createElement('a'); exportPlayerVillages.href = '#'; exportPlayerVillages.innerHTML = translations.action.exportVillages; exportPlayerVillages.addEventListener( 'click', handleExportPlayerVillagesButtonClick ); actionContainer.appendChild(wrapAction(exportPlayerVillages)); }; (async function () { try { renderActions(); const dataFromCache = loadDataFromCache(); if (dataFromCache && dataFromCache.player) { render(dataFromCache); } const dataFromAPI = await loadData(); if (dataFromAPI) { render(dataFromAPI); } } catch (error) { console.log('extended player profile', error); } })();